2026-04-08 07:45:26 +00:00
|
|
|
import * as appstate from '../../appstate.js';
|
2026-04-08 08:24:55 +00:00
|
|
|
import { viewHostCss } from '../shared/css.js';
|
2026-04-08 07:45:26 +00:00
|
|
|
|
|
|
|
|
import {
|
|
|
|
|
DeesElement,
|
|
|
|
|
customElement,
|
|
|
|
|
html,
|
|
|
|
|
state,
|
|
|
|
|
css,
|
|
|
|
|
cssManager,
|
|
|
|
|
type TemplateResult,
|
|
|
|
|
} from '@design.estate/dees-element';
|
|
|
|
|
import { type IStatsTile } from '@design.estate/dees-catalog';
|
|
|
|
|
|
|
|
|
|
declare global {
|
|
|
|
|
interface HTMLElementTagNameMap {
|
|
|
|
|
'ops-view-security-overview': OpsViewSecurityOverview;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@customElement('ops-view-security-overview')
|
|
|
|
|
export class OpsViewSecurityOverview extends DeesElement {
|
|
|
|
|
@state()
|
|
|
|
|
accessor statsState: appstate.IStatsState = appstate.statsStatePart.getState()!;
|
|
|
|
|
|
|
|
|
|
constructor() {
|
|
|
|
|
super();
|
|
|
|
|
const sub = appstate.statsStatePart
|
|
|
|
|
.select((s) => s)
|
|
|
|
|
.subscribe((s) => {
|
|
|
|
|
this.statsState = s;
|
|
|
|
|
});
|
|
|
|
|
this.rxSubscriptions.push(sub);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static styles = [
|
|
|
|
|
cssManager.defaultStyles,
|
2026-04-08 08:24:55 +00:00
|
|
|
viewHostCss,
|
2026-04-08 07:45:26 +00:00
|
|
|
css`
|
|
|
|
|
h2 {
|
|
|
|
|
margin: 32px 0 16px 0;
|
|
|
|
|
font-size: 24px;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
color: ${cssManager.bdTheme('#333', '#ccc')};
|
|
|
|
|
}
|
|
|
|
|
dees-statsgrid {
|
|
|
|
|
margin-bottom: 32px;
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
public render(): TemplateResult {
|
|
|
|
|
const metrics = this.statsState.securityMetrics;
|
|
|
|
|
|
|
|
|
|
if (!metrics) {
|
|
|
|
|
return html`
|
|
|
|
|
<div class="loadingMessage">
|
|
|
|
|
<p>Loading security metrics...</p>
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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`
|
|
|
|
|
<dees-heading level="hr">Overview</dees-heading>
|
|
|
|
|
|
|
|
|
|
<dees-statsgrid
|
|
|
|
|
.tiles=${tiles}
|
|
|
|
|
.minTileWidth=${200}
|
|
|
|
|
></dees-statsgrid>
|
|
|
|
|
|
|
|
|
|
<h2>Recent Security Events</h2>
|
|
|
|
|
<dees-table
|
|
|
|
|
.heading1=${'Security Events'}
|
|
|
|
|
.heading2=${'Last 24 hours'}
|
|
|
|
|
.data=${this.getSecurityEvents(metrics)}
|
|
|
|
|
.displayFunction=${(item) => ({
|
|
|
|
|
'Time': new Date(item.timestamp).toLocaleTimeString(),
|
|
|
|
|
'Event': item.event,
|
|
|
|
|
'Severity': item.severity,
|
|
|
|
|
'Details': item.details,
|
|
|
|
|
})}
|
|
|
|
|
></dees-table>
|
|
|
|
|
`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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,
|
|
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
}
|