feat(opsserver): add RADIUS and VPN metrics to combined ops stats and overview dashboards, and stream live log buffer entries in follow mode

This commit is contained in:
2026-04-03 14:11:17 +00:00
parent b3296c6522
commit f6a9e344e5
10 changed files with 237 additions and 41 deletions

View File

@@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@serve.zone/dcrouter',
version: '12.6.6',
version: '12.7.0',
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
}

View File

@@ -15,6 +15,8 @@ export interface IStatsState {
emailStats: interfaces.data.IEmailStats | null;
dnsStats: interfaces.data.IDnsStats | null;
securityMetrics: interfaces.data.ISecurityMetrics | null;
radiusStats: interfaces.data.IRadiusStats | null;
vpnStats: interfaces.data.IVpnStats | null;
lastUpdated: number;
isLoading: boolean;
error: string | null;
@@ -91,6 +93,8 @@ export const statsStatePart = await appState.getStatePart<IStatsState>(
emailStats: null,
dnsStats: null,
securityMetrics: null,
radiusStats: null,
vpnStats: null,
lastUpdated: 0,
isLoading: false,
error: null,
@@ -319,6 +323,8 @@ export const fetchAllStatsAction = statsStatePart.createAction(async (statePartA
dns: true,
security: true,
network: false, // Network is fetched separately for the network view
radius: true,
vpn: true,
},
});
@@ -328,6 +334,8 @@ export const fetchAllStatsAction = statsStatePart.createAction(async (statePartA
emailStats: combinedResponse.metrics.email || currentState.emailStats,
dnsStats: combinedResponse.metrics.dns || currentState.dnsStats,
securityMetrics: combinedResponse.metrics.security || currentState.securityMetrics,
radiusStats: combinedResponse.metrics.radius || currentState.radiusStats,
vpnStats: combinedResponse.metrics.vpn || currentState.vpnStats,
lastUpdated: Date.now(),
isLoading: false,
error: null,
@@ -1781,6 +1789,8 @@ async function dispatchCombinedRefreshActionInner() {
dns: true,
security: true,
network: currentView === 'network', // Only fetch network if on network view
radius: true,
vpn: true,
},
});
@@ -1792,6 +1802,8 @@ async function dispatchCombinedRefreshActionInner() {
emailStats: combinedResponse.metrics.email || currentStatsState.emailStats,
dnsStats: combinedResponse.metrics.dns || currentStatsState.dnsStats,
securityMetrics: combinedResponse.metrics.security || currentStatsState.securityMetrics,
radiusStats: combinedResponse.metrics.radius || currentStatsState.radiusStats,
vpnStats: combinedResponse.metrics.vpn || currentStatsState.vpnStats,
lastUpdated: Date.now(),
isLoading: false,
error: null,

View File

@@ -21,6 +21,8 @@ export class OpsViewOverview extends DeesElement {
emailStats: null,
dnsStats: null,
securityMetrics: null,
radiusStats: null,
vpnStats: null,
lastUpdated: 0,
isLoading: false,
error: null,
@@ -117,6 +119,10 @@ export class OpsViewOverview extends DeesElement {
${this.renderDnsStats()}
${this.renderRadiusStats()}
${this.renderVpnStats()}
<div class="chartGrid">
<dees-chart-area
.label=${'Email Traffic (24h)'}
@@ -378,6 +384,97 @@ export class OpsViewOverview extends DeesElement {
`;
}
private renderRadiusStats(): TemplateResult {
if (!this.statsState.radiusStats) return html``;
const stats = this.statsState.radiusStats;
const authTotal = stats.authRequests || 0;
const acceptRate = authTotal > 0 ? ((stats.authAccepts / authTotal) * 100).toFixed(1) : '0.0';
const tiles: IStatsTile[] = [
{
id: 'radiusStatus',
title: 'RADIUS Status',
value: stats.running ? 'Running' : 'Stopped',
type: 'text',
icon: 'lucide:ShieldCheck',
color: stats.running ? '#22c55e' : '#ef4444',
description: stats.running ? `Uptime: ${this.formatUptime(stats.uptime / 1000)}` : undefined,
},
{
id: 'authRequests',
title: 'Auth Requests',
value: stats.authRequests,
type: 'number',
icon: 'lucide:KeyRound',
color: '#3b82f6',
description: `Accept rate: ${acceptRate}% (${stats.authAccepts} / ${stats.authRejects} rejected)`,
},
{
id: 'activeSessions',
title: 'Active Sessions',
value: stats.activeSessions,
type: 'number',
icon: 'lucide:Users',
color: '#8b5cf6',
},
{
id: 'radiusTraffic',
title: 'Data Transfer',
value: this.formatBytes(stats.totalInputBytes + stats.totalOutputBytes),
type: 'text',
icon: 'lucide:ArrowLeftRight',
color: '#f59e0b',
description: `In: ${this.formatBytes(stats.totalInputBytes)} / Out: ${this.formatBytes(stats.totalOutputBytes)}`,
},
];
return html`
<h2>RADIUS Statistics</h2>
<dees-statsgrid .tiles=${tiles}></dees-statsgrid>
`;
}
private renderVpnStats(): TemplateResult {
if (!this.statsState.vpnStats) return html``;
const stats = this.statsState.vpnStats;
const tiles: IStatsTile[] = [
{
id: 'vpnStatus',
title: 'VPN Status',
value: stats.running ? 'Running' : 'Stopped',
type: 'text',
icon: 'lucide:Shield',
color: stats.running ? '#22c55e' : '#ef4444',
description: `Subnet: ${stats.subnet}`,
},
{
id: 'connectedClients',
title: 'Connected Clients',
value: stats.connectedClients,
type: 'number',
icon: 'lucide:Wifi',
color: '#3b82f6',
description: `${stats.registeredClients} registered`,
},
{
id: 'wgPort',
title: 'WireGuard Port',
value: stats.wgListenPort,
type: 'number',
icon: 'lucide:Network',
color: '#8b5cf6',
},
];
return html`
<h2>VPN Statistics</h2>
<dees-statsgrid .tiles=${tiles}></dees-statsgrid>
`;
}
// --- Chart data helpers ---
private getRecentEventEntries(): Array<{ timestamp: string; level: 'debug' | 'info' | 'warn' | 'error' | 'success'; message: string; source?: string }> {

View File

@@ -20,6 +20,8 @@ export class OpsViewSecurity extends DeesElement {
emailStats: null,
dnsStats: null,
securityMetrics: null,
radiusStats: null,
vpnStats: null,
lastUpdated: 0,
isLoading: false,
error: null,