518 lines
21 KiB
PHP
518 lines
21 KiB
PHP
<?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>
|