function dashboard() { return { services: [], system: { cpu: { percent: 0 }, memory: { percent: 0, used_gb: 0, total_gb: 0 }, disk: { percent: 0, used_gb: 0, total_gb: 0 }, containers: { running: 0, total: 0 }, uptime: { days: 0, seconds: 0 } }, lastUpdate: 'Never', loading: true, wsConnected: false, ws: null, wsReconnectTimeout: null, actionMessage: null, actionError: false, // ──────────────────── Documents ──────────────────── documents: [], showDocModal: false, docTitle: '', docContent: '', docLoading: false, init() { console.log('Initializing RedUnits Control Panel...'); this.fetchDocuments(); this.connectWebSocket(); }, // ──────────────────── WebSocket ──────────────────── connectWebSocket() { const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const wsUrl = `${protocol}//${window.location.host}/ws`; console.log(`Connecting to WebSocket: ${wsUrl}`); this.ws = new WebSocket(wsUrl); this.ws.onopen = () => { console.log('WebSocket connected'); this.wsConnected = true; if (this.wsReconnectTimeout) { clearTimeout(this.wsReconnectTimeout); this.wsReconnectTimeout = null; } }; this.ws.onmessage = (event) => { try { const data = JSON.parse(event.data); if (data.type === 'update') { this.services = data.services; this.system = data.system; this.loading = false; this.updateLastUpdate(); } } catch (e) { console.error('Error parsing WS message:', e); } }; this.ws.onclose = () => { console.warn('WebSocket disconnected. Reconnecting in 5s...'); this.wsConnected = false; this.scheduleReconnect(); }; this.ws.onerror = (err) => { console.error('WebSocket error:', err); this.wsConnected = false; this.ws.close(); }; }, scheduleReconnect() { if (this.wsReconnectTimeout) return; this.wsReconnectTimeout = setTimeout(() => { this.wsReconnectTimeout = null; this.connectWebSocket(); }, 5000); }, // ──────────────────── Manual refresh (REST fallback) ──────────────────── async refresh() { this.loading = true; try { const [svcRes, sysRes] = await Promise.all([ fetch('/api/services'), fetch('/api/system') ]); const svcData = await svcRes.json(); const sysData = await sysRes.json(); this.services = svcData.services; this.system = sysData; this.updateLastUpdate(); } catch (error) { console.error('Error refreshing data:', error); } finally { this.loading = false; } }, // ──────────────────── Documents ──────────────────── async fetchDocuments() { try { const response = await fetch('/api/documents'); if (response.ok) { const data = await response.json(); this.documents = data.documents; } } catch (error) { console.error('Error fetching documents:', error); } }, async openDocument(doc) { this.showDocModal = true; this.docTitle = doc.title; this.docContent = ''; this.docLoading = true; try { const res = await fetch(`/api/document/${doc.id}`); const data = await res.json(); if (res.ok) { this.docContent = marked.parse(data.content); } else { this.docContent = `
Failed to load document: ${data.detail || 'Unknown error'}
`; } } catch (error) { this.docContent = `
Network error while loading document
`; } finally { this.docLoading = false; } }, closeDocument() { this.showDocModal = false; }, // ──────────────────── Container actions ──────────────────── async restartService(serviceId) { if (!confirm(`Restart service "${serviceId}"?`)) return; await this._serviceAction(serviceId, 'restart'); }, async stopService(serviceId) { if (!confirm(`Stop service "${serviceId}"? It will go offline.`)) return; await this._serviceAction(serviceId, 'stop'); }, async _serviceAction(serviceId, action) { try { const res = await fetch(`/api/services/${serviceId}/${action}`, { method: 'POST' }); const data = await res.json(); if (res.ok) { this.showMessage(`✅ ${data.message}`, false); } else { this.showMessage(`❌ ${data.detail || data.message}`, true); } } catch (e) { this.showMessage(`❌ Network error: ${e.message}`, true); } }, showMessage(msg, isError) { this.actionMessage = msg; this.actionError = isError; setTimeout(() => { this.actionMessage = null; }, 4000); }, // ──────────────────── Helpers ──────────────────── updateLastUpdate() { this.lastUpdate = new Date().toLocaleTimeString('en-GB', { timeZone: 'Europe/Riga' }); }, allServicesOnline() { if (this.services.length === 0) return false; return this.services.every(s => s.status.online); }, getProgressColor(percent) { if (percent < 60) return 'green'; if (percent < 80) return 'yellow'; return 'red'; } }; }