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'; import { type IStatsTile } from '@design.estate/dees-catalog'; @customElement('ops-view-security') export class OpsViewSecurity extends DeesElement { @state() accessor statsState: appstate.IStatsState = { serverStats: null, emailStats: null, dnsStats: null, securityMetrics: null, lastUpdated: 0, isLoading: false, error: null, }; @state() accessor selectedTab: 'overview' | 'blocked' | 'authentication' | 'email-security' = 'overview'; 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` .tabs { display: flex; gap: 8px; margin-bottom: 24px; border-bottom: 2px solid ${cssManager.bdTheme('#e9ecef', '#333')}; } .tab { padding: 12px 24px; background: none; border: none; border-bottom: 2px solid transparent; cursor: pointer; font-size: 16px; color: ${cssManager.bdTheme('#666', '#999')}; transition: all 0.2s ease; } .tab:hover { color: ${cssManager.bdTheme('#333', '#ccc')}; } .tab.active { color: ${cssManager.bdTheme('#2196F3', '#4a90e2')}; border-bottom-color: ${cssManager.bdTheme('#2196F3', '#4a90e2')}; } h2 { margin: 32px 0 16px 0; font-size: 24px; font-weight: 600; color: ${cssManager.bdTheme('#333', '#ccc')}; } dees-statsgrid { margin-bottom: 32px; } .securityCard { background: ${cssManager.bdTheme('#fff', '#222')}; border: 1px solid ${cssManager.bdTheme('#e9ecef', '#333')}; border-radius: 8px; padding: 24px; position: relative; overflow: hidden; } .securityCard.alert { border-color: ${cssManager.bdTheme('#f44336', '#ff6666')}; background: ${cssManager.bdTheme('#ffebee', '#4a1f1f')}; } .securityCard.warning { border-color: ${cssManager.bdTheme('#ff9800', '#ffaa33')}; background: ${cssManager.bdTheme('#fff3e0', '#4a3a1f')}; } .securityCard.success { border-color: ${cssManager.bdTheme('#4caf50', '#66cc66')}; background: ${cssManager.bdTheme('#e8f5e9', '#1f3f1f')}; } .cardHeader { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; } .cardTitle { font-size: 18px; font-weight: 600; color: ${cssManager.bdTheme('#333', '#ccc')}; } .cardStatus { font-size: 14px; padding: 4px 12px; border-radius: 16px; font-weight: 500; } .status-critical { background: ${cssManager.bdTheme('#f44336', '#ff6666')}; color: ${cssManager.bdTheme('#fff', '#fff')}; } .status-warning { background: ${cssManager.bdTheme('#ff9800', '#ffaa33')}; color: ${cssManager.bdTheme('#fff', '#fff')}; } .status-good { background: ${cssManager.bdTheme('#4caf50', '#66cc66')}; color: ${cssManager.bdTheme('#fff', '#fff')}; } .metricValue { font-size: 32px; font-weight: 700; margin-bottom: 8px; } .metricLabel { font-size: 14px; color: ${cssManager.bdTheme('#666', '#999')}; } .actionButton { margin-top: 16px; } .blockedIpList { max-height: 400px; overflow-y: auto; } .blockedIpItem { display: flex; justify-content: space-between; align-items: center; padding: 12px; border-bottom: 1px solid ${cssManager.bdTheme('#e9ecef', '#333')}; } .blockedIpItem:last-child { border-bottom: none; } .ipAddress { font-family: 'Consolas', 'Monaco', monospace; font-weight: 600; } .blockReason { font-size: 14px; color: ${cssManager.bdTheme('#666', '#999')}; } .blockTime { font-size: 12px; color: ${cssManager.bdTheme('#999', '#666')}; } `, ]; public render() { return html` Security
${this.renderTabContent()} `; } private renderTabContent() { const metrics = this.statsState.securityMetrics; if (!metrics) { return html`

Loading security metrics...

`; } switch(this.selectedTab) { case 'overview': return this.renderOverview(metrics); case 'blocked': return this.renderBlockedIPs(metrics); case 'authentication': return this.renderAuthentication(metrics); case 'email-security': return this.renderEmailSecurity(metrics); } } private renderOverview(metrics: any) { const threatLevel = this.calculateThreatLevel(metrics); const threatScore = this.getThreatScore(metrics); // Derive active sessions from recent successful auth events (last hour) const allEvents: any[] = metrics.recentEvents || []; const oneHourAgo = Date.now() - 3600000; const recentAuthSuccesses = allEvents.filter( (evt: any) => evt.type === 'authentication' && evt.success === true && evt.timestamp >= oneHourAgo ).length; const tiles: IStatsTile[] = [ { id: 'threatLevel', title: 'Threat Level', value: threatScore, type: 'gauge', icon: 'lucide:Shield', gaugeOptions: { min: 0, max: 100, thresholds: [ { value: 0, color: '#ef4444' }, { value: 30, color: '#f59e0b' }, { value: 70, color: '#22c55e' }, ], }, description: `Status: ${threatLevel.toUpperCase()}`, }, { id: 'blockedThreats', title: 'Blocked Threats', value: (metrics.blockedIPs?.length || 0) + metrics.spamDetected, type: 'number', icon: 'lucide:ShieldCheck', color: '#ef4444', description: 'Total threats blocked today', }, { id: 'activeSessions', title: 'Active Sessions', value: recentAuthSuccesses, type: 'number', icon: 'lucide:Users', color: '#22c55e', description: 'Authenticated in last hour', }, { id: 'authFailures', title: 'Auth Failures', value: metrics.authenticationFailures, type: 'number', icon: 'lucide:LockOpen', color: metrics.authenticationFailures > 10 ? '#ef4444' : '#f59e0b', description: 'Failed login attempts today', }, ]; return html`

Recent Security Events

({ 'Time': new Date(item.timestamp).toLocaleTimeString(), 'Event': item.event, 'Severity': item.severity, 'Details': item.details, })} > `; } private renderBlockedIPs(metrics: any) { return html`

Blocked IP Addresses

this.clearBlockedIPs()}> Clear All
${metrics.blockedIPs && metrics.blockedIPs.length > 0 ? metrics.blockedIPs.map((ipAddress, index) => html`
${ipAddress}
Suspicious activity
Blocked
this.unblockIP(ipAddress)}> Unblock
`) : html`

No blocked IPs

`}
`; } private renderAuthentication(metrics: any) { // Derive auth events from recentEvents const allEvents: any[] = metrics.recentEvents || []; const authEvents = allEvents.filter((evt: any) => evt.type === 'authentication'); const successfulLogins = authEvents.filter((evt: any) => evt.success === true).length; const tiles: IStatsTile[] = [ { id: 'authFailures', title: 'Authentication Failures', value: metrics.authenticationFailures, type: 'number', icon: 'lucide:LockOpen', color: metrics.authenticationFailures > 10 ? '#ef4444' : '#f59e0b', description: 'Failed authentication attempts today', }, { id: 'successfulLogins', title: 'Successful Logins', value: successfulLogins, type: 'number', icon: 'lucide:Lock', color: '#22c55e', description: 'Successful logins today', }, ]; // Map auth events to login history table data const loginHistory = authEvents.map((evt: any) => ({ timestamp: evt.timestamp, username: evt.details?.username || 'unknown', ipAddress: evt.ipAddress || 'unknown', success: evt.success ?? false, reason: evt.success ? '' : evt.message || 'Authentication failed', })); return html`

Recent Login Attempts

({ 'Time': new Date(item.timestamp).toLocaleString(), 'Username': item.username, 'IP Address': item.ipAddress, 'Status': item.success ? 'Success' : 'Failed', 'Reason': item.reason || '-', })} > `; } private renderEmailSecurity(metrics: any) { const tiles: IStatsTile[] = [ { id: 'malware', title: 'Malware Detection', value: metrics.malwareDetected, type: 'number', icon: 'lucide:BugOff', color: metrics.malwareDetected > 0 ? '#ef4444' : '#22c55e', description: 'Malware detected', }, { id: 'phishing', title: 'Phishing Detection', value: metrics.phishingDetected, type: 'number', icon: 'lucide:Fish', color: metrics.phishingDetected > 0 ? '#ef4444' : '#22c55e', description: 'Phishing attempts detected', }, { id: 'suspicious', title: 'Suspicious Activities', value: metrics.suspiciousActivities, type: 'number', icon: 'lucide:TriangleAlert', color: metrics.suspiciousActivities > 5 ? '#ef4444' : '#f59e0b', description: 'Suspicious activities detected', }, { id: 'spam', title: 'Spam Detection', value: metrics.spamDetected, type: 'number', icon: 'lucide:Ban', color: '#f59e0b', description: 'Spam emails blocked', }, ]; return html`

Email Security Configuration

this.saveEmailSecuritySettings()} > Save Settings
`; } private calculateThreatLevel(metrics: any): string { const score = this.getThreatScore(metrics); if (score < 30) return 'alert'; if (score < 70) return 'warning'; return 'success'; } private getThreatScore(metrics: any): number { // Simple scoring algorithm let score = 100; const blockedCount = Array.isArray(metrics.blockedIPs) ? metrics.blockedIPs.length : (metrics.blockedIPs || 0); score -= blockedCount * 2; score -= (metrics.authenticationFailures || 0) * 1; score -= (metrics.spamDetected || 0) * 0.5; score -= (metrics.malwareDetected || 0) * 3; score -= (metrics.phishingDetected || 0) * 3; score -= (metrics.suspiciousActivities || 0) * 2; return Math.max(0, Math.min(100, Math.round(score))); } private getSecurityEvents(metrics: any): any[] { const events: any[] = metrics.recentEvents || []; return events.map((evt: any) => ({ timestamp: evt.timestamp, event: evt.message, severity: evt.level === 'critical' ? 'critical' : evt.level === 'error' ? 'high' : evt.level === 'warn' ? 'warning' : 'info', details: evt.ipAddress ? `IP: ${evt.ipAddress}` : evt.domain ? `Domain: ${evt.domain}` : evt.type, })); } private async clearBlockedIPs() { // SmartProxy manages IP blocking — not yet exposed via API alert('Clearing blocked IPs is not yet supported from the UI.'); } private async unblockIP(ip: string) { // SmartProxy manages IP blocking — not yet exposed via API alert(`Unblocking IP ${ip} is not yet supported from the UI.`); } private async saveEmailSecuritySettings() { // Config is read-only from the UI for now alert('Email security settings are read-only. Update the dcrouter configuration file to change these settings.'); } }