194 lines
5.6 KiB
TypeScript
194 lines
5.6 KiB
TypeScript
/**
|
|
* EcoOS Overview View
|
|
* Shows services status, CPU, memory, system info, and controls
|
|
*/
|
|
|
|
import {
|
|
html,
|
|
DeesElement,
|
|
customElement,
|
|
property,
|
|
css,
|
|
type TemplateResult,
|
|
} from '@design.estate/dees-element';
|
|
|
|
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;
|
|
|
|
public static styles = [
|
|
sharedStyles,
|
|
css`
|
|
:host {
|
|
display: block;
|
|
padding: 20px;
|
|
}
|
|
|
|
.service-status {
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 8px 0;
|
|
}
|
|
|
|
.controls-section {
|
|
margin-top: 16px;
|
|
}
|
|
|
|
.control-status {
|
|
margin-top: 8px;
|
|
font-size: 12px;
|
|
color: var(--ecoos-text-dim);
|
|
}
|
|
`,
|
|
];
|
|
|
|
render(): TemplateResult {
|
|
if (!this.status) {
|
|
return html`<div>Loading...</div>`;
|
|
}
|
|
|
|
const { systemInfo, sway, chromium, swayStatus, chromiumStatus } = this.status;
|
|
|
|
return html`
|
|
<div class="grid">
|
|
<!-- Services Card -->
|
|
<div class="card">
|
|
<div class="card-title">Services</div>
|
|
<div class="service-status">
|
|
<span class="status-dot ${this.getStatusClass(swayStatus)}"></span>
|
|
<span>Sway Compositor</span>
|
|
</div>
|
|
<div class="service-status">
|
|
<span class="status-dot ${this.getStatusClass(chromiumStatus)}"></span>
|
|
<span>Chromium Browser</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- CPU Card -->
|
|
<div class="card">
|
|
<div class="card-title">CPU</div>
|
|
<div class="stat">
|
|
<div class="stat-label">Model</div>
|
|
<div class="stat-value">${systemInfo?.cpu?.model || '-'}</div>
|
|
</div>
|
|
<div class="stat">
|
|
<div class="stat-label">Cores</div>
|
|
<div class="stat-value">${systemInfo?.cpu?.cores || '-'}</div>
|
|
</div>
|
|
<div class="stat">
|
|
<div class="stat-label">Usage</div>
|
|
<div class="stat-value">${systemInfo?.cpu?.usage || 0}%</div>
|
|
<div class="progress-bar">
|
|
<div class="progress-fill" style="width: ${systemInfo?.cpu?.usage || 0}%"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Memory Card -->
|
|
<div class="card">
|
|
<div class="card-title">Memory</div>
|
|
<div class="stat">
|
|
<div class="stat-label">Used / Total</div>
|
|
<div class="stat-value">
|
|
${formatBytes(systemInfo?.memory?.used || 0)} / ${formatBytes(systemInfo?.memory?.total || 0)}
|
|
</div>
|
|
<div class="progress-bar">
|
|
<div class="progress-fill" style="width: ${systemInfo?.memory?.usagePercent || 0}%"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- System Card -->
|
|
<div class="card">
|
|
<div class="card-title">System</div>
|
|
<div class="stat">
|
|
<div class="stat-label">Hostname</div>
|
|
<div class="stat-value">${systemInfo?.hostname || '-'}</div>
|
|
</div>
|
|
<div class="stat">
|
|
<div class="stat-label">Uptime</div>
|
|
<div class="stat-value">${formatUptime(systemInfo?.uptime || 0)}</div>
|
|
</div>
|
|
<div class="stat">
|
|
<div class="stat-label">GPU</div>
|
|
<div class="stat-value">
|
|
${systemInfo?.gpu?.length ? systemInfo.gpu.map(g => g.name).join(', ') : 'None detected'}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Controls Card -->
|
|
<div class="card">
|
|
<div class="card-title">Controls</div>
|
|
<button
|
|
class="btn btn-primary"
|
|
@click=${this.restartChromium}
|
|
?disabled=${this.loading || !sway}
|
|
>
|
|
Restart Browser
|
|
</button>
|
|
<button
|
|
class="btn btn-danger"
|
|
@click=${this.rebootSystem}
|
|
?disabled=${this.loading}
|
|
>
|
|
Reboot System
|
|
</button>
|
|
<div class="control-status" id="control-status"></div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
private getStatusClass(status: IServiceStatus): string {
|
|
switch (status?.state) {
|
|
case 'running': return 'running';
|
|
case 'starting': return 'starting';
|
|
default: return 'stopped';
|
|
}
|
|
}
|
|
|
|
private async restartChromium(): Promise<void> {
|
|
this.loading = true;
|
|
try {
|
|
const response = await fetch('/api/restart-chromium', { method: 'POST' });
|
|
const result = await response.json();
|
|
this.showControlStatus(result.message, !result.success);
|
|
} catch (error) {
|
|
this.showControlStatus(`Error: ${error}`, true);
|
|
} finally {
|
|
this.loading = false;
|
|
}
|
|
}
|
|
|
|
private async rebootSystem(): Promise<void> {
|
|
if (!confirm('Are you sure you want to reboot the system?')) return;
|
|
|
|
this.loading = true;
|
|
try {
|
|
const response = await fetch('/api/reboot', { method: 'POST' });
|
|
const result = await response.json();
|
|
this.showControlStatus(result.message, !result.success);
|
|
} catch (error) {
|
|
this.showControlStatus(`Error: ${error}`, true);
|
|
} finally {
|
|
this.loading = false;
|
|
}
|
|
}
|
|
|
|
private showControlStatus(message: string, isError: boolean): void {
|
|
const el = this.shadowRoot?.getElementById('control-status');
|
|
if (el) {
|
|
el.textContent = message;
|
|
el.style.color = isError ? 'var(--ecoos-error)' : 'var(--ecoos-success)';
|
|
}
|
|
}
|
|
}
|