218 lines
5.9 KiB
TypeScript
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 '';
|
|
}
|
|
}
|