feat(web-ui): reorganize dashboard views into grouped navigation with new email, access, and network subviews

This commit is contained in:
2026-04-08 08:24:55 +00:00
parent 00fdadb088
commit 2325f01cde
31 changed files with 214 additions and 378 deletions

View File

@@ -1,5 +1,3 @@
export * from './ops-view-security.js';
export * from './ops-view-security-overview.js';
export * from './ops-view-security-blocked.js';
export * from './ops-view-security-authentication.js';
export * from './ops-view-security-emailsecurity.js';

View File

@@ -1,4 +1,5 @@
import * as appstate from '../../appstate.js';
import { viewHostCss } from '../shared/css.js';
import {
DeesElement,
@@ -34,8 +35,8 @@ export class OpsViewSecurityAuthentication extends DeesElement {
public static styles = [
cssManager.defaultStyles,
viewHostCss,
css`
:host { display: block; }
h2 {
margin: 32px 0 16px 0;
font-size: 24px;

View File

@@ -1,4 +1,5 @@
import * as appstate from '../../appstate.js';
import { viewHostCss } from '../shared/css.js';
import {
DeesElement,
@@ -34,8 +35,8 @@ export class OpsViewSecurityBlocked extends DeesElement {
public static styles = [
cssManager.defaultStyles,
viewHostCss,
css`
:host { display: block; }
dees-statsgrid {
margin-bottom: 32px;
}

View File

@@ -1,159 +0,0 @@
import * as appstate from '../../appstate.js';
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-emailsecurity': OpsViewSecurityEmailsecurity;
}
}
@customElement('ops-view-security-emailsecurity')
export class OpsViewSecurityEmailsecurity 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,
css`
:host { display: block; }
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;
}
.actionButton {
margin-top: 16px;
}
`,
];
public render(): TemplateResult {
const metrics = this.statsState.securityMetrics;
if (!metrics) {
return html`
<div class="loadingMessage">
<p>Loading security metrics...</p>
</div>
`;
}
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`
<dees-heading level="hr">Email Security</dees-heading>
<dees-statsgrid
.tiles=${tiles}
.minTileWidth=${200}
></dees-statsgrid>
<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 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.');
}
}

View File

@@ -1,4 +1,5 @@
import * as appstate from '../../appstate.js';
import { viewHostCss } from '../shared/css.js';
import {
DeesElement,
@@ -34,8 +35,8 @@ export class OpsViewSecurityOverview extends DeesElement {
public static styles = [
cssManager.defaultStyles,
viewHostCss,
css`
:host { display: block; }
h2 {
margin: 32px 0 16px 0;
font-size: 24px;

View File

@@ -1,114 +0,0 @@
import * as appstate from '../../appstate.js';
import { appRouter } from '../../router.js';
import { viewHostCss } from '../shared/css.js';
import {
DeesElement,
customElement,
html,
state,
css,
cssManager,
type TemplateResult,
} from '@design.estate/dees-element';
// Side-effect imports register the subview custom elements
import './ops-view-security-overview.js';
import './ops-view-security-blocked.js';
import './ops-view-security-authentication.js';
import './ops-view-security-emailsecurity.js';
declare global {
interface HTMLElementTagNameMap {
'ops-view-security': OpsViewSecurity;
}
}
type TSecurityTab = 'overview' | 'blocked' | 'authentication' | 'emailsecurity';
@customElement('ops-view-security')
export class OpsViewSecurity extends DeesElement {
@state()
accessor selectedTab: TSecurityTab = 'overview';
private tabLabelMap: Record<TSecurityTab, string> = {
'overview': 'Overview',
'blocked': 'Blocked IPs',
'authentication': 'Authentication',
'emailsecurity': 'Email Security',
};
private labelToTab: Record<string, TSecurityTab> = {
'Overview': 'overview',
'Blocked IPs': 'blocked',
'Authentication': 'authentication',
'Email Security': 'emailsecurity',
};
private static isSecurityTab(s: string | null): s is TSecurityTab {
return s === 'overview' || s === 'blocked' || s === 'authentication' || s === 'emailsecurity';
}
constructor() {
super();
// Read initial subview from state (URL-driven)
const initialState = appstate.uiStatePart.getState()!;
if (OpsViewSecurity.isSecurityTab(initialState.activeSubview)) {
this.selectedTab = initialState.activeSubview;
}
// Subscribe to future changes (back/forward navigation, direct URL entry)
const sub = appstate.uiStatePart.select((s) => s.activeSubview).subscribe((sub) => {
if (OpsViewSecurity.isSecurityTab(sub) && sub !== this.selectedTab) {
this.selectedTab = sub;
}
});
this.rxSubscriptions.push(sub);
}
async firstUpdated() {
const toggle = this.shadowRoot!.querySelector('dees-input-multitoggle') as any;
if (toggle) {
const sub = toggle.changeSubject.subscribe(() => {
const tab = this.labelToTab[toggle.selectedOption];
if (tab && tab !== this.selectedTab) {
// Push URL → router updates state → subscription updates selectedTab
appRouter.navigateToView('security', tab);
}
});
this.rxSubscriptions.push(sub);
}
}
public static styles = [
cssManager.defaultStyles,
viewHostCss,
css`
dees-input-multitoggle {
margin-bottom: 24px;
}
`,
];
public render(): TemplateResult {
return html`
<dees-heading level="2">Security</dees-heading>
<dees-input-multitoggle
.type=${'single'}
.options=${['Overview', 'Blocked IPs', 'Authentication', 'Email Security']}
.selectedOption=${this.tabLabelMap[this.selectedTab]}
></dees-input-multitoggle>
${this.renderTabContent()}
`;
}
private renderTabContent(): TemplateResult {
switch (this.selectedTab) {
case 'overview': return html`<ops-view-security-overview></ops-view-security-overview>`;
case 'blocked': return html`<ops-view-security-blocked></ops-view-security-blocked>`;
case 'authentication': return html`<ops-view-security-authentication></ops-view-security-authentication>`;
case 'emailsecurity': return html`<ops-view-security-emailsecurity></ops-view-security-emailsecurity>`;
}
}
}