- Implemented `ops-view-logs` for displaying and filtering logs with streaming capabilities. - Created `ops-view-overview` to show server, email, DNS statistics, and charts. - Developed `ops-view-security` for monitoring security metrics, blocked IPs, and authentication attempts. - Added `ops-view-stats` to present comprehensive statistics on server, email, DNS, and security metrics. - Introduced shared styles and components including `ops-sectionheading` for consistent UI.
471 lines
12 KiB
TypeScript
471 lines
12 KiB
TypeScript
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';
|
|
|
|
@customElement('ops-view-security')
|
|
export class OpsViewSecurity extends DeesElement {
|
|
@state()
|
|
private statsState: appstate.IStatsState = {
|
|
serverStats: null,
|
|
emailStats: null,
|
|
dnsStats: null,
|
|
securityMetrics: null,
|
|
lastUpdated: 0,
|
|
isLoading: false,
|
|
error: null,
|
|
};
|
|
|
|
@state()
|
|
private 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 #e9ecef;
|
|
}
|
|
|
|
.tab {
|
|
padding: 12px 24px;
|
|
background: none;
|
|
border: none;
|
|
border-bottom: 2px solid transparent;
|
|
cursor: pointer;
|
|
font-size: 16px;
|
|
color: #666;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.tab:hover {
|
|
color: #333;
|
|
}
|
|
|
|
.tab.active {
|
|
color: #2196F3;
|
|
border-bottom-color: #2196F3;
|
|
}
|
|
|
|
.securityGrid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
|
gap: 16px;
|
|
margin-bottom: 32px;
|
|
}
|
|
|
|
.securityCard {
|
|
background: white;
|
|
border: 1px solid #e9ecef;
|
|
border-radius: 8px;
|
|
padding: 24px;
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.securityCard.alert {
|
|
border-color: #f44336;
|
|
background: #ffebee;
|
|
}
|
|
|
|
.securityCard.warning {
|
|
border-color: #ff9800;
|
|
background: #fff3e0;
|
|
}
|
|
|
|
.securityCard.success {
|
|
border-color: #4caf50;
|
|
background: #e8f5e9;
|
|
}
|
|
|
|
.cardHeader {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.cardTitle {
|
|
font-size: 18px;
|
|
font-weight: 600;
|
|
color: #333;
|
|
}
|
|
|
|
.cardStatus {
|
|
font-size: 14px;
|
|
padding: 4px 12px;
|
|
border-radius: 16px;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.status-critical {
|
|
background: #f44336;
|
|
color: white;
|
|
}
|
|
|
|
.status-warning {
|
|
background: #ff9800;
|
|
color: white;
|
|
}
|
|
|
|
.status-good {
|
|
background: #4caf50;
|
|
color: white;
|
|
}
|
|
|
|
.metricValue {
|
|
font-size: 32px;
|
|
font-weight: 700;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.metricLabel {
|
|
font-size: 14px;
|
|
color: #666;
|
|
}
|
|
|
|
.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 #e9ecef;
|
|
}
|
|
|
|
.blockedIpItem:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.ipAddress {
|
|
font-family: 'Consolas', 'Monaco', monospace;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.blockReason {
|
|
font-size: 14px;
|
|
color: #666;
|
|
}
|
|
|
|
.blockTime {
|
|
font-size: 12px;
|
|
color: #999;
|
|
}
|
|
`,
|
|
];
|
|
|
|
public render() {
|
|
return html`
|
|
<ops-sectionheading>Security</ops-sectionheading>
|
|
|
|
<div class="tabs">
|
|
<button
|
|
class="tab ${this.selectedTab === 'overview' ? 'active' : ''}"
|
|
@click=${() => this.selectedTab = 'overview'}
|
|
>
|
|
Overview
|
|
</button>
|
|
<button
|
|
class="tab ${this.selectedTab === 'blocked' ? 'active' : ''}"
|
|
@click=${() => this.selectedTab = 'blocked'}
|
|
>
|
|
Blocked IPs
|
|
</button>
|
|
<button
|
|
class="tab ${this.selectedTab === 'authentication' ? 'active' : ''}"
|
|
@click=${() => this.selectedTab = 'authentication'}
|
|
>
|
|
Authentication
|
|
</button>
|
|
<button
|
|
class="tab ${this.selectedTab === 'email-security' ? 'active' : ''}"
|
|
@click=${() => this.selectedTab = 'email-security'}
|
|
>
|
|
Email Security
|
|
</button>
|
|
</div>
|
|
|
|
${this.renderTabContent()}
|
|
`;
|
|
}
|
|
|
|
private renderTabContent() {
|
|
const metrics = this.statsState.securityMetrics;
|
|
|
|
if (!metrics) {
|
|
return html`
|
|
<div class="loadingMessage">
|
|
<p>Loading security metrics...</p>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
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);
|
|
|
|
return html`
|
|
<div class="securityGrid">
|
|
<div class="securityCard ${threatLevel}">
|
|
<div class="cardHeader">
|
|
<h3 class="cardTitle">Threat Level</h3>
|
|
<span class="cardStatus status-${threatLevel === 'alert' ? 'critical' : threatLevel === 'warning' ? 'warning' : 'good'}">
|
|
${threatLevel.toUpperCase()}
|
|
</span>
|
|
</div>
|
|
<div class="metricValue">${this.getThreatScore(metrics)}/100</div>
|
|
<div class="metricLabel">Overall security score</div>
|
|
</div>
|
|
|
|
<div class="securityCard">
|
|
<div class="cardHeader">
|
|
<h3 class="cardTitle">Blocked Threats</h3>
|
|
</div>
|
|
<div class="metricValue">${metrics.blockedIPs.length + metrics.spamDetected}</div>
|
|
<div class="metricLabel">Total threats blocked today</div>
|
|
</div>
|
|
|
|
<div class="securityCard">
|
|
<div class="cardHeader">
|
|
<h3 class="cardTitle">Active Sessions</h3>
|
|
</div>
|
|
<div class="metricValue">${0}</div>
|
|
<div class="metricLabel">Current authenticated sessions</div>
|
|
</div>
|
|
</div>
|
|
|
|
<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 renderBlockedIPs(metrics: any) {
|
|
return html`
|
|
<div class="securityCard">
|
|
<div class="cardHeader">
|
|
<h3 class="cardTitle">Blocked IP Addresses</h3>
|
|
<dees-button @click=${() => this.clearBlockedIPs()}>
|
|
Clear All
|
|
</dees-button>
|
|
</div>
|
|
|
|
<div class="blockedIpList">
|
|
${metrics.blockedIPs && metrics.blockedIPs.length > 0 ? metrics.blockedIPs.map((ipAddress, index) => html`
|
|
<div class="blockedIpItem">
|
|
<div>
|
|
<div class="ipAddress">${ipAddress}</div>
|
|
<div class="blockReason">Suspicious activity</div>
|
|
<div class="blockTime">Blocked</div>
|
|
</div>
|
|
<dees-button @click=${() => this.unblockIP(ipAddress)}>
|
|
Unblock
|
|
</dees-button>
|
|
</div>
|
|
`) : html`
|
|
<p>No blocked IPs</p>
|
|
`}
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
private renderAuthentication(metrics: any) {
|
|
return html`
|
|
<div class="securityGrid">
|
|
<div class="securityCard">
|
|
<h3 class="cardTitle">Authentication Statistics</h3>
|
|
<div class="metricValue">${metrics.authenticationFailures}</div>
|
|
<div class="metricLabel">Failed authentication attempts today</div>
|
|
</div>
|
|
|
|
<div class="securityCard">
|
|
<h3 class="cardTitle">Successful Logins</h3>
|
|
<div class="metricValue">${0}</div>
|
|
<div class="metricLabel">Successful logins today</div>
|
|
</div>
|
|
</div>
|
|
|
|
<h2>Recent Login Attempts</h2>
|
|
<dees-table
|
|
.heading1=${'Login History'}
|
|
.heading2=${'Recent authentication attempts'}
|
|
.data=${[]}
|
|
.displayFunction=${(item) => ({
|
|
'Time': new Date(item.timestamp).toLocaleString(),
|
|
'Username': item.username,
|
|
'IP Address': item.ipAddress,
|
|
'Status': item.success ? 'Success' : 'Failed',
|
|
'Reason': item.reason || '-',
|
|
})}
|
|
></dees-table>
|
|
`;
|
|
}
|
|
|
|
private renderEmailSecurity(metrics: any) {
|
|
return html`
|
|
<div class="securityGrid">
|
|
<div class="securityCard">
|
|
<h3 class="cardTitle">Malware Detection</h3>
|
|
<div class="metricValue">${metrics.malwareDetected}</div>
|
|
<div class="metricLabel">Malware detected</div>
|
|
</div>
|
|
|
|
<div class="securityCard">
|
|
<h3 class="cardTitle">Phishing Detection</h3>
|
|
<div class="metricValue">${metrics.phishingDetected}</div>
|
|
<div class="metricLabel">Phishing attempts detected</div>
|
|
</div>
|
|
|
|
<div class="securityCard">
|
|
<h3 class="cardTitle">Suspicious Activities</h3>
|
|
<div class="metricValue">${metrics.suspiciousActivities}</div>
|
|
<div class="metricLabel">Suspicious activities detected</div>
|
|
</div>
|
|
|
|
<div class="securityCard">
|
|
<h3 class="cardTitle">Spam Detection</h3>
|
|
<div class="metricValue">${metrics.spamDetected}</div>
|
|
<div class="metricLabel">Spam emails blocked</div>
|
|
</div>
|
|
</div>
|
|
|
|
<h2>Email Security Configuration</h2>
|
|
<div class="securityCard">
|
|
<dees-form>
|
|
<dees-input-checkbox
|
|
.key=${'enableSPF'}
|
|
.label=${'Enable SPF checking'}
|
|
.value=${true}
|
|
></dees-input-checkbox>
|
|
<dees-input-checkbox
|
|
.key=${'enableDKIM'}
|
|
.label=${'Enable DKIM validation'}
|
|
.value=${true}
|
|
></dees-input-checkbox>
|
|
<dees-input-checkbox
|
|
.key=${'enableDMARC'}
|
|
.label=${'Enable DMARC policy enforcement'}
|
|
.value=${true}
|
|
></dees-input-checkbox>
|
|
<dees-input-checkbox
|
|
.key=${'enableSpamFilter'}
|
|
.label=${'Enable spam filtering'}
|
|
.value=${true}
|
|
></dees-input-checkbox>
|
|
</dees-form>
|
|
<dees-button
|
|
class="actionButton"
|
|
type="highlighted"
|
|
@click=${() => this.saveEmailSecuritySettings()}
|
|
>
|
|
Save Settings
|
|
</dees-button>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
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;
|
|
score -= metrics.blockedIPs.length * 2;
|
|
score -= metrics.authenticationFailures * 1;
|
|
score -= metrics.spamDetected * 0.5;
|
|
score -= metrics.malwareDetected * 3;
|
|
score -= metrics.phishingDetected * 3;
|
|
score -= metrics.suspiciousActivities * 2;
|
|
return Math.max(0, Math.min(100, Math.round(score)));
|
|
}
|
|
|
|
private getSecurityEvents(metrics: any): any[] {
|
|
// Mock data - in real implementation, this would come from the server
|
|
return [
|
|
{
|
|
timestamp: Date.now() - 1000 * 60 * 5,
|
|
event: 'Multiple failed login attempts',
|
|
severity: 'warning',
|
|
details: 'IP: 192.168.1.100',
|
|
},
|
|
{
|
|
timestamp: Date.now() - 1000 * 60 * 15,
|
|
event: 'SPF check failed',
|
|
severity: 'medium',
|
|
details: 'Domain: example.com',
|
|
},
|
|
{
|
|
timestamp: Date.now() - 1000 * 60 * 30,
|
|
event: 'IP blocked due to spam',
|
|
severity: 'high',
|
|
details: 'IP: 10.0.0.1',
|
|
},
|
|
];
|
|
}
|
|
|
|
private async clearBlockedIPs() {
|
|
console.log('Clear blocked IPs');
|
|
}
|
|
|
|
private async unblockIP(ip: string) {
|
|
console.log('Unblock IP:', ip);
|
|
}
|
|
|
|
private async saveEmailSecuritySettings() {
|
|
console.log('Save email security settings');
|
|
}
|
|
} |