207 lines
5.2 KiB
TypeScript
207 lines
5.2 KiB
TypeScript
![]() |
import * as plugins from '../plugins.js';
|
||
|
import * as shared from './shared/index.js';
|
||
|
import * as appstate from '../appstate.js';
|
||
|
|
||
|
import {
|
||
|
DeesElement,
|
||
|
customElement,
|
||
|
html,
|
||
|
state,
|
||
|
css,
|
||
|
cssManager,
|
||
|
} from '@design.estate/dees-element';
|
||
|
|
||
|
@customElement('ops-view-logs')
|
||
|
export class OpsViewLogs extends DeesElement {
|
||
|
@state()
|
||
|
private logState: appstate.ILogState = {
|
||
|
recentLogs: [],
|
||
|
isStreaming: false,
|
||
|
filters: {},
|
||
|
};
|
||
|
|
||
|
constructor() {
|
||
|
super();
|
||
|
const subscription = appstate.logStatePart
|
||
|
.select((stateArg) => stateArg)
|
||
|
.subscribe((logState) => {
|
||
|
this.logState = logState;
|
||
|
});
|
||
|
this.rxSubscriptions.push(subscription);
|
||
|
}
|
||
|
|
||
|
public static styles = [
|
||
|
cssManager.defaultStyles,
|
||
|
shared.viewHostCss,
|
||
|
css`
|
||
|
.controls {
|
||
|
display: flex;
|
||
|
gap: 16px;
|
||
|
margin-bottom: 24px;
|
||
|
flex-wrap: wrap;
|
||
|
}
|
||
|
|
||
|
.filterGroup {
|
||
|
display: flex;
|
||
|
align-items: center;
|
||
|
gap: 8px;
|
||
|
}
|
||
|
|
||
|
.logContainer {
|
||
|
background: #1e1e1e;
|
||
|
border-radius: 8px;
|
||
|
padding: 16px;
|
||
|
max-height: 600px;
|
||
|
overflow-y: auto;
|
||
|
font-family: 'Consolas', 'Monaco', monospace;
|
||
|
font-size: 13px;
|
||
|
}
|
||
|
|
||
|
.logEntry {
|
||
|
margin-bottom: 8px;
|
||
|
line-height: 1.5;
|
||
|
}
|
||
|
|
||
|
.logTimestamp {
|
||
|
color: #7a7a7a;
|
||
|
margin-right: 8px;
|
||
|
}
|
||
|
|
||
|
.logLevel {
|
||
|
font-weight: bold;
|
||
|
margin-right: 8px;
|
||
|
padding: 2px 6px;
|
||
|
border-radius: 3px;
|
||
|
font-size: 11px;
|
||
|
}
|
||
|
|
||
|
.logLevel.debug {
|
||
|
color: #6a9955;
|
||
|
background: rgba(106, 153, 85, 0.1);
|
||
|
}
|
||
|
.logLevel.info {
|
||
|
color: #569cd6;
|
||
|
background: rgba(86, 156, 214, 0.1);
|
||
|
}
|
||
|
.logLevel.warn {
|
||
|
color: #ce9178;
|
||
|
background: rgba(206, 145, 120, 0.1);
|
||
|
}
|
||
|
.logLevel.error {
|
||
|
color: #f44747;
|
||
|
background: rgba(244, 71, 71, 0.1);
|
||
|
}
|
||
|
|
||
|
.logCategory {
|
||
|
color: #c586c0;
|
||
|
margin-right: 8px;
|
||
|
}
|
||
|
|
||
|
.logMessage {
|
||
|
color: #d4d4d4;
|
||
|
}
|
||
|
|
||
|
.noLogs {
|
||
|
color: #7a7a7a;
|
||
|
text-align: center;
|
||
|
padding: 40px;
|
||
|
}
|
||
|
`,
|
||
|
];
|
||
|
|
||
|
public render() {
|
||
|
return html`
|
||
|
<ops-sectionheading>Logs</ops-sectionheading>
|
||
|
|
||
|
<div class="controls">
|
||
|
<div class="filterGroup">
|
||
|
<dees-button
|
||
|
@click=${() => this.fetchLogs()}
|
||
|
>
|
||
|
Refresh Logs
|
||
|
</dees-button>
|
||
|
|
||
|
<dees-button
|
||
|
@click=${() => this.toggleStreaming()}
|
||
|
.type=${this.logState.isStreaming ? 'highlighted' : 'normal'}
|
||
|
>
|
||
|
${this.logState.isStreaming ? 'Stop Streaming' : 'Start Streaming'}
|
||
|
</dees-button>
|
||
|
</div>
|
||
|
|
||
|
<div class="filterGroup">
|
||
|
<label>Level:</label>
|
||
|
<dees-input-dropdown
|
||
|
.options=${['all', 'debug', 'info', 'warn', 'error']}
|
||
|
.selectedOption=${'all'}
|
||
|
@selectedOption=${(e) => this.updateFilter('level', e.detail)}
|
||
|
></dees-input-dropdown>
|
||
|
</div>
|
||
|
|
||
|
<div class="filterGroup">
|
||
|
<label>Category:</label>
|
||
|
<dees-input-dropdown
|
||
|
.options=${['all', 'smtp', 'dns', 'security', 'system', 'email']}
|
||
|
.selectedOption=${'all'}
|
||
|
@selectedOption=${(e) => this.updateFilter('category', e.detail)}
|
||
|
></dees-input-dropdown>
|
||
|
</div>
|
||
|
|
||
|
<div class="filterGroup">
|
||
|
<label>Limit:</label>
|
||
|
<dees-input-dropdown
|
||
|
.options=${['50', '100', '200', '500']}
|
||
|
.selectedOption=${'100'}
|
||
|
@selectedOption=${(e) => this.updateFilter('limit', e.detail)}
|
||
|
></dees-input-dropdown>
|
||
|
</div>
|
||
|
</div>
|
||
|
|
||
|
<div class="logContainer">
|
||
|
${this.logState.recentLogs.length > 0 ?
|
||
|
this.logState.recentLogs.map(log => html`
|
||
|
<div class="logEntry">
|
||
|
<span class="logTimestamp">${new Date(log.timestamp).toLocaleTimeString()}</span>
|
||
|
<span class="logLevel ${log.level}">${log.level.toUpperCase()}</span>
|
||
|
<span class="logCategory">[${log.category}]</span>
|
||
|
<span class="logMessage">${log.message}</span>
|
||
|
</div>
|
||
|
`) : html`
|
||
|
<div class="noLogs">No logs to display</div>
|
||
|
`
|
||
|
}
|
||
|
</div>
|
||
|
`;
|
||
|
}
|
||
|
|
||
|
private async fetchLogs() {
|
||
|
const filters = this.getActiveFilters();
|
||
|
await appstate.logStatePart.dispatchAction(appstate.fetchRecentLogsAction, {
|
||
|
limit: filters.limit || 100,
|
||
|
level: filters.level as 'debug' | 'info' | 'warn' | 'error' | undefined,
|
||
|
category: filters.category as 'smtp' | 'dns' | 'security' | 'system' | 'email' | undefined,
|
||
|
});
|
||
|
}
|
||
|
|
||
|
private updateFilter(type: string, value: string) {
|
||
|
if (value === 'all') {
|
||
|
value = undefined;
|
||
|
}
|
||
|
|
||
|
// Update filters then fetch logs
|
||
|
this.fetchLogs();
|
||
|
}
|
||
|
|
||
|
private getActiveFilters() {
|
||
|
return {
|
||
|
level: this.logState.filters.level?.[0],
|
||
|
category: this.logState.filters.category?.[0],
|
||
|
limit: 100,
|
||
|
};
|
||
|
}
|
||
|
|
||
|
private toggleStreaming() {
|
||
|
// TODO: Implement log streaming with VirtualStream
|
||
|
console.log('Streaming toggle not yet implemented');
|
||
|
}
|
||
|
}
|