38753-vm/index.php
Flatlogic Bot 2c10ebe286 Uptime
2026-02-25 00:08:13 +00:00

518 lines
21 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
declare(strict_types=1);
@ini_set('display_errors', '1');
@error_reporting(E_ALL);
@date_default_timezone_set('UTC');
$v = time(); // Cache busting version
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
<title>๐Œ๐จ๐ง๐ข๐ญ๐จ๐ซ ๐”๐ฉ๐ญ๐ข๐ฆ๐ž ๐›๐ฒ ๐˜๐ฎ๐ฆ๐ž๐ž</title>
<link rel="manifest" href="manifest.json?v=<?php echo $v; ?>">
<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=JetBrains+Mono:wght@400;700&family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
:root {
--crypto-green: #00ff88;
--crypto-red: #ff3366;
--crypto-blue: #00d4ff;
--dark-bg: #0b0e11;
--dark-card: #181a20;
--dark-sidebar: #1e2329;
--text-main: #eaecef;
--text-dim: #848e9c;
--border: #2b3139;
}
* { box-sizing: border-box; }
body {
margin: 0; padding: 0;
font-family: 'Inter', sans-serif;
background: var(--dark-bg);
color: var(--text-main);
display: flex;
min-height: 100vh;
overflow-x: hidden;
}
/* Sidebar */
aside {
width: 260px;
background: var(--dark-sidebar);
border-right: 1px solid var(--border);
display: flex;
flex-direction: column;
flex-shrink: 0;
position: sticky;
top: 0;
height: 100vh;
z-index: 100;
}
.sidebar-header { padding: 1.5rem; border-bottom: 1px solid var(--border); }
.sidebar-header h1 {
font-size: 1.1rem; margin: 0;
background: linear-gradient(90deg, #fff, var(--crypto-blue));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
font-weight: 800;
}
.nav-list { list-style: none; padding: 1rem 0; margin: 0; flex: 1; }
.nav-item {
padding: 0.8rem 1.5rem;
cursor: pointer;
color: var(--text-dim);
font-size: 0.9rem;
transition: all 0.2s;
display: flex;
align-items: center;
gap: 12px;
}
.nav-item:hover, .nav-item.active {
color: #fff;
background: rgba(255,255,255,0.05);
border-left: 3px solid var(--crypto-blue);
}
/* Main Content */
main { flex: 1; display: flex; flex-direction: column; overflow-y: auto; }
.top-bar {
height: 60px; border-bottom: 1px solid var(--border);
display: flex; align-items: center; justify-content: space-between;
padding: 0 2rem; background: var(--dark-bg);
position: sticky; top: 0; z-index: 99;
}
.content-area { padding: 2rem; max-width: 1400px; width: 100%; margin: 0 auto; }
/* Cards */
.dashboard-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 20px; margin-bottom: 2rem; }
.card { background: var(--dark-card); border-radius: 12px; padding: 1.5rem; border: 1px solid var(--border); position: relative; }
.stat-val { font-family: 'JetBrains Mono', monospace; font-size: 1.8rem; font-weight: 700; display: block; margin-top: 5px; }
.stat-label { color: var(--text-dim); font-size: 0.8rem; text-transform: uppercase; letter-spacing: 1px; }
/* Mobile Monitor Specific (CRYPTO CANDLE STYLE) */
#mobile-monitor {
position: fixed; top: 0; left: 0; right: 0; bottom: 0;
background: #000; z-index: 2000; display: none;
flex-direction: column; padding: 4px;
}
#mobile-monitor.active { display: flex; }
.mm-header {
display: flex; justify-content: space-between; align-items: center;
background: #111; padding: 6px 10px; border-radius: 6px; margin-bottom: 4px;
}
.mm-grid { display: grid; grid-template-columns: 1fr; gap: 4px; flex: 1; overflow-y: auto; }
.mm-row {
background: #080808; padding: 8px; border-radius: 6px;
border: 1px solid #1a1a1a; display: flex; flex-direction: column; gap: 4px;
}
.mm-info { display: flex; justify-content: space-between; align-items: center; font-family: 'JetBrains Mono'; }
.mm-url-text { font-size: 0.55rem; color: #aaa; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 70%; }
.mm-lat-text { font-size: 0.7rem; font-weight: bold; }
.mm-spark-container {
height: 80px; width: 100%; position: relative;
background: #050505; border-radius: 4px; overflow: hidden;
}
.mm-canvas { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
.data-table { width: 100%; border-collapse: collapse; }
.data-table th { text-align: left; color: var(--text-dim); padding: 12px; border-bottom: 1px solid var(--border); font-size: 0.8rem; }
.data-table td { padding: 12px; border-bottom: 1px solid var(--border); font-size: 0.9rem; }
.status-badge { padding: 4px 8px; border-radius: 4px; font-size: 0.7rem; font-weight: bold; text-transform: uppercase; }
.bg-ok { background: rgba(0, 255, 136, 0.1); color: var(--crypto-green); }
.bg-err { background: rgba(255, 51, 102, 0.1); color: var(--crypto-red); }
input, select { background: #1e2329; border: 1px solid var(--border); color: #fff; padding: 10px 12px; border-radius: 4px; outline: none; width: 100%; }
button { padding: 10px 20px; border-radius: 4px; border: none; font-weight: 700; cursor: pointer; transition: all 0.2s; }
.btn-glow { background: var(--crypto-blue); color: #000; box-shadow: 0 0 15px rgba(0, 212, 255, 0.2); }
.section { display: none; }
.section.active { display: block; }
/* Floating Button for Mobile */
.fab {
position: fixed; bottom: 20px; right: 20px;
width: 60px; height: 60px; border-radius: 50%;
background: var(--crypto-blue); color: #000;
display: none; align-items: center; justify-content: center;
box-shadow: 0 4px 20px rgba(0, 212, 255, 0.4);
z-index: 1000; cursor: pointer; font-size: 1.5rem;
}
@media (max-width: 768px) {
aside { display: none; }
.top-bar { padding: 0 1rem; }
.content-area { padding: 1rem; }
.fab { display: flex; }
.top-candle-btn { display: flex !important; }
}
</style>
</head>
<body>
<aside>
<div class="sidebar-header">
<h1>๐Œ๐จ๐ง๐ข๐ญ๐จ๐ซ ๐”๐ฉ๐ญ๐ข๐ฆ๐ž</h1>
<div style="font-size: 0.6rem; color: var(--text-dim); margin-top: 4px;">by Yumee โ€ข Healthcare AI</div>
</div>
<ul class="nav-list">
<li class="nav-item active" onclick="showSection('dashboard')"><span>๐Ÿ“Š</span> Dashboard</li>
<li class="nav-item" onclick="showSection('monitors')"><span>๐Ÿ”</span> Monitors</li>
<li class="nav-item" onclick="showSection('teams')"><span>๐Ÿ‘ฅ</span> Team</li>
<li class="nav-item" onclick="showSection('api')"><span>๐Ÿ”‘</span> API</li>
</ul>
<div style="padding: 1rem; border-top: 1px solid var(--border);">
<button class="btn-glow" style="width:100%; margin-bottom:10px;" onclick="enterMobileMonitor()">๐Ÿ“ฑ CANDLE MODE</button>
<button id="global-toggle" style="width:100%; font-size: 0.8rem;" onclick="toggleMonitoring()">STATION: OFFLINE</button>
</div>
</aside>
<main>
<div class="top-bar">
<div style="display:flex; align-items:center; gap:10px;">
<button class="btn-glow top-candle-btn" style="display:none; padding: 4px 10px; font-size: 0.7rem;" onclick="enterMobileMonitor()">CANDLE</button>
<div id="ticker" style="font-family: 'JetBrains Mono', monospace; font-size: 0.65rem; color: var(--crypto-green);">
[NET: 100%]
</div>
</div>
<div style="display:flex; align-items:center; gap: 10px;">
<span id="live-indicator" style="width:8px; height:8px; border-radius:50%; background: #555;"></span>
<span id="clock" style="font-family:'JetBrains Mono'; font-size:0.7rem;">00:00:00</span>
</div>
</div>
<div class="content-area">
<div id="dashboard" class="section active">
<div class="dashboard-grid">
<div class="card" onclick="enterMobileMonitor()" style="cursor:pointer; border: 1px solid var(--crypto-blue);">
<span class="stat-label">Click for Candle View</span>
<span class="stat-val" id="stat-total">0 Assets</span>
</div>
<div class="card">
<span class="stat-label">Avg Latency</span>
<span class="stat-val" id="stat-latency" style="color: var(--crypto-blue)">0ms</span>
</div>
<div class="card">
<span class="stat-label">Health</span>
<span class="stat-val" id="stat-health" style="color: var(--crypto-green)">100%</span>
</div>
</div>
<div class="card">
<h3>Latency Graph</h3>
<canvas id="globalChart" style="height: 180px;"></canvas>
</div>
</div>
<div id="monitors" class="section">
<div class="card" style="margin-bottom: 20px;">
<h3>Add Asset</h3>
<div style="display:flex; gap: 10px;">
<input type="url" id="new-url" placeholder="https://example.com">
<button class="btn-glow" onclick="addAsset()">+ ADD</button>
</div>
</div>
<div class="card">
<table class="data-table">
<thead><tr><th>URL</th><th>Status</th><th>Latency</th><th>Actions</th></tr></thead>
<tbody id="inventory-list"></tbody>
</table>
</div>
</div>
<div id="teams" class="section"><div class="card"><h3>Team Management</h3><p>Manage monitoring teams.</p></div></div>
<div id="api" class="section"><div class="card"><h3>API Keys</h3><p>External integrations.</p></div></div>
</div>
</main>
<!-- Floating Action Button for Mobile -->
<div class="fab" onclick="enterMobileMonitor()">๐Ÿ•ฏ๏ธ</div>
<div id="mobile-monitor">
<div class="mm-header">
<div style="display:flex; align-items:center;">
<div id="mm-status-dot" style="width:8px; height:8px; border-radius:50%; background:#555; margin-right:8px;"></div>
<span style="font-weight:bold; font-size:0.7rem; color:#fff; font-family:'JetBrains Mono'">REALTIME CANDLES</span>
</div>
<div style="display:flex; gap:5px;">
<button onclick="toggleFullscreen()" style="background:#1a1a1a; color:#888; border:1px solid #333; padding:4px 8px; font-size:0.5rem; border-radius:4px;">FS</button>
<button onclick="exitMobileMonitor()" style="background:#300; color:#f55; border:1px solid #500; padding:4px 8px; font-size:0.5rem; border-radius:4px;">EXIT</button>
</div>
</div>
<div id="mm-crypto-grid" class="mm-grid"></div>
<div style="background:#111; padding:6px 10px; border-radius:6px; margin-top:4px; display:flex; justify-content:space-between; align-items:center;">
<div id="mm-clock" style="font-family:'JetBrains Mono'; font-size:0.6rem; color:var(--crypto-blue)">00:00:00</div>
<div id="mm-health-pct" style="font-family:'JetBrains Mono'; font-size:0.6rem; color:var(--crypto-green)">100% HEALTH</div>
</div>
</div>
<script>
let isMonitoring = false;
let mainChart = null;
const sparklines = {};
class CryptoSparkline {
constructor(canvasId) {
this.canvas = document.getElementById(canvasId);
this.ctx = this.canvas.getContext('2d');
this.data = [];
this.lastVal = Math.random() * 50 + 20;
// Pre-fill dummy data
for(let i=0; i<45; i++) {
this.addCandle(this.lastVal + (Math.random()*10-5), 'ok');
}
this.resize();
window.addEventListener('resize', () => this.resize());
}
addCandle(val, status) {
const open = this.lastVal;
const close = val;
const high = Math.max(open, close) + Math.random() * 4;
const low = Math.max(0, Math.min(open, close) - Math.random() * 4);
this.data.push({ open, close, high, low, ok: status === 'ok' });
this.lastVal = val;
if (this.data.length > 55) this.data.shift();
}
resize() {
const rect = this.canvas.parentElement.getBoundingClientRect();
if (!rect.width) return;
this.canvas.width = rect.width * window.devicePixelRatio;
this.canvas.height = rect.height * window.devicePixelRatio;
this.ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
this.draw();
}
update(val, status) {
this.addCandle(val, status);
this.draw();
}
draw() {
const w = this.canvas.width / window.devicePixelRatio;
const h = this.canvas.height / window.devicePixelRatio;
this.ctx.clearRect(0, 0, w, h);
this.ctx.strokeStyle = '#111';
this.ctx.lineWidth = 0.5;
for(let i=0; i<w; i+=25) {
this.ctx.beginPath(); this.ctx.moveTo(i, 0); this.ctx.lineTo(i, h); this.ctx.stroke();
}
for(let i=0; i<h; i+=15) {
this.ctx.beginPath(); this.ctx.moveTo(0, i); this.ctx.lineTo(w, i); this.ctx.stroke();
}
if (this.data.length < 1) return;
const allVals = this.data.flatMap(d => [d.high, d.low]);
const minV = Math.min(...allVals) * 0.8;
const maxV = Math.max(...allVals) * 1.2 || 100;
const range = maxV - minV;
const step = w / this.data.length;
const candleW = Math.max(2, step * 0.7);
this.data.forEach((d, i) => {
const x = i * step + (step/2);
const color = d.ok ? '#00ff88' : '#ff3366';
const yO = h - ((d.open - minV) / range * h);
const yC = h - ((d.close - minV) / range * h);
const yH = h - ((d.high - minV) / range * h);
const yL = h - ((d.low - minV) / range * h);
this.ctx.beginPath();
this.ctx.strokeStyle = color;
this.ctx.lineWidth = 1;
this.ctx.moveTo(x, yH);
this.ctx.lineTo(x, yL);
this.ctx.stroke();
const bY = Math.min(yO, yC);
const bH = Math.max(1, Math.abs(yO - yC));
this.ctx.fillStyle = color;
this.ctx.fillRect(x - candleW/2, bY, candleW, bH);
});
}
}
function showSection(id) {
document.querySelectorAll('.section').forEach(s => s.classList.remove('active'));
const sec = document.getElementById(id);
if (sec) sec.classList.add('active');
document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active'));
refreshData();
}
setInterval(() => {
const time = new Date().toISOString().split('T')[1].split('.')[0];
document.getElementById('clock').innerText = time + ' UTC';
document.getElementById('mm-clock').innerText = time + ' UTC';
}, 1000);
function toggleFullscreen() {
if (!document.fullscreenElement) document.documentElement.requestFullscreen();
else document.exitFullscreen();
}
function enterMobileMonitor() {
document.getElementById('mobile-monitor').style.display = 'flex';
// Force refresh sparklines
Object.values(sparklines).forEach(s => s.resize());
refreshData();
}
function exitMobileMonitor() {
document.getElementById('mobile-monitor').style.display = 'none';
if (document.fullscreenElement) document.exitFullscreen();
}
async function init() {
const resp = await fetch('api/uptime.php?action=settings&v=<?php echo $v; ?>');
const data = await resp.json();
isMonitoring = data.monitoring_enabled;
updateStatusUI();
initChart();
refreshData();
setInterval(refreshData, 10000);
setInterval(runPulse, 15000);
}
function initChart() {
const ctx = document.getElementById('globalChart').getContext('2d');
mainChart = new Chart(ctx, {
type: 'line',
data: {
labels: Array(20).fill(''),
datasets: [{
data: Array(20).fill(0),
borderColor: '#00d4ff',
backgroundColor: 'rgba(0, 212, 255, 0.05)',
fill: true, tension: 0.4, pointRadius: 0
}]
},
options: {
responsive: true, maintainAspectRatio: false,
scales: { x: { display: false }, y: { grid: { color: '#2b3139' }, ticks: { color: '#848e9c', font: { size: 9 } } } },
plugins: { legend: { display: false } }
}
});
}
function updateStatusUI() {
const btn = document.getElementById('global-toggle');
const ind = document.getElementById('live-indicator');
const mmDot = document.getElementById('mm-status-dot');
const color = isMonitoring ? 'var(--crypto-green)' : '#555';
btn.innerText = isMonitoring ? 'STATION: ONLINE' : 'STATION: OFFLINE';
btn.style.background = isMonitoring ? 'var(--crypto-green)' : '#2b3139';
btn.style.color = isMonitoring ? '#000' : '#fff';
ind.style.background = color;
ind.style.boxShadow = isMonitoring ? '0 0 10px ' + color : 'none';
mmDot.style.background = color;
mmDot.style.boxShadow = isMonitoring ? '0 0 8px ' + color : 'none';
}
async function toggleMonitoring() {
isMonitoring = !isMonitoring;
await fetch('api/uptime.php?action=toggle', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ enabled: isMonitoring })
});
updateStatusUI();
}
async function refreshData() {
const statResp = await fetch('api/uptime.php?action=stats&v=<?php echo $v; ?>');
const stats = await statResp.json();
document.getElementById('stat-total').innerText = stats.total + ' Assets';
document.getElementById('stat-latency').innerText = Math.round(stats.avg_latency) + 'ms';
const health = stats.total > 0 ? ((stats.up / stats.total) * 100).toFixed(1) : 100;
document.getElementById('stat-health').innerText = health + '%';
document.getElementById('stat-health').style.color = health < 99 ? 'var(--crypto-red)' : 'var(--crypto-green)';
document.getElementById('mm-health-pct').innerText = health + '% HEALTH';
if (mainChart) {
mainChart.data.datasets[0].data.push(stats.avg_latency);
mainChart.data.datasets[0].data.shift();
mainChart.update('none');
}
const listResp = await fetch('api/uptime.php?action=list&v=<?php echo $v; ?>');
const assets = await listResp.json();
const inventory = document.getElementById('inventory-list');
const mmCryptoGrid = document.getElementById('mm-crypto-grid');
inventory.innerHTML = '';
assets.forEach(u => {
const isOk = u.last_status === 'ok';
inventory.innerHTML += `<tr><td>${u.url}</td><td><span class="status-badge ${isOk?'bg-ok':'bg-err'}">${u.last_status}</span></td><td>${u.last_latency}ms</td><td><button onclick="deleteAsset(${u.id})" style="color:var(--crypto-red); background:none; padding:0;">DEL</button></td></tr>`;
if (!document.getElementById(`mm-row-${u.id}`)) {
const mrow = document.createElement('div');
mrow.id = `mm-row-${u.id}`;
mrow.className = 'mm-row';
mrow.innerHTML = `
<div class="mm-info">
<span class="mm-url-text">${u.url.replace('https://','').replace('http://','')}</span>
<span id="mm-lat-${u.id}" class="mm-lat-text" style="color:${isOk?'var(--crypto-green)':'var(--crypto-red)'}">${u.last_latency}ms</span>
</div>
<div class="mm-spark-container">
<canvas id="mm-canvas-${u.id}" class="mm-canvas"></canvas>
</div>
`;
mmCryptoGrid.appendChild(mrow);
sparklines[u.id] = new CryptoSparkline(`mm-canvas-${u.id}`);
} else {
const latEl = document.getElementById(`mm-lat-${u.id}`);
if (latEl) {
latEl.innerText = u.last_latency + 'ms';
latEl.style.color = isOk ? 'var(--crypto-green)' : 'var(--crypto-red)';
}
if (sparklines[u.id]) sparklines[u.id].update(u.last_latency, u.last_status);
}
});
}
async function runPulse() {
if (!isMonitoring) return;
await fetch('api/uptime.php?action=check');
refreshData();
}
async function addAsset() {
const url = document.getElementById('new-url').value;
if (!url) return;
await fetch('api/uptime.php?action=add', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ url })
});
document.getElementById('new-url').value = '';
refreshData();
}
async function deleteAsset(id) {
if (!confirm('Delete?')) return;
await fetch('api/uptime.php?action=delete', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id })
});
refreshData();
}
init();
</script>
</body>
</html>