/** * EcoOS Overview View * Dashboard with stats grid, service panels, and system info */ import { html, DeesElement, customElement, property, css, type TemplateResult, } from '@design.estate/dees-element'; import { DeesButton, DeesPanel, DeesStatsgrid, DeesBadge, type IStatsTile, } from '@design.estate/dees-catalog'; import { sharedStyles, formatBytes, formatUptime } from '../styles/shared.js'; import type { IStatus, IServiceStatus } from '../../ts_interfaces/status.js'; @customElement('ecoos-overview') export class EcoosOverview extends DeesElement { @property({ type: Object }) public accessor status: IStatus | null = null; @property({ type: Boolean }) public accessor loading: boolean = false; @property({ type: String }) public accessor controlMessage: string = ''; @property({ type: Boolean }) public accessor controlError: boolean = false; public static styles = [ sharedStyles, css` :host { display: block; padding: 16px; } .page { display: flex; flex-direction: column; gap: 16px; } .cards-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px; } @media (max-width: 768px) { .cards-row { grid-template-columns: 1fr; } } .service-row { display: flex; justify-content: space-between; align-items: center; padding: 8px 0; border-bottom: 1px solid var(--border); } .service-row:last-child { border-bottom: none; padding-bottom: 0; } .service-row:first-child { padding-top: 0; } .service-name { font-weight: 500; font-size: var(--text-sm); } .service-error { font-size: var(--text-xs); color: var(--error); margin-top: 2px; } .info-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; } .info-item { display: flex; flex-direction: column; gap: 2px; } .info-label { font-size: var(--text-xs); color: var(--text-tertiary); text-transform: uppercase; letter-spacing: 0.03em; } .info-value { font-size: var(--text-sm); font-family: 'SF Mono', monospace; } .actions-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; } .message { font-size: var(--text-xs); padding: 4px 8px; border-radius: 4px; } .message.success { background: hsla(142.1, 76.2%, 36.3%, 0.15); color: var(--success); } .message.error { background: hsla(0, 84.2%, 60.2%, 0.15); color: var(--error); } `, ]; render(): TemplateResult { if (!this.status) { return html`
Loading...
`; } const { systemInfo, swayStatus, chromiumStatus } = this.status; const cpuUsage = systemInfo?.cpu?.usage || 0; const memUsage = systemInfo?.memory?.usagePercent || 0; const statsTiles: IStatsTile[] = [ { id: 'cpu', title: 'CPU', value: Math.round(cpuUsage), type: 'percentage', icon: 'lucide:cpu', description: `${systemInfo?.cpu?.cores || 0} cores`, }, { id: 'memory', title: 'Memory', value: Math.round(memUsage), type: 'percentage', icon: 'lucide:database', description: `${formatBytes(systemInfo?.memory?.used || 0)} / ${formatBytes(systemInfo?.memory?.total || 0)}`, }, { id: 'uptime', title: 'Uptime', value: formatUptime(systemInfo?.uptime || 0), type: 'text', icon: 'lucide:clock', }, ]; return html`
Sway Compositor
${swayStatus?.error ? html`
${swayStatus.error}
` : ''}
${this.renderStatusBadge(swayStatus)}
Chromium Browser
${chromiumStatus?.error ? html`
${chromiumStatus.error}
` : ''}
${this.renderStatusBadge(chromiumStatus)}
Hostname ${systemInfo?.hostname || '—'}
CPU Model ${this.truncate(systemInfo?.cpu?.model || '—', 20)}
GPU ${systemInfo?.gpu?.length ? systemInfo.gpu.map(g => g.name).join(', ') : 'None'}
${this.controlMessage ? html` ${this.controlMessage} ` : ''}
`; } private renderStatusBadge(status: IServiceStatus): TemplateResult { const state = status?.state || 'stopped'; let badgeType: 'default' | 'success' | 'warning' | 'error' = 'default'; let label = 'Stopped'; if (state === 'running') { badgeType = 'success'; label = 'Running'; } else if (state === 'starting') { badgeType = 'warning'; label = 'Starting'; } else if (state === 'failed') { badgeType = 'error'; label = 'Failed'; } return html`${label}`; } private truncate(str: string, len: number): string { return str.length > len ? str.substring(0, len) + '...' : str; } private async restartChromium(): Promise { this.loading = true; this.controlMessage = ''; try { const response = await fetch('/api/restart-chromium', { method: 'POST' }); const result = await response.json(); this.controlMessage = result.message; this.controlError = !result.success; } catch (error) { this.controlMessage = `Error: ${error}`; this.controlError = true; } finally { this.loading = false; } } private async rebootSystem(): Promise { if (!confirm('Are you sure you want to reboot?')) return; this.loading = true; this.controlMessage = ''; try { const response = await fetch('/api/reboot', { method: 'POST' }); const result = await response.json(); this.controlMessage = result.message; this.controlError = !result.success; } catch (error) { this.controlMessage = `Error: ${error}`; this.controlError = true; } finally { this.loading = false; } } }