308 lines
8.9 KiB
TypeScript
308 lines
8.9 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,
|
|
type TemplateResult,
|
|
} from '@design.estate/dees-element';
|
|
import { type IStatsTile } from '@design.estate/dees-catalog';
|
|
|
|
@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`
|
|
h2 {
|
|
margin: 32px 0 16px 0;
|
|
font-size: 24px;
|
|
font-weight: 600;
|
|
color: ${cssManager.bdTheme('#333', '#ccc')};
|
|
}
|
|
|
|
.chartGrid {
|
|
display: grid;
|
|
grid-template-columns: repeat(2, 1fr);
|
|
grid-gap: 16px;
|
|
margin-top: 32px;
|
|
}
|
|
|
|
.loadingMessage {
|
|
text-align: center;
|
|
padding: 40px;
|
|
color: ${cssManager.bdTheme('#666', '#999')};
|
|
}
|
|
|
|
.errorMessage {
|
|
background-color: ${cssManager.bdTheme('#fee', '#4a1f1f')};
|
|
border: 1px solid ${cssManager.bdTheme('#fcc', '#6a2f2f')};
|
|
border-radius: 4px;
|
|
padding: 16px;
|
|
color: ${cssManager.bdTheme('#c00', '#ff6666')};
|
|
margin: 16px 0;
|
|
}
|
|
|
|
dees-statsgrid {
|
|
margin-bottom: 32px;
|
|
}
|
|
`,
|
|
];
|
|
|
|
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`
|
|
${this.renderServerStats()}
|
|
|
|
${this.renderEmailStats()}
|
|
|
|
${this.renderDnsStats()}
|
|
|
|
<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);
|
|
const secs = Math.floor(seconds % 60);
|
|
|
|
if (days > 0) {
|
|
return `${days}d ${hours}h ${minutes}m ${secs}s`;
|
|
} else if (hours > 0) {
|
|
return `${hours}h ${minutes}m ${secs}s`;
|
|
} else if (minutes > 0) {
|
|
return `${minutes}m ${secs}s`;
|
|
} else {
|
|
return `${secs}s`;
|
|
}
|
|
}
|
|
|
|
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 renderServerStats(): TemplateResult {
|
|
if (!this.statsState.serverStats) return html``;
|
|
|
|
const cpuUsage = Math.round(this.statsState.serverStats.cpuUsage.user);
|
|
const memoryUsage = this.statsState.serverStats.memoryUsage.actualUsagePercentage !== undefined
|
|
? Math.round(this.statsState.serverStats.memoryUsage.actualUsagePercentage)
|
|
: Math.round((this.statsState.serverStats.memoryUsage.heapUsed / this.statsState.serverStats.memoryUsage.heapTotal) * 100);
|
|
|
|
const tiles: IStatsTile[] = [
|
|
{
|
|
id: 'status',
|
|
title: 'Server Status',
|
|
value: this.statsState.serverStats.uptime ? 'Online' : 'Offline',
|
|
type: 'text',
|
|
icon: 'server',
|
|
color: this.statsState.serverStats.uptime ? '#22c55e' : '#ef4444',
|
|
description: `Uptime: ${this.formatUptime(this.statsState.serverStats.uptime)}`,
|
|
},
|
|
{
|
|
id: 'connections',
|
|
title: 'Active Connections',
|
|
value: this.statsState.serverStats.activeConnections,
|
|
type: 'number',
|
|
icon: 'networkWired',
|
|
color: '#3b82f6',
|
|
description: `Total: ${this.statsState.serverStats.totalConnections}`,
|
|
},
|
|
{
|
|
id: 'cpu',
|
|
title: 'CPU Usage',
|
|
value: cpuUsage,
|
|
type: 'gauge',
|
|
icon: 'microchip',
|
|
gaugeOptions: {
|
|
min: 0,
|
|
max: 100,
|
|
thresholds: [
|
|
{ value: 0, color: '#22c55e' },
|
|
{ value: 60, color: '#f59e0b' },
|
|
{ value: 80, color: '#ef4444' },
|
|
],
|
|
},
|
|
},
|
|
{
|
|
id: 'memory',
|
|
title: 'Memory Usage',
|
|
value: memoryUsage,
|
|
type: 'percentage',
|
|
icon: 'memory',
|
|
color: memoryUsage > 80 ? '#ef4444' : memoryUsage > 60 ? '#f59e0b' : '#22c55e',
|
|
description: this.statsState.serverStats.memoryUsage.actualUsageBytes !== undefined && this.statsState.serverStats.memoryUsage.maxMemoryMB !== undefined
|
|
? `${this.formatBytes(this.statsState.serverStats.memoryUsage.actualUsageBytes)} / ${this.formatBytes(this.statsState.serverStats.memoryUsage.maxMemoryMB * 1024 * 1024)}`
|
|
: `${this.formatBytes(this.statsState.serverStats.memoryUsage.rss)} / ${this.formatBytes(this.statsState.serverStats.memoryUsage.heapTotal)}`,
|
|
},
|
|
];
|
|
|
|
return html`
|
|
<dees-statsgrid
|
|
.tiles=${tiles}
|
|
.gridActions=${[
|
|
{
|
|
name: 'Refresh',
|
|
iconName: 'arrowsRotate',
|
|
action: async () => {
|
|
await appstate.statsStatePart.dispatchAction(appstate.fetchAllStatsAction, null);
|
|
},
|
|
},
|
|
]}
|
|
></dees-statsgrid>
|
|
`;
|
|
}
|
|
|
|
private renderEmailStats(): TemplateResult {
|
|
if (!this.statsState.emailStats) return html``;
|
|
|
|
const deliveryRate = this.statsState.emailStats.deliveryRate || 0;
|
|
const bounceRate = this.statsState.emailStats.bounceRate || 0;
|
|
|
|
const tiles: IStatsTile[] = [
|
|
{
|
|
id: 'sent',
|
|
title: 'Emails Sent',
|
|
value: this.statsState.emailStats.sent,
|
|
type: 'number',
|
|
icon: 'paperPlane',
|
|
color: '#22c55e',
|
|
description: `Delivery rate: ${(deliveryRate * 100).toFixed(1)}%`,
|
|
},
|
|
{
|
|
id: 'received',
|
|
title: 'Emails Received',
|
|
value: this.statsState.emailStats.received,
|
|
type: 'number',
|
|
icon: 'envelope',
|
|
color: '#3b82f6',
|
|
},
|
|
{
|
|
id: 'queued',
|
|
title: 'Queued',
|
|
value: this.statsState.emailStats.queued,
|
|
type: 'number',
|
|
icon: 'clock',
|
|
color: '#f59e0b',
|
|
description: 'Pending delivery',
|
|
},
|
|
{
|
|
id: 'failed',
|
|
title: 'Failed',
|
|
value: this.statsState.emailStats.failed,
|
|
type: 'number',
|
|
icon: 'triangleExclamation',
|
|
color: '#ef4444',
|
|
description: `Bounce rate: ${(bounceRate * 100).toFixed(1)}%`,
|
|
},
|
|
];
|
|
|
|
return html`
|
|
<h2>Email Statistics</h2>
|
|
<dees-statsgrid .tiles=${tiles}></dees-statsgrid>
|
|
`;
|
|
}
|
|
|
|
private renderDnsStats(): TemplateResult {
|
|
if (!this.statsState.dnsStats) return html``;
|
|
|
|
const cacheHitRate = Math.round(this.statsState.dnsStats.cacheHitRate * 100);
|
|
|
|
const tiles: IStatsTile[] = [
|
|
{
|
|
id: 'queries',
|
|
title: 'DNS Queries',
|
|
value: this.statsState.dnsStats.totalQueries,
|
|
type: 'number',
|
|
icon: 'globe',
|
|
color: '#3b82f6',
|
|
description: 'Total queries handled',
|
|
},
|
|
{
|
|
id: 'cacheRate',
|
|
title: 'Cache Hit Rate',
|
|
value: cacheHitRate,
|
|
type: 'percentage',
|
|
icon: 'database',
|
|
color: cacheHitRate > 80 ? '#22c55e' : cacheHitRate > 60 ? '#f59e0b' : '#ef4444',
|
|
description: `${this.statsState.dnsStats.cacheHits} hits / ${this.statsState.dnsStats.cacheMisses} misses`,
|
|
},
|
|
{
|
|
id: 'domains',
|
|
title: 'Active Domains',
|
|
value: this.statsState.dnsStats.activeDomains,
|
|
type: 'number',
|
|
icon: 'sitemap',
|
|
color: '#8b5cf6',
|
|
},
|
|
{
|
|
id: 'responseTime',
|
|
title: 'Avg Response Time',
|
|
value: this.statsState.dnsStats.averageResponseTime.toFixed(1),
|
|
unit: 'ms',
|
|
type: 'number',
|
|
icon: 'clockRotateLeft',
|
|
color: this.statsState.dnsStats.averageResponseTime < 50 ? '#22c55e' : '#f59e0b',
|
|
},
|
|
];
|
|
|
|
return html`
|
|
<h2>DNS Statistics</h2>
|
|
<dees-statsgrid .tiles=${tiles}></dees-statsgrid>
|
|
`;
|
|
}
|
|
} |