Compare commits

...

2 Commits

Author SHA1 Message Date
Flatlogic Bot
dc9d779de8 Uptime 2026-02-25 23:31:37 +00:00
Flatlogic Bot
6fabe9540d Autosave: 20260225-232625 2026-02-25 23:26:27 +00:00
14 changed files with 728 additions and 598 deletions

167
admin.php
View File

@ -1,166 +1,3 @@
<?php
require_once __DIR__ . '/db/config.php';
// Simple handling of form submissions
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_POST['action']) && $_POST['action'] === 'add') {
$keywords = $_POST['keywords'] ?? '';
$answer = $_POST['answer'] ?? '';
if ($keywords && $answer) {
$stmt = db()->prepare("INSERT INTO faqs (keywords, answer) VALUES (?, ?)");
$stmt->execute([$keywords, $answer]);
}
} elseif (isset($_POST['action']) && $_POST['action'] === 'delete') {
$id = $_POST['id'] ?? 0;
if ($id) {
$stmt = db()->prepare("DELETE FROM faqs WHERE id = ?");
$stmt->execute([$id]);
}
} elseif (isset($_POST['action']) && $_POST['action'] === 'update_settings') {
$token = $_POST['telegram_token'] ?? '';
$stmt = db()->prepare("INSERT INTO settings (setting_key, setting_value) VALUES ('telegram_token', ?) ON DUPLICATE KEY UPDATE setting_value = ?");
$stmt->execute([$token, $token]);
}
header("Location: admin.php");
exit;
}
$faqs = db()->query("SELECT * FROM faqs ORDER BY created_at DESC")->fetchAll();
$messages = db()->query("SELECT * FROM messages ORDER BY created_at DESC LIMIT 50")->fetchAll();
$telegramToken = '';
$stmt = db()->query("SELECT setting_value FROM settings WHERE setting_key = 'telegram_token'");
$row = $stmt->fetch();
if ($row) {
$telegramToken = $row['setting_value'];
}
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Admin - FAQ Manager</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
<style>
.btn-delete {
background: #dc3545;
color: white;
border: none;
padding: 0.25rem 0.5rem;
border-radius: 4px;
cursor: pointer;
}
.btn-add {
background: #212529;
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 4px;
cursor: pointer;
margin-top: 1rem;
}
</style>
</head>
<body>
<div class="bg-animations">
<div class="blob blob-1"></div>
<div class="blob blob-2"></div>
<div class="blob blob-3"></div>
</div>
<div class="admin-container">
<div style="display: flex; justify-content: space-between; align-items: center;">
<h1>FAQ Manager</h1>
<a href="index.php" class="admin-link">Back to Chat</a>
</div>
<div class="admin-card" style="background: rgba(255, 255, 255, 0.6); padding: 2rem; border-radius: 20px; border: 1px solid rgba(255, 255, 255, 0.5); margin-bottom: 2.5rem; box-shadow: 0 10px 30px rgba(0,0,0,0.05);">
<h3 style="margin-top: 0; margin-bottom: 1.5rem; font-weight: 700;">Telegram Bot Settings</h3>
<form method="POST">
<input type="hidden" name="action" value="update_settings">
<div class="form-group">
<label for="telegram_token">Telegram Bot Token</label>
<input type="text" name="telegram_token" id="telegram_token" class="form-control" placeholder="Paste your bot token from @BotFather" value="<?= htmlspecialchars($telegramToken) ?>">
</div>
<p style="font-size: 0.85em; color: #555; margin-top: 0.5rem;">
Webhook URL: <code>https://<?= $_SERVER['HTTP_HOST'] ?>/api/telegram_webhook.php</code>
</p>
<button type="submit" class="btn-add" style="background: #0088cc; color: white; border: none; padding: 0.8rem 1.5rem; border-radius: 12px; cursor: pointer; font-weight: 600; width: 100%; transition: all 0.3s ease;">Save Token</button>
</form>
</div>
<div class="admin-card" style="background: rgba(255, 255, 255, 0.6); padding: 2rem; border-radius: 20px; border: 1px solid rgba(255, 255, 255, 0.5); margin-bottom: 2.5rem; box-shadow: 0 10px 30px rgba(0,0,0,0.05);">
<h3 style="margin-top: 0; margin-bottom: 1.5rem; font-weight: 700;">Add New FAQ</h3>
<form method="POST">
<input type="hidden" name="action" value="add">
<div class="form-group">
<label for="keywords">Keywords (comma separated)</label>
<input type="text" name="keywords" id="keywords" class="form-control" placeholder="e.g. price, cost, dollar" required>
</div>
<div class="form-group">
<label for="answer">Answer</label>
<textarea name="answer" id="answer" class="form-control" rows="3" placeholder="Enter the answer..." required></textarea>
</div>
<button type="submit" class="btn-add" style="background: #212529; color: white; border: none; padding: 0.8rem 1.5rem; border-radius: 12px; cursor: pointer; font-weight: 600; width: 100%; transition: all 0.3s ease;">Save FAQ</button>
</form>
</div>
<h3>Existing FAQs</h3>
<table class="table">
<thead>
<tr>
<th>Keywords</th>
<th>Answer</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($faqs as $faq): ?>
<tr>
<td><?= htmlspecialchars($faq['keywords']) ?></td>
<td><?= htmlspecialchars($faq['answer']) ?></td>
<td>
<form method="POST" style="display:inline;" onsubmit="return confirm('Delete this FAQ?');">
<input type="hidden" name="action" value="delete">
<input type="hidden" name="id" value="<?= $faq['id'] ?>">
<button type="submit" class="btn-delete">Delete</button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<h3 style="margin-top: 3rem; margin-bottom: 1rem;">Recent Chat History (Last 50)</h3>
<div style="overflow-x: auto; background: rgba(255, 255, 255, 0.4); padding: 1rem; border-radius: 12px; border: 1px solid rgba(255, 255, 255, 0.3);">
<table class="table" style="width: 100%;">
<thead>
<tr>
<th style="width: 15%;">Time</th>
<th style="width: 35%;">User Message</th>
<th style="width: 50%;">AI Response</th>
</tr>
</thead>
<tbody>
<?php if (empty($messages)): ?>
<tr>
<td colspan="3" style="text-align: center; color: #777;">No messages yet.</td>
</tr>
<?php else: ?>
<?php foreach ($messages as $msg): ?>
<tr>
<td style="white-space: nowrap; font-size: 0.85em; color: #555;"><?= htmlspecialchars($msg['created_at']) ?></td>
<td style="background: rgba(255, 255, 255, 0.3); border-radius: 8px; padding: 8px;"><?= htmlspecialchars($msg['user_message']) ?></td>
<td style="background: rgba(255, 255, 255, 0.5); border-radius: 8px; padding: 8px;"><?= htmlspecialchars($msg['ai_response']) ?></td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</body>
</html>
header("Location: index.php");
exit;

62
api/admin_actions.php Normal file
View File

@ -0,0 +1,62 @@
<?php
require_once __DIR__ . '/../db/config.php';
header('Content-Type: application/json');
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
die(json_encode(['success' => false, 'error' => 'Method not allowed']));
}
$action = $_POST['action'] ?? '';
try {
$db = db();
if ($action === 'add_url') {
$name = $_POST['name'] ?? '';
$url = $_POST['url'] ?? '';
if ($name && $url) {
$stmt = $db->prepare("INSERT INTO urls (name, url) VALUES (?, ?)");
$stmt->execute([$name, $url]);
echo json_encode(['success' => true]);
} else {
echo json_encode(['success' => false, 'error' => 'Missing parameters']);
}
} elseif ($action === 'delete_url') {
$id = $_POST['id'] ?? 0;
if ($id) {
$stmt = $db->prepare("DELETE FROM urls WHERE id = ?");
$stmt->execute([$id]);
echo json_encode(['success' => true]);
}
} elseif ($action === 'update_settings') {
$token = $_POST['telegram_bot_token'] ?? '';
$chat_id = $_POST['telegram_chat_id'] ?? '';
$stmt = $db->prepare("INSERT INTO settings (name, value) VALUES ('telegram_bot_token', ?) ON DUPLICATE KEY UPDATE value = ?");
$stmt->execute([$token, $token]);
$stmt = $db->prepare("INSERT INTO settings (name, value) VALUES ('telegram_chat_id', ?) ON DUPLICATE KEY UPDATE value = ?");
$stmt->execute([$chat_id, $chat_id]);
// Auto-register webhook for Telegram commands (/start, /status)
if (!empty($token)) {
$protocol = isset($_SERVER['HTTP_X_FORWARDED_PROTO']) ? $_SERVER['HTTP_X_FORWARDED_PROTO'] : (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http");
$host = isset($_SERVER['HTTP_X_FORWARDED_HOST']) ? $_SERVER['HTTP_X_FORWARDED_HOST'] : (isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : 'localhost');
$webhookUrl = "$protocol://$host/api/telegram_webhook.php";
$url = "https://api.telegram.org/bot$token/setWebhook?url=" . urlencode($webhookUrl);
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_exec($ch);
curl_close($ch);
}
echo json_encode(['success' => true]);
} else {
echo json_encode(['success' => false, 'error' => 'Unknown action']);
}
} catch (Exception $e) {
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
}

57
api/status.php Normal file
View File

@ -0,0 +1,57 @@
<?php
require_once __DIR__ . '/../db/config.php';
header('Content-Type: application/json');
try {
$db = db();
$urls = $db->query("SELECT * FROM urls WHERE is_active = 1")->fetchAll();
$data = [];
foreach ($urls as $url) {
// Fetch last 60 logs for the candlestick/sparkline
$stmt = $db->prepare("SELECT status, response_time, checked_at FROM logs WHERE url_id = ? ORDER BY checked_at DESC LIMIT 60");
$stmt->execute([$url['id']]);
$logs = array_reverse($stmt->fetchAll());
// Calculate uptime percentage (last 24h or total)
$stmt = $db->prepare("SELECT
COUNT(*) as total,
SUM(CASE WHEN status = 'up' THEN 1 ELSE 0 END) as up_count
FROM logs WHERE url_id = ? AND checked_at > DATE_SUB(NOW(), INTERVAL 24 HOUR)");
$stmt->execute([$url['id']]);
$stats = $stmt->fetch();
$uptime = ($stats['total'] > 0) ? round(($stats['up_count'] / $stats['total']) * 100, 2) : 100;
$data[] = [
'id' => $url['id'],
'name' => $url['name'],
'url' => $url['url'],
'status' => $url['status'],
'last_check' => $url['last_check'],
'response_time' => $url['response_time'],
'uptime' => $uptime,
'history' => $logs
];
}
// Check if monitor is running
$isMonitorRunning = false;
$lockFile = __DIR__ . '/../monitor.lock';
if (file_exists($lockFile)) {
$fp = fopen($lockFile, 'r');
if (!flock($fp, LOCK_EX | LOCK_NB)) {
$isMonitorRunning = true;
}
fclose($fp);
}
echo json_encode([
'success' => true,
'monitor_running' => $isMonitorRunning,
'data' => $data
]);
} catch (Exception $e) {
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
}

View File

@ -1,6 +1,5 @@
<?php
require_once __DIR__ . '/../db/config.php';
require_once __DIR__ . '/../ai/LocalAIApi.php';
// Get Telegram Update
$content = file_get_contents("php://input");
@ -19,73 +18,69 @@ if (empty($text)) {
}
// Get Telegram Token from DB
$stmt = db()->query("SELECT setting_value FROM settings WHERE setting_key = 'telegram_token'");
$db = db();
$stmt = $db->prepare("SELECT value FROM settings WHERE name = 'telegram_bot_token'");
$stmt->execute();
$token = $stmt->fetchColumn();
if (!$token) {
error_log("Telegram Error: No bot token found in settings.");
exit;
}
// Check if telegram_chat_id is already set, if not, save it automatically!
$stmt = $db->prepare("SELECT value FROM settings WHERE name = 'telegram_chat_id'");
$stmt->execute();
$currentChatId = $stmt->fetchColumn();
if (empty($currentChatId)) {
$stmt = $db->prepare("INSERT INTO settings (name, value) VALUES (?, ?) ON DUPLICATE KEY UPDATE value = ?");
$stmt->execute([$chatId, $chatId, $chatId]);
}
function sendTelegramMessage($chatId, $text, $token) {
$url = "https://api.telegram.org/bot$token/sendMessage";
$data = [
'chat_id' => $chatId,
'text' => $text,
'parse_mode' => 'Markdown'
'parse_mode' => 'HTML'
];
$options = [
'http' => [
'header' => "Content-type: application/x-www-form-urlencoded\r\n",
'method' => 'POST',
'content' => http_build_query($data),
],
];
$context = stream_context_create($options);
return file_get_contents($url, false, $context);
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));
curl_exec($ch);
curl_close($ch);
}
// Process with AI (Similar logic to api/chat.php)
try {
// 1. Fetch Knowledge Base
$stmt = db()->query("SELECT keywords, answer FROM faqs");
$faqs = $stmt->fetchAll(PDO::FETCH_ASSOC);
$knowledgeBase = "Here is the knowledge base for this website:\n\n";
foreach ($faqs as $faq) {
$knowledgeBase .= "Q: " . $faq['keywords'] . "\nA: " . $faq['answer'] . "\n---\n";
}
$systemPrompt = "You are a helpful AI assistant integrated with Telegram. " .
"Use the provided Knowledge Base to answer user questions. " .
"Keep answers concise for mobile reading. Use Markdown for formatting.\n\n" .
$knowledgeBase;
// 2. Call AI
$response = LocalAIApi::createResponse([
'model' => 'gpt-4o-mini',
'input' => [
['role' => 'system', 'content' => $systemPrompt],
['role' => 'user', 'content' => $text],
]
]);
if (!empty($response['success'])) {
$aiReply = LocalAIApi::extractText($response);
// 3. Save History
try {
$stmt = db()->prepare("INSERT INTO messages (user_message, ai_response) VALUES (?, ?)");
$stmt->execute(["[Telegram] " . $text, $aiReply]);
} catch (Exception $e) {}
// 4. Send back to Telegram
sendTelegramMessage($chatId, $aiReply, $token);
if ($text === '/start') {
$reply = "👋 <b>Welcome to Uptime Monitor Bot!</b>\n\n";
$reply .= "Your Chat ID is: <code>$chatId</code>\n\n";
if (empty($currentChatId)) {
$reply .= "✅ <b>Success!</b> Your Chat ID has been automatically saved to the Uptime Dashboard. You will now receive notifications here.\n\n";
} else {
sendTelegramMessage($chatId, "I'm sorry, I encountered an error processing your request.", $token);
$reply .= "Use this Chat ID in the Settings of your Uptime Dashboard if it's not already set.\n\n";
}
} catch (Exception $e) {
error_log("Telegram Webhook Error: " . $e->getMessage());
}
$reply .= "Available commands:\n/status - Check current URL statuses";
sendTelegramMessage($chatId, $reply, $token);
} elseif ($text === '/status') {
$urls = $db->query("SELECT * FROM urls")->fetchAll();
if (empty($urls)) {
$reply = "No URLs are currently being monitored.";
} else {
$reply = "<b>Uptime Monitor Status</b>\n\n";
foreach ($urls as $url) {
$emoji = ($url['status'] === 'up') ? '✅' : (($url['status'] === 'down') ? '❌' : '❓');
$reply .= $emoji . " " . $url['name'] . "\n";
$reply .= "Status: " . strtoupper($url['status']) . "\n";
$reply .= "Response: " . $url['response_time'] . "ms\n";
$reply .= "URL: " . $url['url'] . "\n\n";
}
}
sendTelegramMessage($chatId, $reply, $token);
} elseif ($text === '/myid') {
$reply = "Your Chat ID is: <code>$chatId</code>";
sendTelegramMessage($chatId, $reply, $token);
}

View File

@ -1,302 +1,11 @@
/* Custom styles for Uptime Monitor */
:root {
--bg-dark: #0f172a;
--pastel-blue: #1e293b;
--neon-white: #f8fafc;
--neon-pink: #f472b6;
}
/* Transitions and global tweaks */
body {
background: linear-gradient(-45deg, #ee7752, #e73c7e, #23a6d5, #23d5ab);
background-size: 400% 400%;
animation: gradient 15s ease infinite;
color: #212529;
font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
font-size: 14px;
margin: 0;
min-height: 100vh;
transition: background-color 0.5s ease;
}
.main-wrapper {
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
width: 100%;
padding: 20px;
box-sizing: border-box;
position: relative;
z-index: 1;
}
@keyframes gradient {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
.chat-container {
width: 100%;
max-width: 600px;
background: rgba(255, 255, 255, 0.85);
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 20px;
display: flex;
flex-direction: column;
height: 85vh;
box-shadow: 0 20px 40px rgba(0,0,0,0.2);
backdrop-filter: blur(15px);
-webkit-backdrop-filter: blur(15px);
overflow: hidden;
}
.chat-header {
padding: 1.5rem;
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
background: rgba(255, 255, 255, 0.5);
font-weight: 700;
font-size: 1.1rem;
display: flex;
justify-content: space-between;
align-items: center;
}
.chat-messages {
flex: 1;
overflow-y: auto;
padding: 1.5rem;
display: flex;
flex-direction: column;
gap: 1.25rem;
}
/* Custom Scrollbar */
::-webkit-scrollbar {
width: 6px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.3);
border-radius: 10px;
}
::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.5);
}
.message {
max-width: 85%;
padding: 0.85rem 1.1rem;
border-radius: 16px;
line-height: 1.5;
font-size: 0.95rem;
box-shadow: 0 4px 15px rgba(0,0,0,0.05);
animation: fadeIn 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(20px) scale(0.95); }
to { opacity: 1; transform: translateY(0) scale(1); }
}
.message.visitor {
align-self: flex-end;
background: linear-gradient(135deg, #212529 0%, #343a40 100%);
color: #fff;
border-bottom-right-radius: 4px;
}
.message.bot {
align-self: flex-start;
background: #ffffff;
color: #212529;
border-bottom-left-radius: 4px;
}
.chat-input-area {
padding: 1.25rem;
background: rgba(255, 255, 255, 0.5);
border-top: 1px solid rgba(0, 0, 0, 0.05);
}
.chat-input-area form {
display: flex;
gap: 0.75rem;
}
.chat-input-area input {
flex: 1;
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 12px;
padding: 0.75rem 1rem;
outline: none;
background: rgba(255, 255, 255, 0.9);
transition: all 0.3s ease;
}
.chat-input-area input:focus {
border-color: #23a6d5;
box-shadow: 0 0 0 3px rgba(35, 166, 213, 0.2);
}
.chat-input-area button {
background: #212529;
color: #fff;
border: none;
padding: 0.75rem 1.5rem;
border-radius: 12px;
cursor: pointer;
font-weight: 600;
transition: all 0.3s ease;
}
.chat-input-area button:hover {
background: #000;
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(0,0,0,0.2);
}
/* Background Animations */
.bg-animations {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 0;
overflow: hidden;
pointer-events: none;
}
.blob {
position: absolute;
width: 500px;
height: 500px;
background: rgba(255, 255, 255, 0.2);
border-radius: 50%;
filter: blur(80px);
animation: move 20s infinite alternate cubic-bezier(0.45, 0, 0.55, 1);
}
.blob-1 {
top: -10%;
left: -10%;
background: rgba(238, 119, 82, 0.4);
}
.blob-2 {
bottom: -10%;
right: -10%;
background: rgba(35, 166, 213, 0.4);
animation-delay: -7s;
width: 600px;
height: 600px;
}
.blob-3 {
top: 40%;
left: 30%;
background: rgba(231, 60, 126, 0.3);
animation-delay: -14s;
width: 450px;
height: 450px;
}
@keyframes move {
0% { transform: translate(0, 0) rotate(0deg) scale(1); }
33% { transform: translate(150px, 100px) rotate(120deg) scale(1.1); }
66% { transform: translate(-50px, 200px) rotate(240deg) scale(0.9); }
100% { transform: translate(0, 0) rotate(360deg) scale(1); }
}
.admin-link {
font-size: 14px;
color: #fff;
text-decoration: none;
background: rgba(0, 0, 0, 0.2);
padding: 0.5rem 1rem;
border-radius: 8px;
transition: all 0.3s ease;
}
.admin-link:hover {
background: rgba(0, 0, 0, 0.4);
text-decoration: none;
}
/* Admin Styles */
.admin-container {
max-width: 900px;
margin: 3rem auto;
padding: 2.5rem;
background: rgba(255, 255, 255, 0.85);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border-radius: 24px;
box-shadow: 0 20px 50px rgba(0,0,0,0.15);
border: 1px solid rgba(255, 255, 255, 0.4);
position: relative;
z-index: 1;
}
.admin-container h1 {
margin-top: 0;
color: #212529;
font-weight: 800;
}
.table {
width: 100%;
border-collapse: separate;
border-spacing: 0 8px;
margin-top: 1.5rem;
}
.table th {
background: transparent;
border: none;
padding: 1rem;
color: #6c757d;
font-weight: 600;
text-transform: uppercase;
font-size: 0.75rem;
letter-spacing: 1px;
}
.table td {
background: #fff;
padding: 1rem;
border: none;
}
.table tr td:first-child { border-radius: 12px 0 0 12px; }
.table tr td:last-child { border-radius: 0 12px 12px 0; }
.form-group {
margin-bottom: 1.25rem;
}
.form-group label {
display: block;
margin-bottom: 0.5rem;
font-weight: 600;
font-size: 0.9rem;
}
.form-control {
width: 100%;
padding: 0.75rem 1rem;
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 12px;
background: #fff;
transition: all 0.3s ease;
box-sizing: border-box;
}
.form-control:focus {
outline: none;
border-color: #23a6d5;
box-shadow: 0 0 0 3px rgba(35, 166, 213, 0.1);
}

View File

@ -1,39 +1,3 @@
document.addEventListener('DOMContentLoaded', () => {
const chatForm = document.getElementById('chat-form');
const chatInput = document.getElementById('chat-input');
const chatMessages = document.getElementById('chat-messages');
const appendMessage = (text, sender) => {
const msgDiv = document.createElement('div');
msgDiv.classList.add('message', sender);
msgDiv.textContent = text;
chatMessages.appendChild(msgDiv);
chatMessages.scrollTop = chatMessages.scrollHeight;
};
chatForm.addEventListener('submit', async (e) => {
e.preventDefault();
const message = chatInput.value.trim();
if (!message) return;
appendMessage(message, 'visitor');
chatInput.value = '';
try {
const response = await fetch('api/chat.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message })
});
const data = await response.json();
// Artificial delay for realism
setTimeout(() => {
appendMessage(data.reply, 'bot');
}, 500);
} catch (error) {
console.error('Error:', error);
appendMessage("Sorry, something went wrong. Please try again.", 'bot');
}
});
});
// Uptime Monitor Client Logic
// Main logic is currently in index.php for simplicity and speed of deployment.
console.log('Uptime Monitor initialized');

View File

@ -1,9 +1,9 @@
<?php
// Generated by setup_mariadb_project.sh — edit as needed.
// Updated to match environment variables
define('DB_HOST', '127.0.0.1');
define('DB_NAME', 'app_38384');
define('DB_USER', 'app_38384');
define('DB_PASS', '5561099f-23a3-43d8-b1a2-54739c50721b');
define('DB_NAME', 'app_38770');
define('DB_USER', 'app_38770');
define('DB_PASS', 'df7ff158-fd14-4299-9b64-64809fce0c52');
function db() {
static $pdo;
@ -14,4 +14,4 @@ function db() {
]);
}
return $pdo;
}
}

38
healthz.php Normal file
View File

@ -0,0 +1,38 @@
<?php
require_once __DIR__ . '/db/config.php';
header('Content-Type: application/json');
$response = [
'status' => 'healthy',
'timestamp' => date('c'),
'php_version' => PHP_VERSION,
'database' => 'disconnected',
'monitor_engine' => 'offline'
];
try {
if (db()) {
$response['database'] = 'connected';
}
} catch (Exception $e) {
$response['database'] = 'error: ' . $e->getMessage();
}
$lockFile = __DIR__ . '/monitor.lock';
if (file_exists($lockFile)) {
$fp = fopen($lockFile, 'r');
if (!flock($fp, LOCK_EX | LOCK_NB)) {
$response['monitor_engine'] = 'running';
}
fclose($fp);
}
if ($response['status'] === 'healthy' && $response['database'] === 'connected' && $response['monitor_engine'] === 'running') {
http_response_code(200);
} else {
// We don't necessarily want 500 if monitor is just offline
http_response_code(200);
}
echo json_encode($response);

369
index.php
View File

@ -1,52 +1,351 @@
<?php
require_once __DIR__ . '/db/config.php';
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Modern AI-ready Chat Assistant';
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Uptime Monitoring 24/7';
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Chat Assistant</title>
<?php if ($projectDescription): ?>
<meta name="description" content="<?= htmlspecialchars($projectDescription) ?>">
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>">
<?php endif; ?>
<?php if ($projectImageUrl): ?>
<meta property="og:image" content="<?= htmlspecialchars($projectImageUrl) ?>">
<?php endif; ?>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover">
<meta name="theme-color" content="#0f172a">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<title>Uptime Monitor</title>
<link rel="manifest" href="manifest.json">
<script>
if ("serviceWorker" in navigator) {
navigator.serviceWorker.register("sw.js");
}
</script>
<meta name="description" content="<?= htmlspecialchars($projectDescription) ?>">
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<script src="https://unpkg.com/lucide@latest"></script>
<style>
:root {
--bg-dark: #0f172a;
--pastel-blue: #1e293b;
--neon-white: #f8fafc;
--neon-pink: #f472b6;
--neon-white-glow: 0 0 10px rgba(248, 250, 252, 0.5);
--neon-pink-glow: 0 0 10px rgba(244, 114, 182, 0.5);
}
body {
background-color: var(--bg-dark);
color: var(--neon-white);
font-family: 'Space Grotesk', sans-serif;
margin: 0;
padding: 0;
overflow-x: hidden;
-webkit-tap-highlight-color: transparent;
padding-top: env(safe-area-inset-top);
padding-bottom: env(safe-area-inset-bottom);
}
.bg-gradient {
background: radial-gradient(circle at top right, #1e293b, #0f172a);
min-height: 100vh; /* Fallback */
min-height: 100dvh; /* Mobile browsers */
}
.card {
background: rgba(30, 41, 59, 0.7);
backdrop-filter: blur(10px);
border: 1px solid rgba(248, 250, 252, 0.1);
border-radius: 1.5rem;
transition: all 0.3s ease;
}
.card:hover {
border-color: rgba(248, 250, 252, 0.3);
transform: translateY(-2px);
}
.status-up {
color: var(--neon-white);
text-shadow: var(--neon-white-glow);
}
.status-down {
color: var(--neon-pink);
text-shadow: var(--neon-pink-glow);
}
.health-bar-container {
width: 50px;
height: 24px;
border: 2px solid rgba(248, 250, 252, 0.3);
border-radius: 4px;
position: relative;
padding: 2px;
flex-shrink: 0;
}
.health-bar-container::after {
content: '';
position: absolute;
right: -6px;
top: 6px;
width: 4px;
height: 8px;
background: rgba(248, 250, 252, 0.3);
border-radius: 0 2px 2px 0;
}
.health-fill {
height: 100%;
background: var(--neon-white);
box-shadow: var(--neon-white-glow);
transition: width 0.5s ease;
}
.health-fill.down {
background: var(--neon-pink);
box-shadow: var(--neon-pink-glow);
}
.candlestick-chart {
display: flex;
align-items: flex-end;
gap: 1px;
height: 50px;
width: 100%;
margin-top: 4px;
}
.candle {
flex: 1;
min-width: 1px;
border-radius: 1px;
transition: height 0.3s ease;
}
.candle.up {
background: rgba(248, 250, 252, 0.5);
}
.candle.down {
background: rgba(244, 114, 182, 0.8);
}
.pulse {
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: .5; }
}
/* Mobile fixes */
input[type="text"], input[type="url"] {
font-size: 16px; /* Prevents iOS/Android auto-zoom on focus */
}
</style>
</head>
<body>
<div class="bg-animations">
<div class="blob blob-1"></div>
<div class="blob blob-2"></div>
<div class="blob blob-3"></div>
</div>
<div class="main-wrapper">
<div class="chat-container">
<div class="chat-header">
<span>Chat Assistant</span>
<a href="admin.php" class="admin-link">Admin</a>
</div>
<div class="chat-messages" id="chat-messages">
<div class="message bot">
Hello! I'm your assistant. How can I help you today?
<body class="bg-gradient">
<!-- Force Mobile App Layout (Max Width MD, always single column) -->
<div class="w-full max-w-md mx-auto px-4 py-6">
<header class="flex flex-col gap-5 mb-6">
<div class="w-full flex justify-between items-start">
<div>
<h1 class="text-3xl font-bold tracking-tighter mb-1">UPTIME<span class="status-down">MONITOR</span></h1>
<div id="monitor-status" class="flex items-center gap-2 text-xs opacity-70">
<div class="w-2 h-2 rounded-full bg-gray-500"></div>
<span>Checking system...</span>
</div>
</div>
</div>
<div class="chat-input-area">
<form id="chat-form">
<input type="text" id="chat-input" placeholder="Type your message..." autocomplete="off">
<button type="submit">Send</button>
</form>
<div class="flex gap-3 w-full">
<button onclick="openModal('settings-modal')" class="p-3 card hover:bg-slate-800 flex justify-center items-center active:scale-95 transition-transform">
<i data-lucide="settings" class="w-5 h-5"></i>
</button>
<button onclick="openModal('add-modal')" class="p-3 card hover:bg-slate-800 flex-1 flex items-center justify-center gap-2 active:scale-95 transition-transform">
<i data-lucide="plus" class="w-5 h-5"></i>
<span class="font-medium text-sm">Add URL</span>
</button>
</div>
</header>
<!-- Changed grid to flex column so it stays stacked like a mobile app -->
<div id="urls-container" class="flex flex-col gap-4 pb-24">
<!-- URL Cards will be injected here -->
<div class="card p-6 flex flex-col items-center justify-center opacity-50 border-dashed min-h-[150px]">
<p class="text-sm">Loading monitor data...</p>
</div>
</div>
</div>
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script>
<!-- Add URL Modal -->
<div id="add-modal" class="fixed inset-0 bg-black/60 backdrop-blur-sm hidden z-50 flex items-center justify-center p-4">
<div class="card w-full max-w-md p-6 bg-slate-900 max-h-[90vh] overflow-y-auto">
<h2 class="text-xl font-bold mb-5">Add New URL</h2>
<form id="add-url-form">
<div class="mb-4">
<label class="block text-sm font-medium mb-1 opacity-70">Name</label>
<input type="text" name="name" required class="w-full bg-slate-800 border border-slate-700 rounded-xl p-3 focus:outline-none focus:border-pink-400 transition-colors">
</div>
<div class="mb-6">
<label class="block text-sm font-medium mb-1 opacity-70">Endpoint URL</label>
<input type="url" name="url" required placeholder="https://example.com" class="w-full bg-slate-800 border border-slate-700 rounded-xl p-3 focus:outline-none focus:border-pink-400 transition-colors">
</div>
<div class="flex gap-3">
<button type="button" onclick="closeModal('add-modal')" class="flex-1 p-3 rounded-xl text-sm font-medium opacity-70 hover:opacity-100 transition-opacity bg-slate-800 active:scale-95 transition-transform">Cancel</button>
<button type="submit" class="flex-1 p-3 bg-pink-500 rounded-xl text-sm font-bold text-white shadow-lg shadow-pink-500/20 hover:bg-pink-600 transition-colors active:scale-95 transition-transform">Monitor</button>
</div>
</form>
</div>
</div>
<!-- Settings Modal -->
<div id="settings-modal" class="fixed inset-0 bg-black/60 backdrop-blur-sm hidden z-50 flex items-center justify-center p-4">
<div class="card w-full max-w-md p-6 bg-slate-900 max-h-[90vh] overflow-y-auto">
<h2 class="text-xl font-bold mb-5">Telegram Settings</h2>
<form id="settings-form">
<div class="mb-4">
<label class="block text-sm font-medium mb-1 opacity-70">Bot Token</label>
<input type="text" name="telegram_bot_token" id="set_bot_token" class="w-full bg-slate-800 border border-slate-700 rounded-xl p-3 focus:outline-none focus:border-pink-400 transition-colors">
</div>
<div class="mb-6">
<label class="block text-sm font-medium mb-1 opacity-70">Chat ID</label>
<input type="text" name="telegram_chat_id" id="set_chat_id" class="w-full bg-slate-800 border border-slate-700 rounded-xl p-3 focus:outline-none focus:border-pink-400 transition-colors">
</div>
<div class="flex gap-3">
<button type="button" onclick="closeModal('settings-modal')" class="flex-1 p-3 rounded-xl text-sm font-medium opacity-70 hover:opacity-100 transition-opacity bg-slate-800 active:scale-95 transition-transform">Cancel</button>
<button type="submit" class="flex-1 p-3 bg-white text-slate-900 rounded-xl text-sm font-bold shadow-lg shadow-white/20 hover:bg-gray-200 transition-colors active:scale-95 transition-transform">Save</button>
</div>
</form>
</div>
</div>
<script>
lucide.createIcons();
function openModal(id) {
document.getElementById(id).classList.remove('hidden');
document.body.style.overflow = 'hidden'; // prevent background scrolling on mobile
}
function closeModal(id) {
document.getElementById(id).classList.add('hidden');
document.body.style.overflow = '';
}
async function fetchData() {
try {
const response = await fetch('api/status.php');
const result = await response.json();
if (result.success) {
updateUI(result.data, result.monitor_running);
}
} catch (error) {
console.error('Error fetching data:', error);
}
}
function updateUI(urls, monitorRunning) {
const container = document.getElementById('urls-container');
const statusEl = document.getElementById('monitor-status');
if (monitorRunning) {
statusEl.innerHTML = '<div class="w-2 h-2 rounded-full bg-green-400 pulse shrink-0"></div><span class="truncate">Monitoring Running (24/7)</span>';
} else {
statusEl.innerHTML = '<div class="w-2 h-2 rounded-full bg-red-400 shrink-0"></div><span class="truncate">Monitor Offline - Check monitor.php</span>';
}
if (urls.length === 0) {
container.innerHTML = `
<div class="card p-6 col-span-full flex flex-col items-center justify-center border-dashed text-center min-h-[200px]">
<i data-lucide="activity" class="w-10 h-10 mb-4 opacity-20"></i>
<p class="opacity-50 text-sm">No URLs monitored yet.</p>
<button onclick="openModal('add-modal')" class="mt-4 text-pink-400 font-medium text-sm p-2 active:scale-95 transition-transform">Add your first URL</button>
</div>
`;
lucide.createIcons();
return;
}
container.innerHTML = urls.map(url => {
const isUp = url.status === 'up';
const history = url.history || [];
// Health bar width based on uptime %
const healthWidth = url.uptime + '%';
return `
<div class="card p-4 flex flex-col gap-3 w-full">
<div class="flex justify-between items-start">
<div class="flex-1 overflow-hidden pr-2">
<h3 class="text-lg font-bold truncate">${url.name}</h3>
<p class="text-[11px] opacity-50 truncate">${url.url}</p>
</div>
<div class="flex flex-col items-end shrink-0">
<div class="health-bar-container">
<div class="health-fill ${isUp ? '' : 'down'}" style="width: ${healthWidth}"></div>
</div>
<span class="text-[9px] font-medium opacity-50 mt-1">${url.uptime}% UPTIME</span>
</div>
</div>
<div class="flex justify-between items-center my-1">
<div class="flex flex-col">
<span class="text-2xl font-bold ${isUp ? 'status-up' : 'status-down'}">
${isUp ? 'RUNNING' : 'DOWN'}
</span>
<span class="text-[9px] opacity-50 font-medium">LAST STATUS</span>
</div>
<div class="flex flex-col items-end">
<span class="text-xl font-bold">${url.response_time}ms</span>
<span class="text-[9px] opacity-50 font-medium">RESP TIME</span>
</div>
</div>
<div class="candlestick-chart">
${history.map(log => {
// Max height for 2000ms
const height = Math.min((log.response_time / 2000) * 100, 100);
const candleClass = log.status === 'up' ? 'up' : 'down';
return `<div class="candle ${candleClass}" style="height: ${Math.max(height, 5)}%"></div>`;
}).join('')}
</div>
<div class="flex justify-between items-center mt-1">
<span class="text-[9px] opacity-40">${url.last_check || 'Never checked'}</span>
<button onclick="deleteUrl(${url.id})" class="text-xs p-2 -mr-2 opacity-40 hover:opacity-100 hover:text-pink-400 active:scale-95 transition-transform">
<i data-lucide="trash-2" class="w-4 h-4"></i>
</button>
</div>
</div>
`;
}).join('');
lucide.createIcons();
}
async function deleteUrl(id) {
if (!confirm('Stop monitoring this URL?')) return;
const formData = new FormData();
formData.append('action', 'delete_url');
formData.append('id', id);
await fetch('api/admin_actions.php', { method: 'POST', body: formData });
fetchData();
}
document.getElementById('add-url-form').onsubmit = async (e) => {
e.preventDefault();
const formData = new FormData(e.target);
formData.append('action', 'add_url');
const response = await fetch('api/admin_actions.php', { method: 'POST', body: formData });
const result = await response.json();
if (result.success) {
closeModal('add-modal');
e.target.reset();
fetchData();
} else {
alert(result.error);
}
};
document.getElementById('settings-form').onsubmit = async (e) => {
e.preventDefault();
const formData = new FormData(e.target);
formData.append('action', 'update_settings');
const response = await fetch('api/admin_actions.php', { method: 'POST', body: formData });
const result = await response.json();
if (result.success) {
closeModal('settings-modal');
alert('Settings saved!');
}
};
setInterval(fetchData, 1000);
fetchData();
</script>
</body>
</html>

17
manifest.json Normal file
View File

@ -0,0 +1,17 @@
{
"name": "Uptime Monitoring 24/7",
"short_name": "Uptime",
"description": "Realtime Uptime Monitoring for your URLs",
"start_url": "/index.php",
"display": "standalone",
"background_color": "#0f172a",
"theme_color": "#0f172a",
"icons": [
{
"src": "https://cdn-icons-png.flaticon.com/512/1000/1000946.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any maskable"
}
]
}

0
monitor.lock Normal file
View File

BIN
monitor.log Normal file

Binary file not shown.

129
monitor.php Normal file
View File

@ -0,0 +1,129 @@
<?php
require_once __DIR__ . '/db/config.php';
// Prevent multiple instances
$lockFile = __DIR__ . '/monitor.lock';
$fp = fopen($lockFile, 'c');
if (!flock($fp, LOCK_EX | LOCK_NB)) {
// Silently exit if already running (good for cron)
exit(0);
}
echo "Monitoring started with Multi-Threading...\n";
function sendTelegramNotification($message) {
try {
$db = db();
$stmt = $db->query("SELECT name, value FROM settings WHERE name IN ('telegram_bot_token', 'telegram_chat_id')");
$settings = [];
while ($row = $stmt->fetch()) {
$settings[$row['name']] = $row['value'];
}
if (empty($settings['telegram_bot_token']) || empty($settings['telegram_chat_id'])) {
return;
}
$url = "https://api.telegram.org/bot" . $settings['telegram_bot_token'] . "/sendMessage";
$data = [
'chat_id' => $settings['telegram_chat_id'],
'text' => $message,
'parse_mode' => 'HTML'
];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
curl_exec($ch);
curl_close($ch);
} catch (Exception $e) {
// Suppress errors to not break the monitor loop
}
}
// Loop indefinitely
while (true) {
try {
$db = db();
$urls = $db->query("SELECT * FROM urls WHERE is_active = 1")->fetchAll();
if (count($urls) > 0) {
$mh = curl_multi_init();
$curl_arr = [];
$url_info = [];
// Add handles
foreach ($urls as $urlData) {
$ch = curl_init($urlData['url']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, false); // We don't need body, just headers
curl_setopt($ch, CURLOPT_NOBODY, true); // HEAD request is faster
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 10); // 10s timeout
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_multi_add_handle($mh, $ch);
$curl_arr[] = $ch;
// Store info mapped by intval of handle
$url_info[(int)$ch] = $urlData;
}
// Execute handles
$active = null;
do {
$mrc = curl_multi_exec($mh, $active);
} while ($mrc == CURLM_CALL_MULTI_PERFORM);
while ($active && $mrc == CURLM_OK) {
if (curl_multi_select($mh) != -1) {
do {
$mrc = curl_multi_exec($mh, $active);
} while ($mrc == CURLM_CALL_MULTI_PERFORM);
}
}
// Read results
foreach ($curl_arr as $ch) {
$ch_id = (int)$ch;
$urlData = $url_info[$ch_id];
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$total_time = curl_getinfo($ch, CURLINFO_TOTAL_TIME);
$total_time_ms = round($total_time * 1000);
// Determine new status: UP if 200-399
$new_status = ($http_code >= 200 && $http_code < 400) ? 'UP' : 'DOWN';
$old_status = $urlData['status'];
// Update database
$stmt = $db->prepare("UPDATE urls SET status = ?, last_checked = NOW() WHERE id = ?");
$stmt->execute([$new_status, $urlData['id']]);
$stmt = $db->prepare("INSERT INTO logs (url_id, status, response_time, checked_at) VALUES (?, ?, ?, NOW())");
$stmt->execute([$urlData['id'], $new_status, $total_time_ms]);
// Check for status change and notify
if ($old_status && $old_status !== $new_status) {
$icon = $new_status === 'UP' ? '✅' : '🚨';
$msg = "{$icon} <b>Monitor Alert</b>\n\n";
$msg .= "<b>URL:</b> {$urlData['url']}\n";
$msg .= "<b>Status:</b> {$new_status} (HTTP {$http_code})\n";
$msg .= "<b>Response:</b> {$total_time_ms}ms";
sendTelegramNotification($msg);
}
curl_multi_remove_handle($mh, $ch);
curl_close($ch);
}
curl_multi_close($mh);
}
} catch (Exception $e) {
echo "Error: " . $e->getMessage() . "\n";
}
// Wait 30 seconds before next check
sleep(30);
}

23
sw.js Normal file
View File

@ -0,0 +1,23 @@
const CACHE_NAME = 'uptime-v1';
const ASSETS = [
'/',
'/index.php',
'/assets/css/custom.css',
'/manifest.json'
];
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => {
return cache.addAll(ASSETS);
})
);
});
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
return response || fetch(event.request);
})
);
});