Files
eco_os/ecoos_daemon/ts_web/elements/ecoos-devices.ts
2026-01-12 14:34:56 +00:00

218 lines
5.9 KiB
TypeScript

/**
* EcoOS Devices View
* Card-based view for network, storage, input, and audio devices
*/
import {
html,
DeesElement,
customElement,
property,
css,
type TemplateResult,
} from '@design.estate/dees-element';
import { DeesPanel, DeesBadge } from '@design.estate/dees-catalog';
import { sharedStyles, formatBytes } from '../styles/shared.js';
import type { ISystemInfo } from '../../ts_interfaces/status.js';
@customElement('ecoos-devices')
export class EcoosDevices extends DeesElement {
@property({ type: Object })
public accessor systemInfo: ISystemInfo | null = null;
public static styles = [
sharedStyles,
css`
:host {
display: block;
padding: 16px;
}
.cards-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16px;
}
@media (max-width: 768px) {
.cards-grid {
grid-template-columns: 1fr;
}
}
.device-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 0;
border-bottom: 1px solid var(--border);
gap: 12px;
}
.device-row:last-child {
border-bottom: none;
padding-bottom: 0;
}
.device-row:first-child {
padding-top: 0;
}
.device-name {
font-weight: 500;
font-size: var(--text-sm);
flex-shrink: 0;
}
.device-info {
flex: 1;
text-align: right;
font-family: 'SF Mono', monospace;
font-size: var(--text-xs);
color: var(--text-secondary);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.device-secondary {
font-size: var(--text-xs);
color: var(--text-tertiary);
margin-top: 2px;
}
.progress-mini {
width: 60px;
height: 4px;
background: var(--border);
border-radius: 2px;
overflow: hidden;
flex-shrink: 0;
}
.progress-mini-bar {
height: 100%;
background: var(--accent);
transition: width 300ms ease;
}
.progress-mini-bar.warning {
background: var(--warning);
}
.progress-mini-bar.error {
background: var(--error);
}
.usage-info {
display: flex;
align-items: center;
gap: 8px;
}
.usage-text {
font-size: var(--text-xs);
color: var(--text-secondary);
font-family: 'SF Mono', monospace;
}
.empty-text {
font-size: var(--text-sm);
color: var(--text-tertiary);
padding: 8px 0;
}
`,
];
render(): TemplateResult {
if (!this.systemInfo) {
return html`<div class="empty">Loading...</div>`;
}
return html`
<div class="cards-grid">
<!-- Network -->
<dees-panel .title=${'Network'}>
${this.systemInfo.network?.length
? this.systemInfo.network.map(n => html`
<div class="device-row">
<span class="device-name">${n.name}</span>
<span class="device-info">${n.ip || '—'}</span>
<dees-badge .type=${n.state === 'up' ? 'success' : 'error'}>${n.state}</dees-badge>
</div>
`)
: html`<div class="empty-text">No network interfaces</div>`
}
</dees-panel>
<!-- Storage -->
<dees-panel .title=${'Storage'}>
${this.systemInfo.disks?.length
? this.systemInfo.disks.map(d => html`
<div class="device-row">
<div>
<div class="device-name">${d.mountpoint}</div>
<div class="device-secondary">${d.device}</div>
</div>
<div class="usage-info">
<span class="usage-text">${formatBytes(d.used)} / ${formatBytes(d.total)}</span>
<div class="progress-mini">
<div class="progress-mini-bar ${this.getUsageClass(d.usagePercent || 0)}" style="width: ${d.usagePercent || 0}%"></div>
</div>
</div>
</div>
`)
: html`<div class="empty-text">No disks</div>`
}
</dees-panel>
<!-- Input Devices -->
<dees-panel .title=${'Input Devices'}>
${this.systemInfo.inputDevices?.length
? this.systemInfo.inputDevices.map(d => html`
<div class="device-row">
<span class="device-name">${d.name}</span>
<dees-badge .type=${'default'}>${d.type}</dees-badge>
</div>
`)
: html`<div class="empty-text">No input devices</div>`
}
</dees-panel>
<!-- Audio Output -->
<dees-panel .title=${'Audio Output'}>
${this.systemInfo.speakers?.length
? this.systemInfo.speakers.map(s => html`
<div class="device-row">
<span class="device-name">${s.description}</span>
${s.isDefault ? html`<dees-badge .type=${'success'}>Default</dees-badge>` : ''}
</div>
`)
: html`<div class="empty-text">No speakers</div>`
}
</dees-panel>
<!-- Audio Input -->
<dees-panel .title=${'Audio Input'}>
${this.systemInfo.microphones?.length
? this.systemInfo.microphones.map(m => html`
<div class="device-row">
<span class="device-name">${m.description}</span>
${m.isDefault ? html`<dees-badge .type=${'success'}>Default</dees-badge>` : ''}
</div>
`)
: html`<div class="empty-text">No microphones</div>`
}
</dees-panel>
</div>
`;
}
private getUsageClass(usage: number): string {
if (usage > 90) return 'error';
if (usage > 75) return 'warning';
return '';
}
}