2025-06-08 07:04:35 +00:00
|
|
|
import * as plugins from '../../plugins.js';
|
|
|
|
import type { OpsServer } from '../classes.opsserver.js';
|
|
|
|
import * as interfaces from '../../../ts_interfaces/index.js';
|
2025-06-09 16:03:27 +00:00
|
|
|
import { MetricsManager } from '../../monitoring/index.js';
|
2025-06-08 07:04:35 +00:00
|
|
|
|
|
|
|
export class StatsHandler {
|
|
|
|
public typedrouter = new plugins.typedrequest.TypedRouter();
|
|
|
|
|
|
|
|
constructor(private opsServerRef: OpsServer) {
|
|
|
|
// Add this handler's router to the parent
|
|
|
|
this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter);
|
|
|
|
this.registerHandlers();
|
|
|
|
}
|
|
|
|
|
|
|
|
private registerHandlers(): void {
|
|
|
|
// Server Statistics Handler
|
|
|
|
this.typedrouter.addTypedHandler(
|
|
|
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetServerStatistics>(
|
|
|
|
'getServerStatistics',
|
|
|
|
async (dataArg, toolsArg) => {
|
|
|
|
const stats = await this.collectServerStats();
|
|
|
|
return {
|
|
|
|
stats: {
|
|
|
|
uptime: stats.uptime,
|
|
|
|
startTime: Date.now() - (stats.uptime * 1000),
|
|
|
|
memoryUsage: stats.memoryUsage,
|
|
|
|
cpuUsage: stats.cpuUsage,
|
|
|
|
activeConnections: stats.activeConnections,
|
|
|
|
totalConnections: stats.totalConnections,
|
|
|
|
},
|
|
|
|
history: dataArg.includeHistory ? stats.history : undefined,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
)
|
|
|
|
);
|
|
|
|
|
|
|
|
// Email Statistics Handler
|
|
|
|
this.typedrouter.addTypedHandler(
|
|
|
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetEmailStatistics>(
|
|
|
|
'getEmailStatistics',
|
|
|
|
async (dataArg, toolsArg) => {
|
|
|
|
const emailServer = this.opsServerRef.dcRouterRef.emailServer;
|
|
|
|
if (!emailServer) {
|
|
|
|
return {
|
|
|
|
stats: {
|
|
|
|
sent: 0,
|
|
|
|
received: 0,
|
|
|
|
bounced: 0,
|
|
|
|
queued: 0,
|
|
|
|
failed: 0,
|
|
|
|
averageDeliveryTime: 0,
|
|
|
|
deliveryRate: 0,
|
|
|
|
bounceRate: 0,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
const stats = await this.collectEmailStats();
|
|
|
|
return {
|
|
|
|
stats: {
|
|
|
|
sent: stats.sentToday,
|
|
|
|
received: stats.receivedToday,
|
|
|
|
bounced: Math.floor(stats.sentToday * stats.bounceRate / 100),
|
|
|
|
queued: stats.queueSize,
|
|
|
|
failed: 0,
|
|
|
|
averageDeliveryTime: 0,
|
|
|
|
deliveryRate: stats.deliveryRate,
|
|
|
|
bounceRate: stats.bounceRate,
|
|
|
|
},
|
|
|
|
domainBreakdown: dataArg.includeDetails ? stats.domainBreakdown : undefined,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
)
|
|
|
|
);
|
|
|
|
|
|
|
|
// DNS Statistics Handler
|
|
|
|
this.typedrouter.addTypedHandler(
|
|
|
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetDnsStatistics>(
|
|
|
|
'getDnsStatistics',
|
|
|
|
async (dataArg, toolsArg) => {
|
|
|
|
const dnsServer = this.opsServerRef.dcRouterRef.dnsServer;
|
|
|
|
if (!dnsServer) {
|
|
|
|
return {
|
|
|
|
stats: {
|
|
|
|
totalQueries: 0,
|
|
|
|
cacheHits: 0,
|
|
|
|
cacheMisses: 0,
|
|
|
|
cacheHitRate: 0,
|
|
|
|
activeDomains: 0,
|
|
|
|
averageResponseTime: 0,
|
|
|
|
queryTypes: {},
|
|
|
|
},
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
const stats = await this.collectDnsStats();
|
|
|
|
return {
|
|
|
|
stats: {
|
|
|
|
totalQueries: stats.totalQueries,
|
|
|
|
cacheHits: stats.cacheHits,
|
|
|
|
cacheMisses: stats.cacheMisses,
|
|
|
|
cacheHitRate: stats.cacheHitRate,
|
|
|
|
activeDomains: stats.topDomains.length,
|
|
|
|
averageResponseTime: 0,
|
|
|
|
queryTypes: stats.queryTypes,
|
|
|
|
},
|
|
|
|
domainBreakdown: dataArg.includeQueryTypes ? stats.domainBreakdown : undefined,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
)
|
|
|
|
);
|
|
|
|
|
|
|
|
// Queue Status Handler
|
|
|
|
this.typedrouter.addTypedHandler(
|
|
|
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetQueueStatus>(
|
|
|
|
'getQueueStatus',
|
|
|
|
async (dataArg, toolsArg) => {
|
|
|
|
const emailServer = this.opsServerRef.dcRouterRef.emailServer;
|
|
|
|
const queues: interfaces.data.IQueueStatus[] = [];
|
|
|
|
|
|
|
|
if (emailServer) {
|
|
|
|
const status = await this.getQueueStatus();
|
|
|
|
queues.push({
|
|
|
|
name: dataArg.queueName || 'default',
|
|
|
|
size: status.pending,
|
|
|
|
processing: status.active,
|
|
|
|
failed: status.failed,
|
|
|
|
retrying: status.retrying,
|
|
|
|
averageProcessingTime: 0,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
queues,
|
|
|
|
totalItems: queues.reduce((sum, q) => sum + q.size + q.processing + q.failed + q.retrying, 0),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
)
|
|
|
|
);
|
|
|
|
|
|
|
|
// Health Status Handler
|
|
|
|
this.typedrouter.addTypedHandler(
|
|
|
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetHealthStatus>(
|
|
|
|
'getHealthStatus',
|
|
|
|
async (dataArg, toolsArg) => {
|
|
|
|
const health = await this.checkHealthStatus();
|
|
|
|
return {
|
|
|
|
health: {
|
|
|
|
healthy: health.healthy,
|
|
|
|
uptime: process.uptime(),
|
|
|
|
services: health.services.reduce((acc, service) => {
|
|
|
|
acc[service.name] = {
|
|
|
|
status: service.status,
|
|
|
|
message: service.message,
|
|
|
|
lastCheck: Date.now(),
|
|
|
|
};
|
|
|
|
return acc;
|
|
|
|
}, {} as any),
|
|
|
|
version: '2.12.0', // TODO: Get from package.json
|
|
|
|
},
|
|
|
|
};
|
|
|
|
}
|
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
private async collectServerStats(): Promise<{
|
|
|
|
uptime: number;
|
|
|
|
cpuUsage: {
|
|
|
|
user: number;
|
|
|
|
system: number;
|
|
|
|
};
|
|
|
|
memoryUsage: interfaces.data.IServerStats['memoryUsage'];
|
|
|
|
requestsPerSecond: number;
|
|
|
|
activeConnections: number;
|
|
|
|
totalConnections: number;
|
|
|
|
history: Array<{
|
|
|
|
timestamp: number;
|
|
|
|
value: number;
|
|
|
|
}>;
|
|
|
|
}> {
|
2025-06-09 16:03:27 +00:00
|
|
|
// Get metrics from MetricsManager if available
|
|
|
|
if (this.opsServerRef.dcRouterRef.metricsManager) {
|
|
|
|
const serverStats = await this.opsServerRef.dcRouterRef.metricsManager.getServerStats();
|
|
|
|
return {
|
|
|
|
uptime: serverStats.uptime,
|
|
|
|
cpuUsage: serverStats.cpuUsage,
|
|
|
|
memoryUsage: serverStats.memoryUsage,
|
|
|
|
requestsPerSecond: serverStats.requestsPerSecond,
|
|
|
|
activeConnections: serverStats.activeConnections,
|
|
|
|
totalConnections: serverStats.totalConnections,
|
|
|
|
history: [], // TODO: Implement history tracking
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fallback to basic stats if MetricsManager not available
|
2025-06-08 07:04:35 +00:00
|
|
|
const uptime = process.uptime();
|
|
|
|
const memUsage = process.memoryUsage();
|
|
|
|
const cpuUsage = plugins.os.loadavg()[0] * 100 / plugins.os.cpus().length;
|
|
|
|
|
|
|
|
return {
|
|
|
|
uptime,
|
|
|
|
cpuUsage: {
|
2025-06-09 16:03:27 +00:00
|
|
|
user: cpuUsage * 0.7,
|
|
|
|
system: cpuUsage * 0.3,
|
2025-06-08 07:04:35 +00:00
|
|
|
},
|
|
|
|
memoryUsage: {
|
|
|
|
heapUsed: memUsage.heapUsed,
|
|
|
|
heapTotal: memUsage.heapTotal,
|
|
|
|
external: memUsage.external,
|
|
|
|
rss: memUsage.rss,
|
|
|
|
},
|
2025-06-09 16:03:27 +00:00
|
|
|
requestsPerSecond: 0,
|
|
|
|
activeConnections: 0,
|
|
|
|
totalConnections: 0,
|
|
|
|
history: [],
|
2025-06-08 07:04:35 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
private async collectEmailStats(): Promise<{
|
|
|
|
sentToday: number;
|
|
|
|
receivedToday: number;
|
|
|
|
bounceRate: number;
|
|
|
|
deliveryRate: number;
|
|
|
|
queueSize: number;
|
|
|
|
domainBreakdown?: { [domain: string]: interfaces.data.IEmailStats };
|
|
|
|
}> {
|
2025-06-09 16:03:27 +00:00
|
|
|
// Get metrics from MetricsManager if available
|
|
|
|
if (this.opsServerRef.dcRouterRef.metricsManager) {
|
|
|
|
const emailStats = await this.opsServerRef.dcRouterRef.metricsManager.getEmailStats();
|
|
|
|
return {
|
|
|
|
sentToday: emailStats.sentToday,
|
|
|
|
receivedToday: emailStats.receivedToday,
|
|
|
|
bounceRate: emailStats.bounceRate,
|
|
|
|
deliveryRate: emailStats.deliveryRate,
|
|
|
|
queueSize: emailStats.queueSize,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fallback if MetricsManager not available
|
2025-06-08 07:04:35 +00:00
|
|
|
return {
|
|
|
|
sentToday: 0,
|
|
|
|
receivedToday: 0,
|
|
|
|
bounceRate: 0,
|
|
|
|
deliveryRate: 100,
|
|
|
|
queueSize: 0,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
private async collectDnsStats(): Promise<{
|
|
|
|
queriesPerSecond: number;
|
|
|
|
totalQueries: number;
|
|
|
|
cacheHits: number;
|
|
|
|
cacheMisses: number;
|
|
|
|
cacheHitRate: number;
|
|
|
|
topDomains: Array<{
|
|
|
|
domain: string;
|
|
|
|
count: number;
|
|
|
|
}>;
|
|
|
|
queryTypes: { [key: string]: number };
|
|
|
|
domainBreakdown?: { [domain: string]: interfaces.data.IDnsStats };
|
|
|
|
}> {
|
2025-06-09 16:03:27 +00:00
|
|
|
// Get metrics from MetricsManager if available
|
|
|
|
if (this.opsServerRef.dcRouterRef.metricsManager) {
|
|
|
|
const dnsStats = await this.opsServerRef.dcRouterRef.metricsManager.getDnsStats();
|
|
|
|
return {
|
|
|
|
queriesPerSecond: dnsStats.queriesPerSecond,
|
|
|
|
totalQueries: dnsStats.totalQueries,
|
|
|
|
cacheHits: dnsStats.cacheHits,
|
|
|
|
cacheMisses: dnsStats.cacheMisses,
|
|
|
|
cacheHitRate: dnsStats.cacheHitRate,
|
|
|
|
topDomains: dnsStats.topDomains,
|
|
|
|
queryTypes: dnsStats.queryTypes,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fallback if MetricsManager not available
|
2025-06-08 07:04:35 +00:00
|
|
|
return {
|
|
|
|
queriesPerSecond: 0,
|
|
|
|
totalQueries: 0,
|
|
|
|
cacheHits: 0,
|
|
|
|
cacheMisses: 0,
|
|
|
|
cacheHitRate: 0,
|
|
|
|
topDomains: [],
|
|
|
|
queryTypes: {},
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
private async getQueueStatus(): Promise<{
|
|
|
|
pending: number;
|
|
|
|
active: number;
|
|
|
|
failed: number;
|
|
|
|
retrying: number;
|
|
|
|
items: Array<{
|
|
|
|
id: string;
|
|
|
|
recipient: string;
|
|
|
|
subject: string;
|
|
|
|
status: string;
|
|
|
|
attempts: number;
|
|
|
|
nextRetry?: number;
|
|
|
|
}>;
|
|
|
|
}> {
|
|
|
|
// TODO: Implement actual queue status collection
|
|
|
|
return {
|
|
|
|
pending: 0,
|
|
|
|
active: 0,
|
|
|
|
failed: 0,
|
|
|
|
retrying: 0,
|
|
|
|
items: [],
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
private async checkHealthStatus(): Promise<{
|
|
|
|
healthy: boolean;
|
|
|
|
services: Array<{
|
|
|
|
name: string;
|
|
|
|
status: 'healthy' | 'degraded' | 'unhealthy';
|
|
|
|
message?: string;
|
|
|
|
}>;
|
|
|
|
checks: Array<{
|
|
|
|
name: string;
|
|
|
|
passed: boolean;
|
|
|
|
message?: string;
|
|
|
|
}>;
|
|
|
|
}> {
|
|
|
|
const services: Array<{
|
|
|
|
name: string;
|
|
|
|
status: 'healthy' | 'degraded' | 'unhealthy';
|
|
|
|
message?: string;
|
|
|
|
}> = [];
|
|
|
|
|
|
|
|
// Check HTTP Proxy
|
|
|
|
if (this.opsServerRef.dcRouterRef.smartProxy) {
|
|
|
|
services.push({
|
|
|
|
name: 'HTTP/HTTPS Proxy',
|
|
|
|
status: 'healthy',
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check Email Server
|
|
|
|
if (this.opsServerRef.dcRouterRef.emailServer) {
|
|
|
|
services.push({
|
|
|
|
name: 'Email Server',
|
|
|
|
status: 'healthy',
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check DNS Server
|
|
|
|
if (this.opsServerRef.dcRouterRef.dnsServer) {
|
|
|
|
services.push({
|
|
|
|
name: 'DNS Server',
|
|
|
|
status: 'healthy',
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check OpsServer
|
|
|
|
services.push({
|
|
|
|
name: 'OpsServer',
|
|
|
|
status: 'healthy',
|
|
|
|
});
|
|
|
|
|
|
|
|
const healthy = services.every(s => s.status === 'healthy');
|
|
|
|
|
|
|
|
return {
|
|
|
|
healthy,
|
|
|
|
services,
|
|
|
|
checks: [
|
|
|
|
{
|
|
|
|
name: 'Memory Usage',
|
|
|
|
passed: process.memoryUsage().heapUsed < (plugins.os.totalmem() * 0.9),
|
|
|
|
message: 'Memory usage within limits',
|
|
|
|
},
|
|
|
|
],
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|