feat(ops-server): implement TypedRouter integration and modular handler classes

This commit is contained in:
Juergen Kunz
2025-06-08 07:04:35 +00:00
parent ab19130904
commit 61778bdba8
10 changed files with 1188 additions and 14 deletions

View File

@ -70,14 +70,14 @@ ts_interfaces/
- **Health Check** - **Health Check**
- [x] `IReq_GetHealthStatus` - Service health monitoring - [x] `IReq_GetHealthStatus` - Service health monitoring
### Phase 2: Backend Implementation ### Phase 2: Backend Implementation
#### 2.1 Enhance OpsServer (`ts/opsserver/classes.opsserver.ts`) #### 2.1 Enhance OpsServer (`ts/opsserver/classes.opsserver.ts`)
- [ ] Add TypedRouter initialization - [x] Add TypedRouter initialization
- [ ] Use TypedServer's built-in typedrouter - [x] Use TypedServer's built-in typedrouter
- [ ] CORS is already handled by TypedServer - [x] CORS is already handled by TypedServer
- [ ] Add handler registration method - [x] Add handler registration method
```typescript ```typescript
// Example structure following cloudly pattern // Example structure following cloudly pattern
@ -122,15 +122,15 @@ TypedServer (built-in typedrouter at /typedrequest)
This allows clean separation of concerns while keeping all handlers accessible through the single `/typedrequest` endpoint. This allows clean separation of concerns while keeping all handlers accessible through the single `/typedrequest` endpoint.
#### 2.2 Create Handler Classes #### 2.2 Create Handler Classes
Create modular handlers in `ts/opsserver/handlers/`: Create modular handlers in `ts/opsserver/handlers/`:
- [ ] `stats.handler.ts` - Server and performance statistics - [x] `stats.handler.ts` - Server and performance statistics
- [ ] `email.handler.ts` - Email-related operations - [x] `security.handler.ts` - Security and reputation metrics
- [ ] `dns.handler.ts` - DNS management statistics - [x] `config.handler.ts` - Configuration management
- [ ] `security.handler.ts` - Security and reputation metrics - [x] `logs.handler.ts` - Log retrieval and streaming
- [ ] `config.handler.ts` - Configuration management - [x] `admin.handler.ts` - Authentication and session management
Each handler should: Each handler should:
- Have its own typedrouter that gets added to OpsServer's router - Have its own typedrouter that gets added to OpsServer's router
@ -298,10 +298,17 @@ Create modular components in `ts_web/elements/components/`:
- Added `@api.global/typedrequest-interfaces` dependency - Added `@api.global/typedrequest-interfaces` dependency
- All interfaces compile successfully - All interfaces compile successfully
- **Phase 2: Backend Implementation** - TypedRouter integration and handlers
- Enhanced OpsServer with hierarchical TypedRouter structure
- Created all handler classes with proper TypedHandler registration
- Implemented mock data responses for all endpoints
- Fixed all TypeScript compilation errors
- VirtualStream used for log streaming with Uint8Array encoding
### Next Steps ### Next Steps
- Phase 2: Backend Implementation - Enhance OpsServer and create handlers
- Phase 3: Frontend State Management - Set up Smartstate - Phase 3: Frontend State Management - Set up Smartstate
- Phase 4: Frontend Integration - Create API clients and update dashboard - Phase 4: Frontend Integration - Create API clients and update dashboard
- Phase 5: Create modular UI components
--- ---

View File

@ -0,0 +1,83 @@
import { expect, tap } from '@git.zone/tstest/tapbundle';
import { DcRouter } from '../ts/index.js';
import { TypedRequest } from '@api.global/typedrequest';
import * as interfaces from '../ts_interfaces/index.js';
let testDcRouter: DcRouter;
tap.test('should start DCRouter with OpsServer', async () => {
testDcRouter = new DcRouter({
// Minimal config for testing
});
await testDcRouter.start();
expect(testDcRouter.opsServer).toBeInstanceOf(Object);
});
tap.test('should respond to health status request', async () => {
const healthRequest = new TypedRequest<interfaces.requests.IReq_GetHealthStatus>(
'http://localhost:3000/typedrequest',
'getHealthStatus'
);
const response = await healthRequest.fire({
detailed: false
});
expect(response).toHaveProperty('health');
expect(response.health.healthy).toBeTrue();
expect(response.health.services).toHaveProperty('OpsServer');
});
tap.test('should respond to server statistics request', async () => {
const statsRequest = new TypedRequest<interfaces.requests.IReq_GetServerStatistics>(
'http://localhost:3000/typedrequest',
'getServerStatistics'
);
const response = await statsRequest.fire({
includeHistory: false
});
expect(response).toHaveProperty('stats');
expect(response.stats).toHaveProperty('uptime');
expect(response.stats).toHaveProperty('cpuUsage');
expect(response.stats).toHaveProperty('memoryUsage');
});
tap.test('should respond to configuration request', async () => {
const configRequest = new TypedRequest<interfaces.requests.IReq_GetConfiguration>(
'http://localhost:3000/typedrequest',
'getConfiguration'
);
const response = await configRequest.fire({});
expect(response).toHaveProperty('config');
expect(response.config).toHaveProperty('email');
expect(response.config).toHaveProperty('dns');
expect(response.config).toHaveProperty('proxy');
expect(response.config).toHaveProperty('security');
});
tap.test('should handle log retrieval request', async () => {
const logsRequest = new TypedRequest<interfaces.requests.IReq_GetRecentLogs>(
'http://localhost:3000/typedrequest',
'getRecentLogs'
);
const response = await logsRequest.fire({
limit: 10
});
expect(response).toHaveProperty('logs');
expect(response).toHaveProperty('total');
expect(response).toHaveProperty('hasMore');
expect(response.logs).toBeArray();
});
tap.test('should stop DCRouter', async () => {
await testDcRouter.stop();
});
export default tap.start();

View File

@ -134,6 +134,8 @@ export class DcRouter {
public storageManager: StorageManager; public storageManager: StorageManager;
public opsServer: OpsServer; public opsServer: OpsServer;
// TypedRouter for API endpoints
public typedrouter = new plugins.typedrequest.TypedRouter();
// Environment access // Environment access
private qenv = new plugins.qenv.Qenv('./', '.nogit/'); private qenv = new plugins.qenv.Qenv('./', '.nogit/');

View File

@ -1,13 +1,27 @@
import type DcRouter from '../classes.dcrouter.js'; import type DcRouter from '../classes.dcrouter.js';
import * as plugins from '../plugins.js'; import * as plugins from '../plugins.js';
import * as paths from '../paths.js'; import * as paths from '../paths.js';
import * as handlers from './handlers/index.js';
export class OpsServer { export class OpsServer {
public dcRouterRef: DcRouter; public dcRouterRef: DcRouter;
public server: plugins.typedserver.utilityservers.UtilityWebsiteServer; public server: plugins.typedserver.utilityservers.UtilityWebsiteServer;
// TypedRouter for OpsServer-specific handlers
public typedrouter = new plugins.typedrequest.TypedRouter();
// Handler instances
private adminHandler: handlers.AdminHandler;
private configHandler: handlers.ConfigHandler;
private logsHandler: handlers.LogsHandler;
private securityHandler: handlers.SecurityHandler;
private statsHandler: handlers.StatsHandler;
constructor(dcRouterRefArg: DcRouter) { constructor(dcRouterRefArg: DcRouter) {
this.dcRouterRef = dcRouterRefArg; this.dcRouterRef = dcRouterRefArg;
// Add our typedrouter to the dcRouter's main typedrouter
this.dcRouterRef.typedrouter.addTypedRouter(this.typedrouter);
} }
public async start() { public async start() {
@ -16,9 +30,34 @@ export class OpsServer {
feedMetadata: null, feedMetadata: null,
serveDir: paths.distServe, serveDir: paths.distServe,
}); });
// The server has a built-in typedrouter at /typedrequest
// Add the main dcRouter typedrouter to the server's typedrouter
this.server.typedrouter.addTypedRouter(this.dcRouterRef.typedrouter);
// Set up handlers
this.setupHandlers();
await this.server.start(3000); await this.server.start(3000);
} }
/**
* Set up all TypedRequest handlers
*/
private setupHandlers(): void {
// Instantiate all handlers - they self-register with the typedrouter
this.adminHandler = new handlers.AdminHandler(this);
this.configHandler = new handlers.ConfigHandler(this);
this.logsHandler = new handlers.LogsHandler(this);
this.securityHandler = new handlers.SecurityHandler(this);
this.statsHandler = new handlers.StatsHandler(this);
console.log('✅ OpsServer TypedRequest handlers initialized');
}
public async stop() {} public async stop() {
if (this.server) {
await this.server.stop();
}
}
} }

View File

@ -0,0 +1,148 @@
import * as plugins from '../../plugins.js';
import type { OpsServer } from '../classes.opsserver.js';
import * as interfaces from '../../../ts_interfaces/index.js';
export class AdminHandler {
public typedrouter = new plugins.typedrequest.TypedRouter();
// Simple in-memory session storage (in production, use proper session management)
private sessions = new Map<string, {
identity: interfaces.data.IIdentity;
createdAt: number;
lastAccess: number;
}>();
constructor(private opsServerRef: OpsServer) {
// Add this handler's router to the parent
this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter);
this.registerHandlers();
}
private registerHandlers(): void {
// Admin Login Handler
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_AdminLoginWithUsernameAndPassword>(
'adminLoginWithUsernameAndPassword',
async (dataArg, toolsArg) => {
try {
// TODO: Implement proper authentication
// For now, use a simple hardcoded check
if (dataArg.username === 'admin' && dataArg.password === 'admin') {
const token = plugins.uuid.v4();
const identity: interfaces.data.IIdentity = {
token,
expiresAt: Date.now() + 24 * 60 * 60 * 1000, // 24 hours
permissions: ['admin'],
};
// Store session
this.sessions.set(token, {
identity,
createdAt: Date.now(),
lastAccess: Date.now(),
});
// Clean up old sessions
this.cleanupSessions();
return {
identity,
};
} else {
return {};
}
} catch (error) {
return {};
}
}
)
);
// Admin Logout Handler
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_AdminLogout>(
'adminLogout',
async (dataArg, toolsArg) => {
if (dataArg.identity?.token && this.sessions.has(dataArg.identity.token)) {
this.sessions.delete(dataArg.identity.token);
return {
success: true,
};
} else {
return {
success: false,
};
}
}
)
);
// Verify Identity Handler
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_VerifyIdentity>(
'verifyIdentity',
async (dataArg, toolsArg) => {
if (!dataArg.identity?.token) {
return {
valid: false,
};
}
const session = this.sessions.get(dataArg.identity.token);
if (session && session.identity.expiresAt > Date.now()) {
// Update last access
session.lastAccess = Date.now();
return {
valid: true,
identity: session.identity,
};
} else {
// Clean up expired session
if (session) {
this.sessions.delete(dataArg.identity.token);
}
return {
valid: false,
};
}
}
)
);
}
/**
* Clean up expired sessions (older than 24 hours)
*/
private cleanupSessions(): void {
const now = Date.now();
const maxAge = 24 * 60 * 60 * 1000; // 24 hours
for (const [token, session] of this.sessions.entries()) {
if (now - session.lastAccess > maxAge) {
this.sessions.delete(token);
}
}
}
/**
* Create a guard for authentication
* This can be used by other handlers to protect endpoints
*/
public createAuthGuard() {
return async (dataArg: { identity?: interfaces.data.IIdentity }) => {
if (!dataArg.identity?.token) {
return false;
}
const session = this.sessions.get(dataArg.identity.token);
if (session && session.identity.expiresAt > Date.now()) {
// Update last access
session.lastAccess = Date.now();
return true;
}
return false;
};
}
}

View File

@ -0,0 +1,143 @@
import * as plugins from '../../plugins.js';
import type { OpsServer } from '../classes.opsserver.js';
import * as interfaces from '../../../ts_interfaces/index.js';
export class ConfigHandler {
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 {
// Get Configuration Handler
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetConfiguration>(
'getConfiguration',
async (dataArg, toolsArg) => {
const config = await this.getConfiguration(dataArg.section);
return {
config,
section: dataArg.section,
};
}
)
);
// Update Configuration Handler
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_UpdateConfiguration>(
'updateConfiguration',
async (dataArg, toolsArg) => {
try {
const updatedConfig = await this.updateConfiguration(dataArg.section, dataArg.config);
return {
updated: true,
config: updatedConfig,
};
} catch (error) {
return {
updated: false,
config: null,
};
}
}
)
);
}
private async getConfiguration(section?: string): Promise<{
email: {
enabled: boolean;
ports: number[];
maxMessageSize: number;
rateLimits: {
perMinute: number;
perHour: number;
perDay: number;
};
};
dns: {
enabled: boolean;
port: number;
nameservers: string[];
caching: boolean;
ttl: number;
};
proxy: {
enabled: boolean;
httpPort: number;
httpsPort: number;
maxConnections: number;
};
security: {
blockList: string[];
rateLimit: boolean;
spamDetection: boolean;
tlsRequired: boolean;
};
}> {
const dcRouter = this.opsServerRef.dcRouterRef;
return {
email: {
enabled: !!dcRouter.emailServer,
ports: dcRouter.emailServer ? [25, 465, 587, 2525] : [],
maxMessageSize: 10 * 1024 * 1024, // 10MB default
rateLimits: {
perMinute: 10,
perHour: 100,
perDay: 1000,
},
},
dns: {
enabled: !!dcRouter.dnsServer,
port: 53,
nameservers: dcRouter.options.dnsNsDomains || [],
caching: true,
ttl: 300,
},
proxy: {
enabled: !!dcRouter.smartProxy,
httpPort: 80,
httpsPort: 443,
maxConnections: 1000,
},
security: {
blockList: [],
rateLimit: true,
spamDetection: true,
tlsRequired: false,
},
};
}
private async updateConfiguration(section: string, config: any): Promise<any> {
// TODO: Implement actual configuration updates
// This would involve:
// 1. Validating the configuration changes
// 2. Applying them to the running services
// 3. Persisting them to storage
// 4. Potentially restarting affected services
// For now, just validate and return the config
if (section === 'email' && config.maxMessageSize && config.maxMessageSize < 1024) {
throw new Error('Maximum message size must be at least 1KB');
}
if (section === 'dns' && config.ttl && (config.ttl < 0 || config.ttl > 86400)) {
throw new Error('DNS TTL must be between 0 and 86400 seconds');
}
if (section === 'proxy' && config.maxConnections && config.maxConnections < 1) {
throw new Error('Maximum connections must be at least 1');
}
// In a real implementation, apply the changes here
// For now, return the current configuration
const currentConfig = await this.getConfiguration(section);
return currentConfig;
}
}

View File

@ -0,0 +1,5 @@
export * from './admin.handler.js';
export * from './config.handler.js';
export * from './logs.handler.js';
export * from './security.handler.js';
export * from './stats.handler.js';

View File

@ -0,0 +1,195 @@
import * as plugins from '../../plugins.js';
import type { OpsServer } from '../classes.opsserver.js';
import * as interfaces from '../../../ts_interfaces/index.js';
export class LogsHandler {
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 {
// Get Recent Logs Handler
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetRecentLogs>(
'getRecentLogs',
async (dataArg, toolsArg) => {
const logs = await this.getRecentLogs(
dataArg.level,
dataArg.category,
dataArg.limit || 100,
dataArg.offset || 0,
dataArg.search,
dataArg.timeRange
);
return {
logs,
total: logs.length, // TODO: Implement proper total count
hasMore: false, // TODO: Implement proper pagination
};
}
)
);
// Get Log Stream Handler
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetLogStream>(
'getLogStream',
async (dataArg, toolsArg) => {
// Create a virtual stream for log streaming
const virtualStream = new plugins.typedrequest.VirtualStream<Uint8Array>();
// Set up log streaming
const streamLogs = this.setupLogStream(
virtualStream,
dataArg.filters?.level,
dataArg.filters?.category,
dataArg.follow
);
// Start streaming
streamLogs.start();
// VirtualStream handles cleanup automatically
return {
logStream: virtualStream as any, // Cast to IVirtualStream interface
};
}
)
);
}
private async getRecentLogs(
level?: 'error' | 'warn' | 'info' | 'debug',
category?: 'smtp' | 'dns' | 'security' | 'system' | 'email',
limit: number = 100,
offset: number = 0,
search?: string,
timeRange?: '1h' | '6h' | '24h' | '7d' | '30d'
): Promise<Array<{
timestamp: number;
level: 'debug' | 'info' | 'warn' | 'error';
category: 'smtp' | 'dns' | 'security' | 'system' | 'email';
message: string;
metadata?: any;
}>> {
// TODO: Implement actual log retrieval from storage or logger
// For now, return mock data
const mockLogs: Array<{
timestamp: number;
level: 'debug' | 'info' | 'warn' | 'error';
category: 'smtp' | 'dns' | 'security' | 'system' | 'email';
message: string;
metadata?: any;
}> = [];
const categories: Array<'smtp' | 'dns' | 'security' | 'system' | 'email'> = ['smtp', 'dns', 'security', 'system', 'email'];
const levels: Array<'debug' | 'info' | 'warn' | 'error'> = ['info', 'warn', 'error', 'debug'];
const now = Date.now();
// Generate some mock log entries
for (let i = 0; i < 50; i++) {
const mockCategory = categories[Math.floor(Math.random() * categories.length)];
const mockLevel = levels[Math.floor(Math.random() * levels.length)];
// Filter by requested criteria
if (level && mockLevel !== level) continue;
if (category && mockCategory !== category) continue;
mockLogs.push({
timestamp: now - (i * 60000), // 1 minute apart
level: mockLevel,
category: mockCategory,
message: `Sample log message ${i} from ${mockCategory}`,
metadata: {
requestId: plugins.uuid.v4(),
},
});
}
// Apply pagination
return mockLogs.slice(offset, offset + limit);
}
private setupLogStream(
virtualStream: plugins.typedrequest.VirtualStream<Uint8Array>,
levelFilter?: string[],
categoryFilter?: string[],
follow: boolean = true
): {
start: () => void;
stop: () => void;
} {
let intervalId: NodeJS.Timeout | null = null;
let logIndex = 0;
const start = () => {
if (!follow) {
// Send existing logs and close
this.getRecentLogs(
levelFilter?.[0] as any,
categoryFilter?.[0] as any,
100,
0
).then(logs => {
logs.forEach(log => {
const logData = JSON.stringify(log);
const encoder = new TextEncoder();
virtualStream.sendData(encoder.encode(logData));
});
// VirtualStream doesn't have end() method - it closes automatically
});
return;
}
// For follow mode, simulate real-time log streaming
intervalId = setInterval(() => {
const categories: Array<'smtp' | 'dns' | 'security' | 'system' | 'email'> = ['smtp', 'dns', 'security', 'system', 'email'];
const levels: Array<'debug' | 'info' | 'warn' | 'error'> = ['info', 'warn', 'error', 'debug'];
const mockCategory = categories[Math.floor(Math.random() * categories.length)];
const mockLevel = levels[Math.floor(Math.random() * levels.length)];
// Filter by requested criteria
if (levelFilter && !levelFilter.includes(mockLevel)) return;
if (categoryFilter && !categoryFilter.includes(mockCategory)) return;
const logEntry = {
timestamp: Date.now(),
level: mockLevel,
category: mockCategory,
message: `Real-time log ${logIndex++} from ${mockCategory}`,
metadata: {
requestId: plugins.uuid.v4(),
},
};
const logData = JSON.stringify(logEntry);
const encoder = new TextEncoder();
virtualStream.sendData(encoder.encode(logData));
}, 2000); // Send a log every 2 seconds
// TODO: Hook into actual logger events
// logger.on('log', (logEntry) => {
// if (matchesCriteria(logEntry, level, service)) {
// virtualStream.sendData(formatLogEntry(logEntry));
// }
// });
};
const stop = () => {
if (intervalId) {
clearInterval(intervalId);
intervalId = null;
}
// TODO: Unhook from logger events
};
return { start, stop };
}
}

View File

@ -0,0 +1,208 @@
import * as plugins from '../../plugins.js';
import type { OpsServer } from '../classes.opsserver.js';
import * as interfaces from '../../../ts_interfaces/index.js';
export class SecurityHandler {
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 {
// Security Metrics Handler
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetSecurityMetrics>(
'getSecurityMetrics',
async (dataArg, toolsArg) => {
const metrics = await this.collectSecurityMetrics();
return {
metrics: {
blockedIPs: metrics.blockedIPs,
reputationScores: metrics.reputationScores,
spamDetected: metrics.spamDetection.detected,
malwareDetected: metrics.malwareDetected,
phishingDetected: metrics.phishingDetected,
authenticationFailures: metrics.authFailures,
suspiciousActivities: metrics.suspiciousActivities,
},
trends: dataArg.includeDetails ? {
spam: metrics.trends.spam,
malware: metrics.trends.malware,
phishing: metrics.trends.phishing,
} : undefined,
};
}
)
);
// Active Connections Handler
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetActiveConnections>(
'getActiveConnections',
async (dataArg, toolsArg) => {
const connections = await this.getActiveConnections(dataArg.protocol, dataArg.state);
const connectionInfos: interfaces.data.IConnectionInfo[] = connections.map(conn => ({
id: conn.id,
remoteAddress: conn.source.ip,
localAddress: conn.destination.ip,
startTime: conn.startTime,
protocol: conn.type === 'http' ? 'https' : conn.type as any,
state: conn.status as any,
bytesReceived: Math.floor(conn.bytesTransferred / 2),
bytesSent: Math.floor(conn.bytesTransferred / 2),
}));
const summary = {
total: connectionInfos.length,
byProtocol: connectionInfos.reduce((acc, conn) => {
acc[conn.protocol] = (acc[conn.protocol] || 0) + 1;
return acc;
}, {} as { [protocol: string]: number }),
byState: connectionInfos.reduce((acc, conn) => {
acc[conn.state] = (acc[conn.state] || 0) + 1;
return acc;
}, {} as { [state: string]: number }),
};
return {
connections: connectionInfos,
summary,
};
}
)
);
// Rate Limit Status Handler
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetRateLimitStatus>(
'getRateLimitStatus',
async (dataArg, toolsArg) => {
const status = await this.getRateLimitStatus(dataArg.domain, dataArg.ip);
const limits: interfaces.data.IRateLimitInfo[] = status.limits.map(limit => ({
domain: limit.identifier,
currentRate: limit.current,
limit: limit.limit,
remaining: limit.limit - limit.current,
resetTime: limit.resetAt,
blocked: limit.status === 'limited',
}));
return {
limits,
globalLimit: dataArg.includeBlocked ? {
current: limits.reduce((sum, l) => sum + l.currentRate, 0),
limit: 1000, // Global limit
remaining: 1000 - limits.reduce((sum, l) => sum + l.currentRate, 0),
} : undefined,
};
}
)
);
}
private async collectSecurityMetrics(): Promise<{
blockedIPs: string[];
reputationScores: { [domain: string]: number };
spamDetection: {
detected: number;
falsePositives: number;
};
malwareDetected: number;
phishingDetected: number;
authFailures: number;
suspiciousActivities: number;
trends: {
spam: Array<{ timestamp: number; value: number }>;
malware: Array<{ timestamp: number; value: number }>;
phishing: Array<{ timestamp: number; value: number }>;
};
}> {
// TODO: Implement actual security metrics collection
return {
blockedIPs: [],
reputationScores: {},
spamDetection: {
detected: 0,
falsePositives: 0,
},
malwareDetected: 0,
phishingDetected: 0,
authFailures: 0,
suspiciousActivities: 0,
trends: {
spam: [],
malware: [],
phishing: [],
},
};
}
private async getActiveConnections(
protocol?: 'http' | 'https' | 'smtp' | 'smtps',
state?: string
): Promise<Array<{
id: string;
type: 'http' | 'smtp' | 'dns';
source: {
ip: string;
port: number;
country?: string;
};
destination: {
ip: string;
port: number;
service?: string;
};
startTime: number;
bytesTransferred: number;
status: 'active' | 'idle' | 'closing';
}>> {
const connections: Array<{
id: string;
type: 'http' | 'smtp' | 'dns';
source: {
ip: string;
port: number;
country?: string;
};
destination: {
ip: string;
port: number;
service?: string;
};
startTime: number;
bytesTransferred: number;
status: 'active' | 'idle' | 'closing';
}> = [];
// TODO: Implement actual connection tracking
// This would collect from:
// - SmartProxy connections
// - Email server connections
// - DNS server connections
return connections;
}
private async getRateLimitStatus(
domain?: string,
ip?: string
): Promise<{
limits: Array<{
identifier: string;
type: 'ip' | 'domain' | 'email';
limit: number;
current: number;
resetAt: number;
status: 'ok' | 'warning' | 'limited';
}>;
}> {
// TODO: Implement actual rate limit status collection
return {
limits: [],
};
}
}

View File

@ -0,0 +1,344 @@
import * as plugins from '../../plugins.js';
import type { OpsServer } from '../classes.opsserver.js';
import * as interfaces from '../../../ts_interfaces/index.js';
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;
}>;
}> {
const uptime = process.uptime();
const memUsage = process.memoryUsage();
const totalMem = plugins.os.totalmem();
const freeMem = plugins.os.freemem();
const usedMem = totalMem - freeMem;
// Get CPU usage (simplified - in production would use proper monitoring)
const cpuUsage = plugins.os.loadavg()[0] * 100 / plugins.os.cpus().length;
// TODO: Implement proper request tracking
const requestsPerSecond = 0;
const activeConnections = 0;
const totalConnections = 0;
return {
uptime,
cpuUsage: {
user: cpuUsage * 0.7, // Approximate user CPU
system: cpuUsage * 0.3, // Approximate system CPU
},
memoryUsage: {
heapUsed: memUsage.heapUsed,
heapTotal: memUsage.heapTotal,
external: memUsage.external,
rss: memUsage.rss,
},
requestsPerSecond,
activeConnections,
totalConnections,
history: [], // TODO: Implement history tracking
};
}
private async collectEmailStats(): Promise<{
sentToday: number;
receivedToday: number;
bounceRate: number;
deliveryRate: number;
queueSize: number;
domainBreakdown?: { [domain: string]: interfaces.data.IEmailStats };
}> {
// TODO: Implement actual email statistics collection
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 };
}> {
// TODO: Implement actual DNS statistics collection
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',
},
],
};
}
}