/**
* 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;
}
}
}