This commit is contained in:
Flatlogic Bot 2026-02-25 00:08:13 +00:00
parent 65a6c22962
commit 2c10ebe286
2 changed files with 127 additions and 197 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

324
index.php
View File

@ -3,6 +3,7 @@ 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">
@ -10,7 +11,7 @@ declare(strict_types=1);
<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">
<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">
@ -25,7 +26,6 @@ declare(strict_types=1);
--dark-sidebar: #1e2329;
--text-main: #eaecef;
--text-dim: #848e9c;
--neon-glow: 0 0 10px rgba(0, 255, 136, 0.5);
--border: #2b3139;
}
@ -54,14 +54,9 @@ declare(strict_types=1);
z-index: 100;
}
.sidebar-header {
padding: 1.5rem;
border-bottom: 1px solid var(--border);
}
.sidebar-header { padding: 1.5rem; border-bottom: 1px solid var(--border); }
.sidebar-header h1 {
font-size: 1.1rem;
margin: 0;
font-size: 1.1rem; margin: 0;
background: linear-gradient(90deg, #fff, var(--crypto-blue));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
@ -86,149 +81,75 @@ declare(strict_types=1);
}
/* Main Content */
main {
flex: 1;
display: flex;
flex-direction: column;
overflow-y: auto;
}
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;
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 & Components */
.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;
}
/* 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; }
/* Battery Bars */
.battery-container { display: flex; gap: 2px; height: 20px; margin-top: 10px; }
.battery-bar { flex: 1; background: #2b3139; border-radius: 2px; position: relative; }
.battery-bar.ok { background: var(--crypto-green); box-shadow: 0 0 5px rgba(0, 255, 136, 0.3); }
.battery-bar.err { background: var(--crypto-red); }
/* Mobile Monitor Specific (CRYPTO STYLE) */
/* 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: 5px;
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: 8px 12px;
border-radius: 8px;
margin-bottom: 5px;
}
.mm-grid {
display: grid;
grid-template-columns: 1fr;
gap: 5px;
flex: 1;
overflow-y: auto;
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: 10px;
border-radius: 8px;
border: 1px solid #1a1a1a;
display: flex;
flex-direction: column;
gap: 5px;
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.6rem; color: #888; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 60%; }
.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: 60px;
width: 100%;
position: relative;
background: rgba(255,255,255,0.02);
border-radius: 4px;
overflow: hidden;
}
.mm-canvas {
position: absolute;
top: 0; left: 0; width: 100%; height: 100%;
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%; }
/* List Tables */
.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); }
/* Inputs & Buttons */
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;
}
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); }
/* Sections */
.section { display: none; }
.section.active { display: block; }
/* Responsive */
/* 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; }
.dashboard-grid { grid-template-columns: 1fr !important; }
.fab { display: flex; }
.top-candle-btn { display: flex !important; }
}
</style>
</head>
@ -246,28 +167,31 @@ declare(strict_types=1);
<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()">๐Ÿ“ฑ CRYPTO MODE</button>
<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 id="ticker" style="font-family: 'JetBrains Mono', monospace; font-size: 0.8rem; color: var(--crypto-green);">
[NETWORK HEALTH: 100%] [NODES ACTIVE: 12]
<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: 15px;">
<span id="live-indicator" style="width:10px; height:10px; border-radius:50%; background: #555;"></span>
<span id="clock" style="font-family:'JetBrains Mono'; font-size:0.8rem;">00:00:00</span>
<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">
<span class="stat-label">Total Assets</span>
<span class="stat-val" id="stat-total">0</span>
<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>
@ -279,14 +203,9 @@ declare(strict_types=1);
</div>
</div>
<div class="card" style="margin-bottom: 2rem;">
<h3>Asset Status (Battery View)</h3>
<div id="battery-rows"></div>
</div>
<div class="card">
<h3>Latency Graph</h3>
<canvas id="globalChart" style="height: 200px;"></canvas>
<canvas id="globalChart" style="height: 180px;"></canvas>
</div>
</div>
@ -306,17 +225,19 @@ declare(strict_types=1);
</div>
</div>
<div id="teams" class="section"><div class="card"><h3>Team Management</h3><p>Manage your monitoring teams here.</p></div></div>
<div id="api" class="section"><div class="card"><h3>API Keys</h3><p>Generate keys for external integrations.</p></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>
<!-- MOBILE MONITOR OVERLAY (CRYPTO STYLE) -->
<!-- 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 MONITOR</span>
<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>
@ -324,9 +245,9 @@ declare(strict_types=1);
</div>
</div>
<div id="mm-crypto-grid" class="mm-grid"></div>
<div style="background:#111; padding:8px 12px; border-radius:8px; margin-top:5px; display:flex; justify-content:space-between; align-items:center;">
<div id="mm-clock" style="font-family:'JetBrains Mono'; font-size:0.65rem; color:var(--crypto-blue)">00:00:00</div>
<div id="mm-health-pct" style="font-family:'JetBrains Mono'; font-size:0.65rem; color:var(--crypto-green)">100% HEALTH</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>
@ -336,17 +257,32 @@ declare(strict_types=1);
const sparklines = {};
class CryptoSparkline {
constructor(canvasId, initialData = []) {
constructor(canvasId) {
this.canvas = document.getElementById(canvasId);
this.ctx = this.canvas.getContext('2d');
this.data = initialData.length ? initialData : Array(30).fill(0);
this.color = '#00ff88';
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);
@ -354,9 +290,7 @@ declare(strict_types=1);
}
update(val, status) {
this.data.push(val);
if (this.data.length > 50) this.data.shift();
this.color = status === 'ok' ? '#00ff88' : '#ff3366';
this.addCandle(val, status);
this.draw();
}
@ -365,36 +299,47 @@ declare(strict_types=1);
const h = this.canvas.height / window.devicePixelRatio;
this.ctx.clearRect(0, 0, w, h);
if (this.data.length < 2) return;
const min = Math.min(...this.data) * 0.8;
const max = Math.max(...this.data) * 1.2 || 100;
const range = max - min;
this.ctx.beginPath();
this.ctx.strokeStyle = this.color;
this.ctx.lineWidth = 2;
this.ctx.lineJoin = 'round';
this.ctx.lineCap = 'round';
const step = w / (this.data.length - 1);
for (let i = 0; i < this.data.length; i++) {
const x = i * step;
const y = h - ((this.data[i] - min) / range * h);
if (i === 0) this.ctx.moveTo(x, y);
else this.ctx.lineTo(x, y);
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();
}
this.ctx.stroke();
// Gradient fill
const fillGrad = this.ctx.createLinearGradient(0, 0, 0, h);
fillGrad.addColorStop(0, this.color + '44');
fillGrad.addColorStop(1, this.color + '00');
this.ctx.lineTo(w, h);
this.ctx.lineTo(0, h);
this.ctx.fillStyle = fillGrad;
this.ctx.fill();
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);
});
}
}
@ -419,6 +364,8 @@ declare(strict_types=1);
function enterMobileMonitor() {
document.getElementById('mobile-monitor').style.display = 'flex';
// Force refresh sparklines
Object.values(sparklines).forEach(s => s.resize());
refreshData();
}
@ -428,7 +375,7 @@ declare(strict_types=1);
}
async function init() {
const resp = await fetch('api/uptime.php?action=settings');
const resp = await fetch('api/uptime.php?action=settings&v=<?php echo $v; ?>');
const data = await resp.json();
isMonitoring = data.monitoring_enabled;
updateStatusUI();
@ -484,9 +431,9 @@ declare(strict_types=1);
}
async function refreshData() {
const statResp = await fetch('api/uptime.php?action=stats');
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;
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 + '%';
@ -499,42 +446,25 @@ declare(strict_types=1);
mainChart.update('none');
}
const listResp = await fetch('api/uptime.php?action=list');
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 batteryRows = document.getElementById('battery-rows');
const mmCryptoGrid = document.getElementById('mm-crypto-grid');
inventory.innerHTML = '';
batteryRows.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>`;
// Dashboard Battery Rows
const brow = document.createElement('div');
brow.style.marginBottom = '10px';
brow.innerHTML = `<div style="font-size:0.7rem; color:#888; display:flex; justify-content:space-between"><span>${u.url}</span><span>${u.last_latency}ms</span></div>`;
const bcont = document.createElement('div');
bcont.className = 'battery-container';
for(let i=0; i<24; i++) {
const b = document.createElement('div');
b.className = 'battery-bar' + (isOk ? ' ok' : ' err');
bcont.appendChild(b);
}
brow.appendChild(bcont);
batteryRows.appendChild(brow);
// Mobile Crypto Grid
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.split('//')[1]}</span>
<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">