Files
dcrouter/ts_web/elements/ops-view-overview.ts
Juergen Kunz f1fb4c8495 feat: Add operations view components for logs, overview, security, and stats
- Implemented `ops-view-logs` for displaying and filtering logs with streaming capabilities.
- Created `ops-view-overview` to show server, email, DNS statistics, and charts.
- Developed `ops-view-security` for monitoring security metrics, blocked IPs, and authentication attempts.
- Added `ops-view-stats` to present comprehensive statistics on server, email, DNS, and security metrics.
- Introduced shared styles and components including `ops-sectionheading` for consistent UI.
2025-06-08 12:03:17 +00:00

222 lines
6.4 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-overview')
export class OpsViewOverview extends DeesElement {
@state()
private statsState: appstate.IStatsState = {
serverStats: null,
emailStats: null,
dnsStats: null,
securityMetrics: null,
lastUpdated: 0,
isLoading: false,
error: null,
};
constructor() {
super();
const subscription = appstate.statsStatePart
.select((stateArg) => stateArg)
.subscribe((statsState) => {
this.statsState = statsState;
});
this.rxSubscriptions.push(subscription);
}
public static styles = [
cssManager.defaultStyles,
shared.viewHostCss,
css`
.statsGrid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
grid-gap: 16px;
margin-bottom: 40px;
}
.statCard {
background: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 8px;
padding: 24px;
}
.statCard h3 {
margin: 0 0 16px 0;
font-size: 18px;
font-weight: 600;
color: #333;
}
.statValue {
font-size: 32px;
font-weight: 700;
color: #2196F3;
margin-bottom: 8px;
}
.statLabel {
font-size: 14px;
color: #666;
}
.chartGrid {
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-gap: 16px;
margin-top: 32px;
}
.loadingMessage {
text-align: center;
padding: 40px;
color: #666;
}
.errorMessage {
background-color: #fee;
border: 1px solid #fcc;
border-radius: 4px;
padding: 16px;
color: #c00;
margin: 16px 0;
}
`,
];
public render() {
return html`
<ops-sectionheading>Overview</ops-sectionheading>
${this.statsState.isLoading ? html`
<div class="loadingMessage">
<dees-spinner></dees-spinner>
<p>Loading statistics...</p>
</div>
` : this.statsState.error ? html`
<div class="errorMessage">
Error loading statistics: ${this.statsState.error}
</div>
` : html`
<div class="statsGrid">
${this.statsState.serverStats ? html`
<div class="statCard">
<h3>Server Status</h3>
<div class="statValue">${this.statsState.serverStats.uptime ? 'Online' : 'Offline'}</div>
<div class="statLabel">Uptime: ${this.formatUptime(this.statsState.serverStats.uptime)}</div>
</div>
<div class="statCard">
<h3>Connections</h3>
<div class="statValue">${this.statsState.serverStats.activeConnections}</div>
<div class="statLabel">Active connections</div>
</div>
<div class="statCard">
<h3>Memory Usage</h3>
<div class="statValue">${this.formatBytes(this.statsState.serverStats.memoryUsage.heapUsed)}</div>
<div class="statLabel">of ${this.formatBytes(this.statsState.serverStats.memoryUsage.heapTotal)}</div>
</div>
<div class="statCard">
<h3>CPU Usage</h3>
<div class="statValue">${Math.round((this.statsState.serverStats.cpuUsage.user + this.statsState.serverStats.cpuUsage.system) / 2)}%</div>
<div class="statLabel">Average load</div>
</div>
` : ''}
</div>
${this.statsState.emailStats ? html`
<h2>Email Statistics</h2>
<div class="statsGrid">
<div class="statCard">
<h3>Emails Sent</h3>
<div class="statValue">${this.statsState.emailStats.sent}</div>
<div class="statLabel">Total sent</div>
</div>
<div class="statCard">
<h3>Emails Received</h3>
<div class="statValue">${this.statsState.emailStats.received}</div>
<div class="statLabel">Total received</div>
</div>
<div class="statCard">
<h3>Failed Deliveries</h3>
<div class="statValue">${this.statsState.emailStats.failed}</div>
<div class="statLabel">Delivery failures</div>
</div>
<div class="statCard">
<h3>Queued</h3>
<div class="statValue">${this.statsState.emailStats.queued}</div>
<div class="statLabel">In queue</div>
</div>
</div>
` : ''}
${this.statsState.dnsStats ? html`
<h2>DNS Statistics</h2>
<div class="statsGrid">
<div class="statCard">
<h3>DNS Queries</h3>
<div class="statValue">${this.statsState.dnsStats.totalQueries}</div>
<div class="statLabel">Total queries handled</div>
</div>
<div class="statCard">
<h3>Cache Hit Rate</h3>
<div class="statValue">${Math.round(this.statsState.dnsStats.cacheHitRate * 100)}%</div>
<div class="statLabel">Cache efficiency</div>
</div>
</div>
` : ''}
<div class="chartGrid">
<dees-chart-area .label=${'Email Traffic (24h)'} .data=${[]}></dees-chart-area>
<dees-chart-area .label=${'DNS Queries (24h)'} .data=${[]}></dees-chart-area>
<dees-chart-log .label=${'Recent Events'} .data=${[]}></dees-chart-log>
<dees-chart-log .label=${'Security Alerts'} .data=${[]}></dees-chart-log>
</div>
`}
`;
}
private formatUptime(seconds: number): string {
const days = Math.floor(seconds / 86400);
const hours = Math.floor((seconds % 86400) / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
if (days > 0) {
return `${days}d ${hours}h ${minutes}m`;
} else if (hours > 0) {
return `${hours}h ${minutes}m`;
} else {
return `${minutes}m`;
}
}
private formatBytes(bytes: number): string {
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
let size = bytes;
let unitIndex = 0;
while (size >= 1024 && unitIndex < units.length - 1) {
size /= 1024;
unitIndex++;
}
return `${size.toFixed(1)} ${units[unitIndex]}`;
}
}