Files
dcrouter/ts_web/elements/ops-view-stats.ts

299 lines
9.5 KiB
TypeScript
Raw Normal View History

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-stats')
export class OpsViewStats extends DeesElement {
@state()
private statsState: appstate.IStatsState = {
serverStats: null,
emailStats: null,
dnsStats: null,
securityMetrics: null,
lastUpdated: 0,
isLoading: false,
error: null,
};
@state()
private uiState: appstate.IUiState = {
activeView: 'dashboard',
sidebarCollapsed: false,
autoRefresh: true,
refreshInterval: 30000,
theme: 'light',
};
constructor() {
super();
const statsSubscription = appstate.statsStatePart
.select((stateArg) => stateArg)
.subscribe((statsState) => {
this.statsState = statsState;
});
this.rxSubscriptions.push(statsSubscription);
const uiSubscription = appstate.uiStatePart
.select((stateArg) => stateArg)
.subscribe((uiState) => {
this.uiState = uiState;
});
this.rxSubscriptions.push(uiSubscription);
}
public static styles = [
cssManager.defaultStyles,
shared.viewHostCss,
css`
.controls {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
padding: 16px;
background: #f8f9fa;
border-radius: 8px;
}
.refreshButton {
display: flex;
align-items: center;
gap: 8px;
}
.lastUpdated {
font-size: 14px;
color: #666;
}
.statsSection {
margin-bottom: 48px;
}
.sectionTitle {
font-size: 24px;
font-weight: 600;
margin-bottom: 24px;
color: #333;
}
.metricsGrid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 16px;
}
.metricCard {
background: white;
border: 1px solid #e9ecef;
border-radius: 8px;
padding: 20px;
transition: all 0.2s ease;
}
.metricCard:hover {
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
transform: translateY(-2px);
}
.metricLabel {
font-size: 14px;
color: #666;
margin-bottom: 8px;
}
.metricValue {
font-size: 28px;
font-weight: 700;
color: #2196F3;
}
.metricUnit {
font-size: 16px;
color: #999;
margin-left: 4px;
}
.chartContainer {
background: white;
border: 1px solid #e9ecef;
border-radius: 8px;
padding: 24px;
margin-top: 24px;
}
`,
];
public render() {
return html`
<ops-sectionheading>Statistics</ops-sectionheading>
<div class="controls">
<div class="refreshButton">
<dees-button
@click=${() => appstate.statsStatePart.dispatchAction(appstate.fetchAllStatsAction, null)}
.disabled=${this.statsState.isLoading}
>
${this.statsState.isLoading ? html`<dees-spinner size="small"></dees-spinner>` : 'Refresh'}
</dees-button>
<dees-button
@click=${() => appstate.uiStatePart.dispatchAction(appstate.toggleAutoRefreshAction, null)}
.type=${this.uiState.autoRefresh ? 'highlighted' : 'normal'}
>
Auto-refresh: ${this.uiState.autoRefresh ? 'ON' : 'OFF'}
</dees-button>
</div>
<div class="lastUpdated">
${this.statsState.lastUpdated ? html`
Last updated: ${new Date(this.statsState.lastUpdated).toLocaleTimeString()}
` : ''}
</div>
</div>
${this.statsState.serverStats ? html`
<div class="statsSection">
<h2 class="sectionTitle">Server Metrics</h2>
<div class="metricsGrid">
<div class="metricCard">
<div class="metricLabel">Uptime</div>
<div class="metricValue">${this.formatUptime(this.statsState.serverStats.uptime)}</div>
</div>
<div class="metricCard">
<div class="metricLabel">CPU Usage</div>
<div class="metricValue">${Math.round((this.statsState.serverStats.cpuUsage.user + this.statsState.serverStats.cpuUsage.system) / 2)}<span class="metricUnit">%</span></div>
</div>
<div class="metricCard">
<div class="metricLabel">Memory Used</div>
<div class="metricValue">${this.formatBytes(this.statsState.serverStats.memoryUsage.heapUsed)}</div>
</div>
<div class="metricCard">
<div class="metricLabel">Active Connections</div>
<div class="metricValue">${this.statsState.serverStats.activeConnections}</div>
</div>
</div>
<div class="chartContainer">
<dees-chart-area
.label=${'Server Performance (Last 24 Hours)'}
.data=${[]}
></dees-chart-area>
</div>
</div>
` : ''}
${this.statsState.emailStats ? html`
<div class="statsSection">
<h2 class="sectionTitle">Email Statistics</h2>
<dees-table
.heading1=${'Email Metrics'}
.heading2=${'Current statistics for email processing'}
.data=${[
{ metric: 'Total Sent', value: this.statsState.emailStats.sent, unit: 'emails' },
{ metric: 'Total Received', value: this.statsState.emailStats.received, unit: 'emails' },
{ metric: 'Failed Deliveries', value: this.statsState.emailStats.failed, unit: 'emails' },
{ metric: 'Currently Queued', value: this.statsState.emailStats.queued, unit: 'emails' },
{ metric: 'Average Delivery Time', value: this.statsState.emailStats.averageDeliveryTime, unit: 'ms' },
{ metric: 'Delivery Rate', value: `${Math.round(this.statsState.emailStats.deliveryRate * 100)}`, unit: '%' },
]}
.displayFunction=${(item) => ({
Metric: item.metric,
Value: `${item.value} ${item.unit}`,
})}
></dees-table>
</div>
` : ''}
${this.statsState.dnsStats ? html`
<div class="statsSection">
<h2 class="sectionTitle">DNS Statistics</h2>
<div class="metricsGrid">
<div class="metricCard">
<div class="metricLabel">Total Queries</div>
<div class="metricValue">${this.formatNumber(this.statsState.dnsStats.totalQueries)}</div>
</div>
<div class="metricCard">
<div class="metricLabel">Cache Hit Rate</div>
<div class="metricValue">${Math.round(this.statsState.dnsStats.cacheHitRate * 100)}<span class="metricUnit">%</span></div>
</div>
<div class="metricCard">
<div class="metricLabel">Average Response Time</div>
<div class="metricValue">${this.statsState.dnsStats.averageResponseTime}<span class="metricUnit">ms</span></div>
</div>
<div class="metricCard">
<div class="metricLabel">Domains Configured</div>
<div class="metricValue">${this.statsState.dnsStats.activeDomains}</div>
</div>
</div>
</div>
` : ''}
${this.statsState.securityMetrics ? html`
<div class="statsSection">
<h2 class="sectionTitle">Security Metrics</h2>
<dees-table
.heading1=${'Security Events'}
.heading2=${'Recent security-related activities'}
.data=${[
{ metric: 'Blocked IPs', value: this.statsState.securityMetrics.blockedIPs.length, severity: 'high' },
{ metric: 'Failed Authentications', value: this.statsState.securityMetrics.authenticationFailures, severity: 'medium' },
{ metric: 'Spam Detected', value: this.statsState.securityMetrics.spamDetected, severity: 'low' },
{ metric: 'Spam Detected', value: this.statsState.securityMetrics.spamDetected, severity: 'medium' },
{ metric: 'Malware Detected', value: this.statsState.securityMetrics.malwareDetected, severity: 'high' },
{ metric: 'Phishing Detected', value: this.statsState.securityMetrics.phishingDetected, severity: 'high' },
]}
.displayFunction=${(item) => ({
'Security Metric': item.metric,
'Count': item.value,
'Severity': item.severity,
})}
></dees-table>
</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`;
} 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]}`;
}
private formatNumber(num: number): string {
if (num >= 1000000) {
return `${(num / 1000000).toFixed(1)}M`;
} else if (num >= 1000) {
return `${(num / 1000).toFixed(1)}K`;
}
return num.toString();
}
}