/** * EcoOS Logs View * Panel-wrapped terminal-style log viewer */ import { html, DeesElement, customElement, property, state, css, type TemplateResult, } from '@design.estate/dees-element'; import { DeesPanel } from '@design.estate/dees-catalog'; import { sharedStyles } from '../styles/shared.js'; @customElement('ecoos-logs') export class EcoosLogs extends DeesElement { @property({ type: Array }) public accessor daemonLogs: string[] = []; @property({ type: Array }) public accessor systemLogs: string[] = []; @state() private accessor activeTab: 'daemon' | 'system' = 'daemon'; @state() private accessor autoScroll: boolean = true; public static styles = [ sharedStyles, css` :host { display: block; padding: 16px; } .container { display: flex; flex-direction: column; height: calc(100vh - 140px); min-height: 300px; } .tabs { display: flex; gap: 0; border-bottom: 1px solid var(--border); margin-bottom: 12px; } .tab { padding: 8px 16px; font-size: var(--text-sm); font-weight: 500; color: var(--text-tertiary); cursor: pointer; border-bottom: 2px solid transparent; margin-bottom: -1px; transition: color 150ms ease; } .tab:hover { color: var(--text); } .tab.active { color: var(--text); border-bottom-color: var(--accent); } .header-row { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; } .count { font-size: var(--text-xs); color: var(--text-tertiary); font-family: 'SF Mono', monospace; } .auto-scroll { display: flex; align-items: center; gap: 6px; font-size: var(--text-xs); color: var(--text-tertiary); cursor: pointer; padding: 4px 8px; border-radius: 4px; transition: background 150ms ease; } .auto-scroll:hover { background: var(--bg-hover); } .auto-scroll.active { color: var(--accent); } .auto-scroll .indicator { width: 6px; height: 6px; border-radius: 50%; background: var(--text-tertiary); } .auto-scroll.active .indicator { background: var(--accent); } .terminal { flex: 1; background: hsl(0 0% 2%); font-family: 'SF Mono', 'Fira Code', 'Consolas', monospace; font-size: 11px; line-height: 1.5; padding: 12px; overflow-y: auto; overflow-x: hidden; border-radius: 6px; } .line { white-space: pre-wrap; word-break: break-all; padding: 1px 0; color: var(--text-secondary); } .line.error { color: var(--error); } .line.warning { color: var(--warning); } .empty-logs { color: var(--text-tertiary); font-style: italic; } `, ]; render(): TemplateResult { const logs = this.activeTab === 'daemon' ? this.daemonLogs : this.systemLogs; return html`
this.switchTab('daemon')} >Daemon
this.switchTab('system')} >System
${logs.length} lines
Auto-scroll
${logs.length === 0 ? html`
No logs
` : logs.map(log => html`
${log}
`) }
`; } private getLogLevel(log: string): string { const lower = log.toLowerCase(); if (lower.includes('error') || lower.includes('fail') || lower.includes('fatal')) { return 'error'; } if (lower.includes('warn')) { return 'warning'; } return ''; } private switchTab(tab: 'daemon' | 'system'): void { this.activeTab = tab; if (this.autoScroll) { this.scrollToBottom(); } } private toggleAutoScroll(): void { this.autoScroll = !this.autoScroll; if (this.autoScroll) { this.scrollToBottom(); } } private handleScroll(): void { const terminal = this.shadowRoot?.getElementById('terminal'); if (terminal) { const isAtBottom = terminal.scrollHeight - terminal.scrollTop <= terminal.clientHeight + 50; if (!isAtBottom && this.autoScroll) { this.autoScroll = false; } } } updated(changedProperties: Map): void { super.updated(changedProperties); if ((changedProperties.has('daemonLogs') || changedProperties.has('systemLogs')) && this.autoScroll) { this.scrollToBottom(); } } private scrollToBottom(): void { requestAnimationFrame(() => { const terminal = this.shadowRoot?.getElementById('terminal'); if (terminal) { terminal.scrollTop = terminal.scrollHeight; } }); } }