diff --git a/dashcaddy-api/status/js/monitoring-widgets.js b/dashcaddy-api/status/js/monitoring-widgets.js
deleted file mode 100644
index 60fc646..0000000
--- a/dashcaddy-api/status/js/monitoring-widgets.js
+++ /dev/null
@@ -1,335 +0,0 @@
-// ========== MONITORING WIDGETS ==========
-// Embeds a compact system-resource + health summary panel directly on the
-// main dashboard. Replaces the need for a separate monitoring-dashboard.html
-// page β quick at-a-glance stats where you already are.
-(function () {
-
- // ----- Style injection (scoped to .dc-monitor so it doesn't leak) -----
- const styleEl = document.createElement('style');
- styleEl.textContent = `
- .dc-monitor {
- display: grid;
- grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
- gap: 12px;
- margin-bottom: 16px;
- padding: 12px 16px;
- background: var(--card-base);
- border: 1px solid var(--border);
- border-radius: var(--radius);
- }
- .dc-monitor-card {
- padding: 10px 12px;
- background: var(--card-bg, rgba(255,255,255,0.04));
- border-radius: 8px;
- border: 1px solid var(--border);
- }
- .dc-monitor-label {
- font-size: 0.7rem;
- color: var(--muted);
- text-transform: uppercase;
- letter-spacing: 0.5px;
- margin-bottom: 4px;
- }
- .dc-monitor-value {
- font-size: 1.4rem;
- font-weight: 600;
- color: var(--fg);
- }
- .dc-monitor-sub {
- font-size: 0.7rem;
- color: var(--muted);
- margin-top: 4px;
- }
- .dc-monitor-bar {
- margin-top: 6px;
- width: 100%;
- height: 4px;
- background: color-mix(in srgb, var(--muted) 20%, transparent);
- border-radius: 2px;
- overflow: hidden;
- }
- .dc-monitor-bar-fill {
- height: 100%;
- width: 0%;
- background: var(--ok-fg, #27ae60);
- transition: width 0.3s ease, background 0.3s ease;
- }
- .dc-monitor-bar-fill.warn { background: #f39c12; }
- .dc-monitor-bar-fill.bad { background: #e74c3c; }
- .dc-monitor-header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- margin-bottom: 8px;
- }
- .dc-monitor-title {
- font-size: 0.85rem;
- font-weight: 500;
- color: var(--muted);
- display: flex;
- align-items: center;
- gap: 6px;
- }
- .dc-monitor-pill {
- display: inline-flex;
- align-items: center;
- gap: 4px;
- padding: 2px 8px;
- border-radius: 999px;
- font-size: 0.7rem;
- font-weight: 500;
- }
- .dc-monitor-pill.ok { background: color-mix(in srgb, #27ae60 20%, transparent); color: #27ae60; }
- .dc-monitor-pill.warn { background: color-mix(in srgb, #f39c12 20%, transparent); color: #f39c12; }
- .dc-monitor-pill.bad { background: color-mix(in srgb, #e74c3c 20%, transparent); color: #e74c3c; }
- .dc-monitor-refresh {
- font-size: 0.7rem;
- color: var(--muted);
- opacity: 0.7;
- }
- `;
- document.head.appendChild(styleEl);
-
- // ----- Container element (inserted above service-filter-bar) -----
- const filterBar = document.getElementById('service-filter-bar');
- if (!filterBar) return;
-
- const panel = document.createElement('div');
- panel.className = 'dc-monitor';
- panel.id = 'dc-monitor-panel';
- panel.innerHTML = `
-
-
-
Services
-
β
-
loadingβ¦
-
-
-
Containers Up
-
β
-
loadingβ¦
-
-
-
-
- `;
- // Insert ABOVE the filter bar
- filterBar.parentNode.insertBefore(panel, filterBar);
-
- // ----- Helpers -----
- function setBar(id, pct) {
- const el = document.getElementById(id);
- if (!el) return;
- const p = Math.max(0, Math.min(100, Number(pct) || 0));
- el.style.width = p + '%';
- el.classList.remove('warn', 'bad');
- if (p >= 85) el.classList.add('bad');
- else if (p >= 65) el.classList.add('warn');
- }
-
- function fmtPct(v) {
- if (v == null || isNaN(v)) return 'β';
- return (Math.round(v * 10) / 10) + '%';
- }
-
- function fmtBytes(b) {
- if (b == null || isNaN(b)) return 'β';
- const units = ['B', 'KB', 'MB', 'GB', 'TB'];
- let i = 0;
- while (b >= 1024 && i < units.length - 1) { b /= 1024; i++; }
- return b.toFixed(1) + ' ' + units[i];
- }
-
- // ----- Robust services count -----
- // Read from multiple sources so we always have a number:
- // 1. window.APPS (populated by grid.js after loadServices)
- // 2. #cards .card elements (post-buildGrid)
- // 3. live fetch /api/v1/services (last-resort fallback if grid hasn't run)
- async function fetchServicesCount() {
- // Source 1+2: window.APPS / DOM cards
- if (Array.isArray(window.APPS) && window.APPS.length > 0) {
- const up = document.querySelectorAll('#cards .card[data-status="on"]').length;
- return { total: window.APPS.length, up, source: 'APPS' };
- }
- const cards = document.querySelectorAll('#cards .card');
- if (cards.length > 0) {
- const up = Array.from(cards).filter(c => c.dataset.status === 'on').length;
- return { total: cards.length, up, source: 'DOM' };
- }
- // Source 3: fetch live (endpoints may return {success, services:[...]} OR raw array)
- try {
- const r = await fetch('/api/v1/services', { cache: 'no-store' });
- if (!r.ok) return { total: 0, up: 0, source: 'fetch-fail' };
- const body = await r.json();
- const list = (body && Array.isArray(body.services)) ? body.services
- : (Array.isArray(body)) ? body
- : [];
- // Persist for the grid so this fallback only fires once
- if (Array.isArray(window.APPS) || typeof window.APPS === 'undefined') window.APPS = list;
- const up = document.querySelectorAll('#cards .card[data-status="on"]').length;
- return { total: list.length, up, source: 'fetch' };
- } catch (_) {
- return { total: 0, up: 0, source: 'fetch-error' };
- }
- }
-
- async function setServicesCard() {
- const { total, up } = await fetchServicesCount();
- const el = document.getElementById('dc-monitor-services');
- const sub = document.getElementById('dc-monitor-services-sub');
- if (el) el.textContent = `${up} / ${total}`;
- if (sub) sub.textContent = total === 0
- ? 'no services yet'
- : `${up} online Β· ${total - up} offline`;
- }
-
- function applyHealthSummary(data) {
- const el = document.getElementById('dc-monitor-health');
- const sub = document.getElementById('dc-monitor-health-sub');
- if (!el) return;
- if (!data || data.summary == null) {
- el.textContent = 'β';
- if (sub) sub.textContent = 'no data';
- return;
- }
- const s = data.summary;
- const healthy = s.healthy ?? s.up ?? 0;
- const unhealthy = s.unhealthy ?? s.down ?? 0;
- const total = s.total ?? (healthy + unhealthy);
- el.textContent = `${healthy}/${total}`;
- if (sub) {
- if (unhealthy === 0) {
- sub.innerHTML = 'β all healthy';
- } else if (unhealthy <= 2) {
- sub.innerHTML = `β ${unhealthy} degraded`;
- } else {
- sub.innerHTML = `β ${unhealthy} down`;
- }
- }
- }
-
- // ----- Data fetches -----
- async function fetchStats() {
- try {
- const r = await fetch('/api/v1/monitoring/stats', { cache: 'no-store' });
- if (!r.ok) return null;
- const data = await r.json();
- return (data && data.stats) ? data.stats : null;
- } catch (_) {
- return null;
- }
- }
-
- async function fetchHealth() {
- try {
- const r = await fetch('/api/v1/health-checks/status', { cache: 'no-store' });
- if (!r.ok) return null;
- return await r.json();
- } catch (_) {
- return null;
- }
- }
-
- function applyStats(stats) {
- const containers = document.getElementById('dc-monitor-containers');
- const containersSub = document.getElementById('dc-monitor-containers-sub');
- const cpuEl = document.getElementById('dc-monitor-cpu');
- const memEl = document.getElementById('dc-monitor-mem');
-
- if (!stats) {
- if (containers) containers.textContent = 'β';
- if (cpuEl) cpuEl.textContent = 'β';
- if (memEl) memEl.textContent = 'β';
- return;
- }
-
- const entries = Object.values(stats);
- if (entries.length === 0) {
- if (containers) containers.textContent = '0';
- if (containersSub) containersSub.textContent = 'no containers reporting';
- if (cpuEl) cpuEl.textContent = '0%';
- if (memEl) memEl.textContent = '0%';
- setBar('dc-monitor-cpu-bar', 0);
- setBar('dc-monitor-mem-bar', 0);
- return;
- }
-
- let cpuSum = 0, memSum = 0, memBytes = 0, cpuCount = 0, memCount = 0;
- entries.forEach(s => {
- // CPU may be percentage (0-100) or fraction (0-1) β handle both
- if (s.cpu != null) {
- const cpu = Number(s.cpu);
- if (!isNaN(cpu)) {
- cpuSum += cpu > 1 ? cpu : cpu * 100;
- cpuCount++;
- }
- }
- if (s.memory != null) {
- const mem = Number(s.memory);
- if (!isNaN(mem)) {
- memSum += mem;
- memBytes += Number(s.memoryUsage || 0);
- memCount++;
- }
- }
- });
-
- const avgCpu = cpuCount ? cpuSum / cpuCount : 0;
- const avgMem = memCount ? memSum / memCount : 0;
-
- if (containers) containers.textContent = String(entries.length);
- if (containersSub) {
- const memTxt = memBytes ? ` Β· ${fmtBytes(memBytes)} RAM` : '';
- containersSub.textContent = `running${memTxt}`;
- }
- if (cpuEl) cpuEl.textContent = fmtPct(avgCpu);
- if (memEl) memEl.textContent = fmtPct(avgMem);
- setBar('dc-monitor-cpu-bar', avgCpu);
- setBar('dc-monitor-mem-bar', avgMem);
- }
-
- // ----- Public refresh function -----
- let inFlight = false;
- async function refresh() {
- if (inFlight) return;
- inFlight = true;
- try {
- setServicesCard();
- const [stats, health] = await Promise.all([fetchStats(), fetchHealth()]);
- applyStats(stats);
- applyHealthSummary(health);
- const stamp = document.getElementById('dc-monitor-refresh-stamp');
- if (stamp) {
- const now = new Date();
- stamp.textContent = `updated ${now.toLocaleTimeString()}`;
- }
- } finally {
- inFlight = false;
- }
- }
-
- // Expose for init.js to call once and re-call after each refreshAll cycle
- window.refreshMonitoringWidgets = refresh;
-
- // Auto-refresh on the STATS interval (separate from full DASHBOARD refresh)
- setInterval(refresh, (typeof DC !== 'undefined' && DC.POLL && DC.POLL.STATS) || 5000);
-
- // Refresh once on first script load (init.js also calls this; double-call is harmless)
- setTimeout(refresh, 200);
-
-})();