feat(dees-chart-log): Enhance log component with realistic log simulation and improved UI controls
This commit is contained in:
@ -2,3 +2,27 @@
|
|||||||
* Give a short rundown of components and a few points abputspecific features on each.
|
* Give a short rundown of components and a few points abputspecific features on each.
|
||||||
* Try to list all components in a summary.
|
* Try to list all components in a summary.
|
||||||
* Then list all components with a short description.
|
* Then list all components with a short description.
|
||||||
|
|
||||||
|
## Chart Components
|
||||||
|
|
||||||
|
### dees-chart-area
|
||||||
|
- Fully functional area chart component using ApexCharts
|
||||||
|
- Displays time-series data with gradient fills
|
||||||
|
- Responsive with ResizeObserver
|
||||||
|
- Demo shows CPU and memory usage metrics
|
||||||
|
|
||||||
|
### dees-chart-log
|
||||||
|
- Server log viewer component (not a chart despite the name)
|
||||||
|
- Terminal-style interface with monospace font
|
||||||
|
- Supports log levels: debug, info, warn, error, success
|
||||||
|
- Features:
|
||||||
|
- Auto-scroll toggle
|
||||||
|
- Clear logs button
|
||||||
|
- Colored log levels
|
||||||
|
- Timestamp with milliseconds
|
||||||
|
- Source labels for log entries
|
||||||
|
- Maximum 1000 entries (configurable)
|
||||||
|
- Light/dark theme support
|
||||||
|
- Demo includes realistic server log simulation
|
||||||
|
- Note: In demos, buttons use `@clicked` event (not `@click`)
|
||||||
|
- Demo uses global reference to access log element (window.__demoLogElement)
|
@ -1,6 +1,123 @@
|
|||||||
import { html } from '@design.estate/dees-element';
|
import { html } from '@design.estate/dees-element';
|
||||||
|
|
||||||
export const demoFunc = () => {
|
export const demoFunc = () => {
|
||||||
|
let intervalId: number;
|
||||||
|
|
||||||
|
const serverSources = ['Server', 'Database', 'API', 'Auth', 'Cache', 'Queue', 'WebSocket', 'Scheduler'];
|
||||||
|
|
||||||
|
const logTemplates = {
|
||||||
|
debug: [
|
||||||
|
'Loading module: {{module}}',
|
||||||
|
'Cache hit for key: {{key}}',
|
||||||
|
'SQL query executed in {{time}}ms',
|
||||||
|
'Request headers: {{headers}}',
|
||||||
|
'Environment variable loaded: {{var}}',
|
||||||
|
],
|
||||||
|
info: [
|
||||||
|
'Request received: {{method}} {{path}}',
|
||||||
|
'User {{userId}} authenticated successfully',
|
||||||
|
'Processing job {{jobId}} from queue',
|
||||||
|
'Scheduled task "{{task}}" started',
|
||||||
|
'WebSocket connection established from {{ip}}',
|
||||||
|
],
|
||||||
|
warn: [
|
||||||
|
'Slow query detected: {{query}} ({{time}}ms)',
|
||||||
|
'Memory usage at {{percent}}%',
|
||||||
|
'Rate limit approaching for IP {{ip}}',
|
||||||
|
'Deprecated API endpoint called: {{endpoint}}',
|
||||||
|
'Certificate expires in {{days}} days',
|
||||||
|
],
|
||||||
|
error: [
|
||||||
|
'Database connection lost: {{error}}',
|
||||||
|
'Failed to process request: {{error}}',
|
||||||
|
'Authentication failed for user {{user}}',
|
||||||
|
'File not found: {{path}}',
|
||||||
|
'Service unavailable: {{service}}',
|
||||||
|
],
|
||||||
|
success: [
|
||||||
|
'Server started successfully on port {{port}}',
|
||||||
|
'Database migration completed',
|
||||||
|
'Backup completed: {{size}} MB',
|
||||||
|
'SSL certificate renewed',
|
||||||
|
'Health check passed: all systems operational',
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateRandomLog = () => {
|
||||||
|
const logElement = (window as any).__demoLogElement;
|
||||||
|
if (!logElement) {
|
||||||
|
console.warn('Log element not ready yet');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const levels: Array<'debug' | 'info' | 'warn' | 'error' | 'success'> = ['debug', 'info', 'warn', 'error', 'success'];
|
||||||
|
const weights = [0.2, 0.5, 0.15, 0.1, 0.05]; // Weighted probability
|
||||||
|
|
||||||
|
const random = Math.random();
|
||||||
|
let cumulative = 0;
|
||||||
|
let level: typeof levels[0] = 'info';
|
||||||
|
|
||||||
|
for (let i = 0; i < weights.length; i++) {
|
||||||
|
cumulative += weights[i];
|
||||||
|
if (random < cumulative) {
|
||||||
|
level = levels[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const source = serverSources[Math.floor(Math.random() * serverSources.length)];
|
||||||
|
const templates = logTemplates[level];
|
||||||
|
const template = templates[Math.floor(Math.random() * templates.length)];
|
||||||
|
|
||||||
|
// Replace placeholders with random values
|
||||||
|
const message = template
|
||||||
|
.replace('{{module}}', ['express', 'mongoose', 'redis', 'socket.io'][Math.floor(Math.random() * 4)])
|
||||||
|
.replace('{{key}}', 'user:' + Math.floor(Math.random() * 1000))
|
||||||
|
.replace('{{time}}', String(Math.floor(Math.random() * 500) + 50))
|
||||||
|
.replace('{{headers}}', 'Content-Type: application/json, Authorization: Bearer ...')
|
||||||
|
.replace('{{var}}', ['NODE_ENV', 'DATABASE_URL', 'API_KEY', 'PORT'][Math.floor(Math.random() * 4)])
|
||||||
|
.replace('{{method}}', ['GET', 'POST', 'PUT', 'DELETE'][Math.floor(Math.random() * 4)])
|
||||||
|
.replace('{{path}}', ['/api/users', '/api/auth/login', '/api/products', '/health'][Math.floor(Math.random() * 4)])
|
||||||
|
.replace('{{userId}}', String(Math.floor(Math.random() * 10000)))
|
||||||
|
.replace('{{jobId}}', 'job_' + Math.random().toString(36).substring(2, 11))
|
||||||
|
.replace('{{task}}', ['cleanup', 'backup', 'report-generation', 'cache-refresh'][Math.floor(Math.random() * 4)])
|
||||||
|
.replace('{{ip}}', `192.168.1.${Math.floor(Math.random() * 255)}`)
|
||||||
|
.replace('{{query}}', 'SELECT * FROM users WHERE ...')
|
||||||
|
.replace('{{percent}}', String(Math.floor(Math.random() * 30) + 70))
|
||||||
|
.replace('{{endpoint}}', '/api/v1/legacy')
|
||||||
|
.replace('{{days}}', String(Math.floor(Math.random() * 30) + 1))
|
||||||
|
.replace('{{error}}', ['ECONNREFUSED', 'ETIMEDOUT', 'ENOTFOUND'][Math.floor(Math.random() * 3)])
|
||||||
|
.replace('{{user}}', 'user_' + Math.floor(Math.random() * 1000))
|
||||||
|
.replace('{{service}}', ['Redis', 'MongoDB', 'ElasticSearch'][Math.floor(Math.random() * 3)])
|
||||||
|
.replace('{{port}}', String(3000 + Math.floor(Math.random() * 10)))
|
||||||
|
.replace('{{size}}', String(Math.floor(Math.random() * 500) + 100));
|
||||||
|
|
||||||
|
logElement.addLog(level, message, source);
|
||||||
|
};
|
||||||
|
|
||||||
|
const startSimulation = () => {
|
||||||
|
if (!intervalId) {
|
||||||
|
// Generate logs at random intervals between 500ms and 2500ms
|
||||||
|
const scheduleNext = () => {
|
||||||
|
generateRandomLog();
|
||||||
|
const nextDelay = Math.random() * 2000 + 500;
|
||||||
|
intervalId = window.setTimeout(() => {
|
||||||
|
if (intervalId) {
|
||||||
|
scheduleNext();
|
||||||
|
}
|
||||||
|
}, nextDelay);
|
||||||
|
};
|
||||||
|
scheduleNext();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const stopSimulation = () => {
|
||||||
|
if (intervalId) {
|
||||||
|
window.clearTimeout(intervalId);
|
||||||
|
intervalId = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<style>
|
<style>
|
||||||
.demoBox {
|
.demoBox {
|
||||||
@ -9,11 +126,31 @@ export const demoFunc = () => {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 40px;
|
padding: 40px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
.controls {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
.info {
|
||||||
|
color: #888;
|
||||||
|
font-size: 12px;
|
||||||
|
font-family: 'Geist Sans', sans-serif;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<div class="demoBox">
|
<div class="demoBox">
|
||||||
|
<div class="controls">
|
||||||
|
<dees-button @clicked=${() => generateRandomLog()}>Add Single Log</dees-button>
|
||||||
|
<dees-button @clicked=${() => startSimulation()}>Start Simulation</dees-button>
|
||||||
|
<dees-button @clicked=${() => stopSimulation()}>Stop Simulation</dees-button>
|
||||||
|
</div>
|
||||||
|
<div class="info">Simulating realistic server logs with various levels and sources</div>
|
||||||
<dees-chart-log
|
<dees-chart-log
|
||||||
.label=${'Event Log'}
|
.label=${'Production Server Logs'}
|
||||||
></dees-chart-log>
|
></dees-chart-log>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
@ -13,7 +13,6 @@ import {
|
|||||||
import * as domtools from '@design.estate/dees-domtools';
|
import * as domtools from '@design.estate/dees-domtools';
|
||||||
import { demoFunc } from './dees-chart-log.demo.js';
|
import { demoFunc } from './dees-chart-log.demo.js';
|
||||||
|
|
||||||
import ApexCharts from 'apexcharts';
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
@ -21,69 +20,308 @@ declare global {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ILogEntry {
|
||||||
|
timestamp: string;
|
||||||
|
level: 'debug' | 'info' | 'warn' | 'error' | 'success';
|
||||||
|
message: string;
|
||||||
|
source?: string;
|
||||||
|
}
|
||||||
|
|
||||||
@customElement('dees-chart-log')
|
@customElement('dees-chart-log')
|
||||||
export class DeesChartLog extends DeesElement {
|
export class DeesChartLog extends DeesElement {
|
||||||
public static demo = demoFunc;
|
public static demo = demoFunc;
|
||||||
|
|
||||||
// instance
|
|
||||||
@state()
|
|
||||||
public chart: ApexCharts;
|
|
||||||
|
|
||||||
@property()
|
@property()
|
||||||
public label: string = 'Untitled Chart';
|
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() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
domtools.elementBasic.setup();
|
domtools.elementBasic.setup();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static styles = [
|
public static styles = [
|
||||||
cssManager.defaultStyles,
|
cssManager.defaultStyles,
|
||||||
css`
|
css`
|
||||||
:host {
|
:host {
|
||||||
font-family: 'Geist Sans', sans-serif;
|
font-family: 'Geist Mono', 'Consolas', 'Monaco', monospace;
|
||||||
color: #ccc;
|
color: #ccc;
|
||||||
font-weight: 600;
|
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
line-height: 1.4;
|
||||||
}
|
}
|
||||||
.mainbox {
|
.mainbox {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 400px;
|
height: 400px;
|
||||||
background: #222;
|
background: ${cssManager.bdTheme('#f8f9fa', '#0a0a0a')};
|
||||||
|
border: 1px solid ${cssManager.bdTheme('#dee2e6', '#333')};
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding: 32px 16px 16px 0px;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chartTitle {
|
.header {
|
||||||
position: absolute;
|
background: ${cssManager.bdTheme('#e9ecef', '#1a1a1a')};
|
||||||
top: 0;
|
padding: 8px 16px;
|
||||||
left: 0;
|
border-bottom: 1px solid ${cssManager.bdTheme('#dee2e6', '#333')};
|
||||||
width: 100%;
|
display: flex;
|
||||||
text-align: center;
|
justify-content: space-between;
|
||||||
padding-top: 16px;
|
align-items: center;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
.chartContainer {
|
|
||||||
position: relative;
|
.title {
|
||||||
width: 100%;
|
font-weight: 600;
|
||||||
|
color: ${cssManager.bdTheme('#212529', '#fff')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-button {
|
||||||
|
background: ${cssManager.bdTheme('#e9ecef', '#2a2a2a')};
|
||||||
|
border: 1px solid ${cssManager.bdTheme('#ced4da', '#444')};
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 4px 8px;
|
||||||
|
color: ${cssManager.bdTheme('#495057', '#ccc')};
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 11px;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-button:hover {
|
||||||
|
background: ${cssManager.bdTheme('#dee2e6', '#3a3a3a')};
|
||||||
|
border-color: ${cssManager.bdTheme('#adb5bd', '#555')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-button.active {
|
||||||
|
background: ${cssManager.bdTheme('#007bff', '#4a4a4a')};
|
||||||
|
color: ${cssManager.bdTheme('#fff', '#fff')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.logContainer {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
padding: 8px 16px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logEntry {
|
||||||
|
margin-bottom: 2px;
|
||||||
|
display: flex;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timestamp {
|
||||||
|
color: ${cssManager.bdTheme('#6c757d', '#666')};
|
||||||
|
margin-right: 8px;
|
||||||
|
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('#6c757d', '#999')};
|
||||||
|
background: ${cssManager.bdTheme('rgba(108, 117, 125, 0.1)', '#333')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.level.info {
|
||||||
|
color: ${cssManager.bdTheme('#0066cc', '#4a9eff')};
|
||||||
|
background: ${cssManager.bdTheme('rgba(0, 102, 204, 0.1)', 'rgba(74, 158, 255, 0.1)')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.level.warn {
|
||||||
|
color: ${cssManager.bdTheme('#ff8800', '#ffb84a')};
|
||||||
|
background: ${cssManager.bdTheme('rgba(255, 136, 0, 0.1)', 'rgba(255, 184, 74, 0.1)')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.level.error {
|
||||||
|
color: ${cssManager.bdTheme('#dc3545', '#ff4a4a')};
|
||||||
|
background: ${cssManager.bdTheme('rgba(220, 53, 69, 0.1)', 'rgba(255, 74, 74, 0.1)')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.level.success {
|
||||||
|
color: ${cssManager.bdTheme('#28a745', '#4aff88')};
|
||||||
|
background: ${cssManager.bdTheme('rgba(40, 167, 69, 0.1)', 'rgba(74, 255, 136, 0.1)')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.source {
|
||||||
|
color: ${cssManager.bdTheme('#6c757d', '#888')};
|
||||||
|
margin-right: 8px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message {
|
||||||
|
color: ${cssManager.bdTheme('#212529', '#ddd')};
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
color: ${cssManager.bdTheme('#6c757d', '#666')};
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom scrollbar */
|
||||||
|
.logContainer::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logContainer::-webkit-scrollbar-track {
|
||||||
|
background: ${cssManager.bdTheme('#e9ecef', '#1a1a1a')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.logContainer::-webkit-scrollbar-thumb {
|
||||||
|
background: ${cssManager.bdTheme('#adb5bd', '#444')};
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logContainer::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: ${cssManager.bdTheme('#6c757d', '#555')};
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
|
|
||||||
public render(): TemplateResult {
|
public render(): TemplateResult {
|
||||||
return html` <div class="mainbox">
|
return html`
|
||||||
<div class="chartTitle">${this.label}</div>
|
<div class="mainbox">
|
||||||
<div class="chartContainer"></div>
|
<div class="header">
|
||||||
</div> `;
|
<div class="title">${this.label}</div>
|
||||||
|
<div class="controls">
|
||||||
|
<button
|
||||||
|
class="control-button ${this.autoScroll ? 'active' : ''}"
|
||||||
|
@click=${() => { this.autoScroll = !this.autoScroll; }}
|
||||||
|
>
|
||||||
|
Auto Scroll
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="control-button"
|
||||||
|
@click=${() => { this.clearLogs(); }}
|
||||||
|
>
|
||||||
|
Clear
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="logContainer">
|
||||||
|
${this.logEntries.length === 0
|
||||||
|
? html`<div class="empty-state">No logs to display</div>`
|
||||||
|
: this.logEntries.map(entry => this.renderLogEntry(entry))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
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`
|
||||||
|
<div class="logEntry">
|
||||||
|
<span class="timestamp">${timestamp}</span>
|
||||||
|
<span class="level ${entry.level}">${entry.level}</span>
|
||||||
|
${entry.source ? html`<span class="source">[${entry.source}]</span>` : ''}
|
||||||
|
<span class="message">${entry.message}</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async firstUpdated() {
|
public async firstUpdated() {
|
||||||
const domtoolsInstance = await this.domtoolsPromise;
|
const domtoolsInstance = 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();
|
||||||
|
|
||||||
|
// For demo purposes, store reference globally
|
||||||
|
if ((window as any).__demoLogElement === undefined) {
|
||||||
|
(window as any).__demoLogElement = this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async updateLog() {
|
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]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user