import { DeesElement, css, cssManager, customElement, html, property, type TemplateResult, } from '@design.estate/dees-element'; import * as domtools from '@design.estate/dees-domtools'; import { demoFunc } from './dees-chart-log.demo.js'; declare global { interface HTMLElementTagNameMap { 'dees-chart-log': DeesChartLog; } } export interface ILogEntry { timestamp: string; level: 'debug' | 'info' | 'warn' | 'error' | 'success'; message: string; source?: string; } @customElement('dees-chart-log') export class DeesChartLog extends DeesElement { public static demo = demoFunc; @property() public label: string = 'Server Logs'; @property({ type: Array }) public logEntries: ILogEntry[] = []; @property({ type: Boolean }) public autoScroll: boolean = true; @property({ type: Number }) public maxEntries: number = 1000; private logContainer: HTMLDivElement; constructor() { super(); domtools.elementBasic.setup(); } public static styles = [ cssManager.defaultStyles, css` :host { font-family: 'SF Mono', 'Monaco', 'Consolas', 'Liberation Mono', 'Courier New', monospace; color: ${cssManager.bdTheme('hsl(0 0% 3.9%)', 'hsl(0 0% 98%)')}; font-size: 12px; line-height: 1.5; } .mainbox { position: relative; width: 100%; height: 400px; background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 3.9%)')}; border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')}; border-radius: 8px; display: flex; flex-direction: column; overflow: hidden; } .header { background: ${cssManager.bdTheme('hsl(0 0% 97%)', 'hsl(0 0% 7%)')}; padding: 12px 16px; border-bottom: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')}; display: flex; justify-content: space-between; align-items: center; flex-shrink: 0; } .title { font-weight: 500; font-size: 14px; color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 95%)')}; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; } .controls { display: flex; gap: 8px; } .control-button { background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 14.9%)')}; border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')}; border-radius: 6px; padding: 6px 12px; color: ${cssManager.bdTheme('hsl(0 0% 45.1%)', 'hsl(0 0% 63.9%)')}; cursor: pointer; font-size: 12px; font-weight: 500; transition: all 0.15s; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; } .control-button:hover { background: ${cssManager.bdTheme('hsl(0 0% 95.1%)', 'hsl(0 0% 14.9%)')}; border-color: ${cssManager.bdTheme('hsl(0 0% 79.8%)', 'hsl(0 0% 20.9%)')}; color: ${cssManager.bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 93.9%)')}; } .control-button.active { background: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 93.9%)')}; color: ${cssManager.bdTheme('hsl(0 0% 98%)', 'hsl(0 0% 3.9%)')}; } .logContainer { flex: 1; overflow-y: auto; overflow-x: hidden; padding: 16px; font-size: 12px; } .logEntry { margin-bottom: 4px; display: flex; white-space: pre-wrap; word-break: break-all; font-variant-numeric: tabular-nums; } .timestamp { color: ${cssManager.bdTheme('hsl(0 0% 63.9%)', 'hsl(0 0% 45.1%)')}; margin-right: 12px; flex-shrink: 0; } .level { margin-right: 8px; padding: 0 6px; border-radius: 3px; font-weight: 600; text-transform: uppercase; font-size: 10px; flex-shrink: 0; } .level.debug { color: ${cssManager.bdTheme('hsl(0 0% 45.1%)', 'hsl(0 0% 63.9%)')}; background: ${cssManager.bdTheme('hsl(0 0% 45.1% / 0.1)', 'hsl(0 0% 63.9% / 0.1)')}; } .level.info { color: ${cssManager.bdTheme('hsl(222.2 47.4% 51.2%)', 'hsl(217.2 91.2% 59.8%)')}; background: ${cssManager.bdTheme('hsl(222.2 47.4% 51.2% / 0.1)', 'hsl(217.2 91.2% 59.8% / 0.1)')}; } .level.warn { color: ${cssManager.bdTheme('hsl(25 95% 53%)', 'hsl(25 95% 63%)')}; background: ${cssManager.bdTheme('hsl(25 95% 53% / 0.1)', 'hsl(25 95% 63% / 0.1)')}; } .level.error { color: ${cssManager.bdTheme('hsl(0 84.2% 60.2%)', 'hsl(0 72.2% 50.6%)')}; background: ${cssManager.bdTheme('hsl(0 84.2% 60.2% / 0.1)', 'hsl(0 72.2% 50.6% / 0.1)')}; } .level.success { color: ${cssManager.bdTheme('hsl(142.1 76.2% 36.3%)', 'hsl(142.1 70.6% 45.3%)')}; background: ${cssManager.bdTheme('hsl(142.1 76.2% 36.3% / 0.1)', 'hsl(142.1 70.6% 45.3% / 0.1)')}; } .source { color: ${cssManager.bdTheme('hsl(0 0% 45.1%)', 'hsl(0 0% 63.9%)')}; margin-right: 8px; flex-shrink: 0; } .message { color: ${cssManager.bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 90%)')}; flex: 1; } .empty-state { display: flex; align-items: center; justify-content: center; height: 100%; color: ${cssManager.bdTheme('hsl(0 0% 45.1%)', 'hsl(0 0% 63.9%)')}; font-style: italic; } /* Custom scrollbar */ .logContainer::-webkit-scrollbar { width: 8px; } .logContainer::-webkit-scrollbar-track { background: ${cssManager.bdTheme('hsl(0 0% 95%)', 'hsl(0 0% 10%)')}; } .logContainer::-webkit-scrollbar-thumb { background: ${cssManager.bdTheme('hsl(0 0% 70%)', 'hsl(0 0% 30%)')}; border-radius: 4px; } .logContainer::-webkit-scrollbar-thumb:hover { background: ${cssManager.bdTheme('hsl(0 0% 60%)', 'hsl(0 0% 40%)')}; } `, ]; public render(): TemplateResult { return html`
${this.label}
${this.logEntries.length === 0 ? html`
No logs to display
` : this.logEntries.map(entry => this.renderLogEntry(entry)) }
`; } private renderLogEntry(entry: ILogEntry): TemplateResult { const timestamp = new Date(entry.timestamp).toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit', fractionalSecondDigits: 3 }); return html`
${timestamp} ${entry.level} ${entry.source ? html`[${entry.source}]` : ''} ${entry.message}
`; } public async firstUpdated() { await this.domtoolsPromise; this.logContainer = this.shadowRoot.querySelector('.logContainer'); // Initialize with demo server logs const demoLogs: ILogEntry[] = [ { timestamp: new Date().toISOString(), level: 'info', message: 'Server started on port 3000', source: 'Server' }, { timestamp: new Date().toISOString(), level: 'debug', message: 'Loading configuration from /etc/app/config.json', source: 'Config' }, { timestamp: new Date().toISOString(), level: 'info', message: 'Connected to MongoDB at mongodb://localhost:27017', source: 'Database' }, { timestamp: new Date().toISOString(), level: 'success', message: 'Database connection established successfully', source: 'Database' }, { timestamp: new Date().toISOString(), level: 'warn', message: 'No SSL certificate found, using self-signed certificate', source: 'Security' }, { timestamp: new Date().toISOString(), level: 'info', message: 'API routes initialized: GET /api/users, POST /api/users, DELETE /api/users/:id', source: 'Router' }, { timestamp: new Date().toISOString(), level: 'debug', message: 'Middleware stack: cors, bodyParser, authentication, errorHandler', source: 'Middleware' }, { timestamp: new Date().toISOString(), level: 'info', message: 'WebSocket server listening on ws://localhost:3001', source: 'WebSocket' }, ]; this.logEntries = demoLogs; this.scrollToBottom(); } public async updateLog(entries?: ILogEntry[]) { if (entries) { // Add new entries this.logEntries = [...this.logEntries, ...entries]; // Trim if exceeds max entries if (this.logEntries.length > this.maxEntries) { this.logEntries = this.logEntries.slice(-this.maxEntries); } // Trigger re-render this.requestUpdate(); // Auto-scroll if enabled await this.updateComplete; if (this.autoScroll) { this.scrollToBottom(); } } } public clearLogs() { this.logEntries = []; this.requestUpdate(); } private scrollToBottom() { if (this.logContainer) { this.logContainer.scrollTop = this.logContainer.scrollHeight; } } public addLog(level: ILogEntry['level'], message: string, source?: string) { const newEntry: ILogEntry = { timestamp: new Date().toISOString(), level, message, source }; this.updateLog([newEntry]); } }