2024-02-05 10:07:49 +01:00
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 ;
}
}
2025-06-12 10:44:21 +00:00
export interface ILogEntry {
timestamp : string ;
level : 'debug' | 'info' | 'warn' | 'error' | 'success' ;
message : string ;
source? : string ;
}
2024-02-05 10:07:49 +01:00
@customElement ( 'dees-chart-log' )
export class DeesChartLog extends DeesElement {
public static demo = demoFunc ;
@property ( )
2025-06-12 10:44:21 +00:00
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 ;
2024-02-05 10:07:49 +01:00
constructor ( ) {
super ( ) ;
domtools . elementBasic . setup ( ) ;
2025-06-16 14:37:09 +00:00
2024-02-05 10:07:49 +01:00
}
public static styles = [
cssManager . defaultStyles ,
css `
: host {
2025-06-27 15:58:26 +00:00
font - family : 'SF Mono' , 'Monaco' , 'Consolas' , 'Liberation Mono' , 'Courier New' , monospace ;
color : $ { cssManager . bdTheme ( 'hsl(0 0% 3.9%)' , 'hsl(0 0% 98%)' ) } ;
2024-02-05 10:07:49 +01:00
font - size : 12px ;
2025-06-27 15:58:26 +00:00
line - height : 1.5 ;
2024-02-05 10:07:49 +01:00
}
. mainbox {
position : relative ;
width : 100 % ;
height : 400px ;
2025-06-27 15:58:26 +00:00
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%)' ) } ;
2024-02-05 10:07:49 +01:00
border - radius : 8px ;
2025-06-12 10:44:21 +00:00
display : flex ;
flex - direction : column ;
overflow : hidden ;
2024-02-05 10:07:49 +01:00
}
2025-06-12 10:44:21 +00:00
. header {
2025-06-27 15:58:26 +00:00
background : $ { cssManager . bdTheme ( 'hsl(0 0% 97%)' , 'hsl(0 0% 7%)' ) } ;
padding : 12px 16 px ;
border - bottom : 1px solid $ { cssManager . bdTheme ( 'hsl(0 0% 89.8%)' , 'hsl(0 0% 14.9%)' ) } ;
2025-06-12 10:44:21 +00:00
display : flex ;
justify - content : space - between ;
align - items : center ;
flex - shrink : 0 ;
2024-02-05 10:07:49 +01:00
}
2025-06-12 10:44:21 +00:00
. title {
2025-06-27 15:58:26 +00:00
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 ;
2025-06-12 10:44:21 +00:00
}
. controls {
display : flex ;
gap : 8px ;
}
. control - button {
2025-06-27 15:58:26 +00:00
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 12 px ;
color : $ { cssManager . bdTheme ( 'hsl(0 0% 45.1%)' , 'hsl(0 0% 63.9%)' ) } ;
2025-06-12 10:44:21 +00:00
cursor : pointer ;
2025-06-27 15:58:26 +00:00
font - size : 12px ;
font - weight : 500 ;
transition : all 0.15 s ;
font - family : - apple - system , BlinkMacSystemFont , 'Segoe UI' , sans - serif ;
2025-06-12 10:44:21 +00:00
}
. control - button :hover {
2025-06-27 15:58:26 +00:00
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%)' ) } ;
2025-06-12 10:44:21 +00:00
}
. control - button . active {
2025-06-27 15:58:26 +00:00
background : $ { cssManager . bdTheme ( 'hsl(0 0% 9%)' , 'hsl(0 0% 93.9%)' ) } ;
color : $ { cssManager . bdTheme ( 'hsl(0 0% 98%)' , 'hsl(0 0% 3.9%)' ) } ;
2025-06-12 10:44:21 +00:00
}
. logContainer {
flex : 1 ;
overflow - y : auto ;
overflow - x : hidden ;
2025-06-27 15:58:26 +00:00
padding : 16px ;
2025-06-12 10:44:21 +00:00
font - size : 12px ;
}
. logEntry {
2025-06-27 15:58:26 +00:00
margin - bottom : 4px ;
2025-06-12 10:44:21 +00:00
display : flex ;
white - space : pre - wrap ;
word - break : break - all ;
2025-06-27 15:58:26 +00:00
font - variant - numeric : tabular - nums ;
2025-06-12 10:44:21 +00:00
}
. timestamp {
2025-06-27 15:58:26 +00:00
color : $ { cssManager . bdTheme ( 'hsl(0 0% 63.9%)' , 'hsl(0 0% 45.1%)' ) } ;
margin - right : 12px ;
2025-06-12 10:44:21 +00:00
flex - shrink : 0 ;
}
. level {
margin - right : 8px ;
padding : 0 6 px ;
border - radius : 3px ;
font - weight : 600 ;
text - transform : uppercase ;
font - size : 10px ;
flex - shrink : 0 ;
}
. level . debug {
2025-06-27 15:58:26 +00:00
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)' ) } ;
2025-06-12 10:44:21 +00:00
}
. level . info {
2025-06-27 15:58:26 +00:00
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)' ) } ;
2025-06-12 10:44:21 +00:00
}
. level . warn {
2025-06-27 15:58:26 +00:00
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)' ) } ;
2025-06-12 10:44:21 +00:00
}
. level . error {
2025-06-27 15:58:26 +00:00
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)' ) } ;
2025-06-12 10:44:21 +00:00
}
. level . success {
2025-06-27 15:58:26 +00:00
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)' ) } ;
2025-06-12 10:44:21 +00:00
}
. source {
2025-06-27 15:58:26 +00:00
color : $ { cssManager . bdTheme ( 'hsl(0 0% 45.1%)' , 'hsl(0 0% 63.9%)' ) } ;
2025-06-12 10:44:21 +00:00
margin - right : 8px ;
flex - shrink : 0 ;
}
. message {
2025-06-27 15:58:26 +00:00
color : $ { cssManager . bdTheme ( 'hsl(0 0% 15%)' , 'hsl(0 0% 90%)' ) } ;
2025-06-12 10:44:21 +00:00
flex : 1 ;
}
. empty - state {
display : flex ;
align - items : center ;
justify - content : center ;
2024-02-05 10:07:49 +01:00
height : 100 % ;
2025-06-27 15:58:26 +00:00
color : $ { cssManager . bdTheme ( 'hsl(0 0% 45.1%)' , 'hsl(0 0% 63.9%)' ) } ;
2025-06-12 10:44:21 +00:00
font - style : italic ;
}
/* Custom scrollbar */
. logContainer : : - webkit - scrollbar {
width : 8px ;
}
. logContainer : : - webkit - scrollbar - track {
2025-06-27 15:58:26 +00:00
background : $ { cssManager . bdTheme ( 'hsl(0 0% 95%)' , 'hsl(0 0% 10%)' ) } ;
2025-06-12 10:44:21 +00:00
}
. logContainer : : - webkit - scrollbar - thumb {
2025-06-27 15:58:26 +00:00
background : $ { cssManager . bdTheme ( 'hsl(0 0% 70%)' , 'hsl(0 0% 30%)' ) } ;
2025-06-12 10:44:21 +00:00
border - radius : 4px ;
}
. logContainer : : - webkit - scrollbar - thumb :hover {
2025-06-27 15:58:26 +00:00
background : $ { cssManager . bdTheme ( 'hsl(0 0% 60%)' , 'hsl(0 0% 40%)' ) } ;
2024-02-05 10:07:49 +01:00
}
` ,
] ;
public render ( ) : TemplateResult {
2025-06-12 10:44:21 +00:00
return html `
< div class = "mainbox" >
< div class = "header" >
< 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 >
` ;
2024-02-05 10:07:49 +01:00
}
public async firstUpdated() {
2025-06-12 11:00:33 +00:00
await this . domtoolsPromise ;
2025-06-12 10:44:21 +00:00
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 ( ) ;
2024-02-05 10:07:49 +01:00
}
2025-06-12 10:44:21 +00:00
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 ] ) ;
2024-02-05 10:07:49 +01:00
}
}