import { customElement, DeesElement, type TemplateResult, html, property, css, cssManager, state, } from '@design.estate/dees-element'; import { DeesAppuiSecondarymenu, DeesIcon, DeesStatsGrid } from '@design.estate/dees-catalog'; import type { ISecondaryMenuGroup, ISecondaryMenuItem } from '../../elements/interfaces/secondarymenu.js'; import { demo } from './eco-view-system.demo.js'; // Ensure components are registered DeesAppuiSecondarymenu; DeesIcon; DeesStatsGrid; declare global { interface HTMLElementTagNameMap { 'eco-view-system': EcoViewSystem; } } export type TSystemPanel = | 'overview' | 'cpu' | 'memory' | 'storage' | 'network' | 'processes'; @customElement('eco-view-system') export class EcoViewSystem extends DeesElement { public static demo = demo; public static demoGroup = 'Views'; public static styles = [ cssManager.defaultStyles, css` :host { display: block; width: 100%; height: 100%; background: ${cssManager.bdTheme('#f5f5f7', 'hsl(240 6% 10%)')}; color: ${cssManager.bdTheme('hsl(0 0% 10%)', 'hsl(0 0% 98%)')}; font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; } .system-container { display: flex; height: 100%; } dees-appui-secondarymenu { flex-shrink: 0; background: ${cssManager.bdTheme('#ffffff', 'hsl(240 6% 8%)')}; border-right: 1px solid ${cssManager.bdTheme('hsl(0 0% 90%)', 'hsl(240 5% 15%)')}; } .content { flex: 1; overflow-y: auto; padding: 32px 48px; } .panel-header { margin-bottom: 32px; } .panel-title { font-size: 28px; font-weight: 600; color: ${cssManager.bdTheme('hsl(0 0% 10%)', 'hsl(0 0% 98%)')}; margin-bottom: 8px; } .panel-description { font-size: 14px; color: ${cssManager.bdTheme('hsl(0 0% 50%)', 'hsl(0 0% 60%)')}; } .stats-section { margin-bottom: 32px; } .section-title { font-size: 13px; font-weight: 600; color: ${cssManager.bdTheme('hsl(0 0% 50%)', 'hsl(0 0% 50%)')}; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 16px; } dees-statsgrid { --dees-statsgrid-gap: 16px; } .process-list { background: ${cssManager.bdTheme('#ffffff', 'hsl(240 6% 12%)')}; border: 1px solid ${cssManager.bdTheme('hsl(0 0% 90%)', 'hsl(240 5% 18%)')}; border-radius: 12px; overflow: hidden; } .process-header { display: grid; grid-template-columns: 2fr 1fr 1fr 1fr; padding: 12px 16px; background: ${cssManager.bdTheme('hsl(0 0% 97%)', 'hsl(240 5% 14%)')}; border-bottom: 1px solid ${cssManager.bdTheme('hsl(0 0% 90%)', 'hsl(240 5% 18%)')}; font-size: 12px; font-weight: 600; color: ${cssManager.bdTheme('hsl(0 0% 50%)', 'hsl(0 0% 55%)')}; text-transform: uppercase; letter-spacing: 0.5px; } .process-row { display: grid; grid-template-columns: 2fr 1fr 1fr 1fr; padding: 12px 16px; border-bottom: 1px solid ${cssManager.bdTheme('hsl(0 0% 94%)', 'hsl(240 5% 15%)')}; font-size: 14px; color: ${cssManager.bdTheme('hsl(0 0% 20%)', 'hsl(0 0% 85%)')}; } .process-row:last-child { border-bottom: none; } .process-name { font-weight: 500; } .process-value { color: ${cssManager.bdTheme('hsl(0 0% 40%)', 'hsl(0 0% 65%)')}; } .process-value.high { color: hsl(0 84% 60%); font-weight: 500; } `, ]; @property({ type: String }) accessor activePanel: TSystemPanel = 'overview'; // System data (can be set externally) @property({ type: Number }) accessor cpuUsage = 0; @property({ type: Number }) accessor memoryUsage = 0; @property({ type: Number }) accessor diskUsage = 0; @property({ type: Number }) accessor cpuTemp = 0; @property({ type: String }) accessor uptime = '--'; @property({ type: Number }) accessor cpuCores = 0; @property({ type: Number }) accessor cpuPhysicalCores = 0; @property({ type: String }) accessor cpuModel = 'Unknown'; @property({ type: Number }) accessor cpuSpeed = 0; @property({ type: Number }) accessor cpuSpeedMax = 0; @property({ type: Number }) accessor memoryTotal = 0; @property({ type: Number }) accessor memoryUsed = 0; @property({ type: Number }) accessor memoryFree = 0; @property({ type: Number }) accessor memoryAvailable = 0; @property({ type: Number }) accessor memoryCached = 0; @property({ type: Number }) accessor memoryBuffers = 0; @property({ type: Number }) accessor swapTotal = 0; @property({ type: Number }) accessor swapUsed = 0; @property({ type: Number }) accessor diskTotal = 0; @property({ type: Number }) accessor diskUsed = 0; @property({ type: Number }) accessor diskFree = 0; @property({ type: Number }) accessor networkRxSec = 0; @property({ type: Number }) accessor networkTxSec = 0; @property({ type: Number }) accessor networkRxTotal = 0; @property({ type: Number }) accessor networkTxTotal = 0; @property({ type: String }) accessor hostname = 'Unknown'; @property({ type: String }) accessor platform = 'Unknown'; @property({ type: String }) accessor distro = ''; @property({ type: Array }) accessor loadAvg: number[] = [0, 0, 0]; @property({ type: Array }) accessor coreLoads: number[] = []; @state() accessor networkInHistory: number[] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; @state() accessor networkOutHistory: number[] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; @state() accessor memoryUsageHistory: number[] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; // Process data @state() accessor processTotal = 0; @state() accessor processRunning = 0; @state() accessor processSleeping = 0; @state() accessor processBlocked = 0; @state() accessor topProcesses: Array<{ name: string; pid: number; cpu: number; memory: number; state: string }> = []; // Public method to update metrics from backend public setMetrics(metrics: { cpu: { usage: number; cores: number; physicalCores?: number; model: string; speed: number; speedMax?: number; loadAvg: number[]; coreLoads?: number[] }; memory: { total: number; used: number; free: number; available?: number; usagePercent: number; swapTotal?: number; swapUsed?: number; cached?: number; buffers?: number }; disk?: { total: number; used: number; free: number; usagePercent: number }; network?: { rxSec: number; txSec: number; rxTotal: number; txTotal: number }; system: { platform: string; distro?: string; hostname: string; uptimeFormatted: string }; }): void { // CPU metrics this.cpuUsage = metrics.cpu.usage; this.cpuCores = metrics.cpu.cores; this.cpuPhysicalCores = metrics.cpu.physicalCores || metrics.cpu.cores; this.cpuModel = metrics.cpu.model; this.cpuSpeed = metrics.cpu.speed; this.cpuSpeedMax = metrics.cpu.speedMax || metrics.cpu.speed; this.loadAvg = metrics.cpu.loadAvg; this.coreLoads = metrics.cpu.coreLoads || []; // Memory metrics this.memoryUsage = metrics.memory.usagePercent; this.memoryTotal = metrics.memory.total; this.memoryUsed = metrics.memory.used; this.memoryFree = metrics.memory.free; this.memoryAvailable = metrics.memory.available || metrics.memory.free; this.memoryCached = metrics.memory.cached || 0; this.memoryBuffers = metrics.memory.buffers || 0; this.swapTotal = metrics.memory.swapTotal || 0; this.swapUsed = metrics.memory.swapUsed || 0; // Update memory usage history for trend chart this.memoryUsageHistory = [...this.memoryUsageHistory.slice(1), metrics.memory.usagePercent]; // Disk metrics if (metrics.disk) { this.diskUsage = metrics.disk.usagePercent; this.diskTotal = metrics.disk.total; this.diskUsed = metrics.disk.used; this.diskFree = metrics.disk.free; } // Network metrics if (metrics.network) { this.networkRxSec = metrics.network.rxSec; this.networkTxSec = metrics.network.txSec; this.networkRxTotal = metrics.network.rxTotal; this.networkTxTotal = metrics.network.txTotal; // Update history for trend charts this.networkInHistory = [...this.networkInHistory.slice(1), metrics.network.rxSec]; this.networkOutHistory = [...this.networkOutHistory.slice(1), metrics.network.txSec]; } // System metrics this.hostname = metrics.system.hostname; this.platform = metrics.system.platform; this.distro = metrics.system.distro || ''; this.uptime = metrics.system.uptimeFormatted; } // Method to set CPU temperature separately (may not always be available) public setTemperature(temp: { main: number; cores?: number[]; max?: number }): void { this.cpuTemp = temp.main || 0; } // Method to set processes data public setProcesses(data: { total: number; running: number; sleeping: number; blocked?: number; list: Array<{ name: string; pid: number; cpu: number; memory: number; state: string }> }): void { this.processTotal = data.total; this.processRunning = data.running; this.processSleeping = data.sleeping; this.processBlocked = data.blocked || 0; this.topProcesses = data.list || []; } // Helper to format bytes private formatBytes(bytes: number): string { if (bytes === 0) return '0 B'; const k = 1024; const sizes = ['B', 'KB', 'MB', 'GB', 'TB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]; } private getMenuGroups(): ISecondaryMenuGroup[] { return [ { name: 'Monitor', iconName: 'lucide:activity', items: [ { key: 'overview', iconName: 'lucide:layoutDashboard', action: () => this.activePanel = 'overview', }, ], }, { name: 'Hardware', iconName: 'lucide:cpu', items: [ { key: 'cpu', iconName: 'lucide:cpu', action: () => this.activePanel = 'cpu', }, { key: 'memory', iconName: 'lucide:memoryStick', action: () => this.activePanel = 'memory', }, { key: 'storage', iconName: 'lucide:hardDrive', action: () => this.activePanel = 'storage', }, ], }, { name: 'Network', iconName: 'lucide:network', items: [ { key: 'network', iconName: 'lucide:wifi', action: () => this.activePanel = 'network', }, ], }, { name: 'Software', iconName: 'lucide:layers', items: [ { key: 'processes', iconName: 'lucide:listTree', action: () => this.activePanel = 'processes', }, ], }, ]; } private getSelectedItem(): ISecondaryMenuItem | null { for (const group of this.getMenuGroups()) { for (const item of group.items) { if ('key' in item && item.key === this.activePanel) { return item; } } } return null; } public render(): TemplateResult { return html`