diff --git a/backend/src/routes/dashboard.ts b/backend/src/routes/dashboard.ts index 5bc7e1b2..12c192af 100644 --- a/backend/src/routes/dashboard.ts +++ b/backend/src/routes/dashboard.ts @@ -1,6 +1,7 @@ import { Router, type Request, type Response } from 'express'; import { DatabaseService, type StackRestartSummary } from '../services/DatabaseService'; import { CloudBackupService } from '../services/CloudBackupService'; +import TrivyService from '../services/TrivyService'; import { effectiveTier } from '../middleware/tierGates'; import { isDebugEnabled } from '../utils/debug'; import type { LicenseTier } from '../services/license-types'; @@ -18,6 +19,7 @@ export interface ConfigurationStatus { agents: { discord: AgentStatus; slack: AgentStatus; webhook: AgentStatus }; alertRules: number; routingRules: { count: number; enabledCount: number; locked: boolean }; + suppressionRules: { total: number; enabledCount: number }; }; automation: { autoHeal: { total: number; enabled: number }; @@ -29,6 +31,7 @@ export interface ConfigurationStatus { mfaEnabled: boolean | null; ssoEnabled: boolean; ssoProvider: string | null; + trivyInstalled: boolean; scanPolicies: { total: number; enabled: number; locked: boolean }; }; thresholds: { @@ -101,6 +104,10 @@ export function buildLocalConfigurationStatus( enabledCount: notifRoutes.filter(r => r.enabled).length, locked: false, }, + suppressionRules: (() => { + const rules = db.getNotificationSuppressionRules(); + return { total: rules.length, enabledCount: rules.filter(r => r.enabled).length }; + })(), }, automation: { autoHeal: { @@ -128,6 +135,7 @@ export function buildLocalConfigurationStatus( mfaEnabled: mfaRow ? mfaRow.enabled === 1 : null, ssoEnabled: !!enabledSso, ssoProvider: enabledSso?.provider ?? null, + trivyInstalled: TrivyService.getInstance().getSource() !== 'none', // Scan policies are available on every tier. scanPolicies: { total: scanPolicies.length, diff --git a/frontend/src/components/dashboard/ConfigurationStatus.tsx b/frontend/src/components/dashboard/ConfigurationStatus.tsx index 947f5409..98b59ca6 100644 --- a/frontend/src/components/dashboard/ConfigurationStatus.tsx +++ b/frontend/src/components/dashboard/ConfigurationStatus.tsx @@ -141,7 +141,7 @@ export function ConfigurationStatus({ onOpenSection }: ConfigurationStatusProps
- + {!notifications.routingRules.locked && ( )} + + {!security.scanPolicies.locked && ( window.dispatchEvent( new CustomEvent(SENCHO_NAVIGATE_EVENT, { detail: { view: 'security', tab: 'policies' } }), diff --git a/frontend/src/components/dashboard/__tests__/ConfigurationStatus.test.tsx b/frontend/src/components/dashboard/__tests__/ConfigurationStatus.test.tsx index 9ac390f7..f2e08194 100644 --- a/frontend/src/components/dashboard/__tests__/ConfigurationStatus.test.tsx +++ b/frontend/src/components/dashboard/__tests__/ConfigurationStatus.test.tsx @@ -20,6 +20,7 @@ function makePayload(overrides: Partial = {}): Confi }, alertRules: 0, routingRules: { count: 0, enabledCount: 0, locked: true }, + suppressionRules: { total: 0, enabledCount: 0 }, }, automation: { autoHeal: { total: 0, enabled: 0 }, @@ -31,6 +32,7 @@ function makePayload(overrides: Partial = {}): Confi mfaEnabled: null, ssoEnabled: false, ssoProvider: null, + trivyInstalled: false, scanPolicies: { total: 0, enabled: 0, locked: false }, }, thresholds: { cpuLimit: 90, ramLimit: 90, diskLimit: 90, dockerJanitorGb: 5, globalCrash: false, hostAlertsEnabled: true }, @@ -68,11 +70,11 @@ describe('ConfigurationStatus row visibility', () => { expect(screen.getByText('Auto-heal policies')).toBeDefined(); expect(screen.getByText('Auto-update schedules')).toBeDefined(); // Rows whose payload reports locked stay hidden. - expect(screen.queryByText('Notification routing')).toBeNull(); + expect(screen.queryByText('Routing')).toBeNull(); expect(screen.queryByText('Webhooks')).toBeNull(); expect(screen.queryByText('Scheduled tasks')).toBeNull(); - // Scan policies are free, so the Vulnerability scanning row renders. - expect(screen.getByText('Vulnerability scanning')).toBeDefined(); + // Scan policies are free, so the row renders. + expect(screen.getByText('Scan policies')).toBeDefined(); // Cloud Backup row is universal (Custom S3 is open to every tier). expect(screen.getByText('Cloud Backup')).toBeDefined(); }); @@ -89,6 +91,7 @@ describe('ConfigurationStatus row visibility', () => { }, alertRules: 2, routingRules: { count: 1, enabledCount: 1, locked: false }, + suppressionRules: { total: 0, enabledCount: 0 }, }, automation: { autoHeal: { total: 3, enabled: 2 }, @@ -100,6 +103,7 @@ describe('ConfigurationStatus row visibility', () => { mfaEnabled: true, ssoEnabled: true, ssoProvider: 'oidc_google', + trivyInstalled: true, scanPolicies: { total: 2, enabled: 2, locked: false }, }, }), @@ -110,10 +114,10 @@ describe('ConfigurationStatus row visibility', () => { expect(screen.getByText('Automation')).toBeDefined(); expect(screen.getByText('Auto-heal policies')).toBeDefined(); expect(screen.getByText('Auto-update schedules')).toBeDefined(); - expect(screen.getByText('Notification routing')).toBeDefined(); + expect(screen.getByText('Routing')).toBeDefined(); expect(screen.getByText('Webhooks')).toBeDefined(); expect(screen.getByText('Scheduled tasks')).toBeDefined(); - expect(screen.getByText('Vulnerability scanning')).toBeDefined(); + expect(screen.getByText('Scan policies')).toBeDefined(); expect(screen.getByText('Cloud Backup')).toBeDefined(); // SSO label maps the provider to a friendly name. expect(screen.getByText('Google')).toBeDefined(); diff --git a/frontend/src/components/dashboard/useConfigurationStatus.ts b/frontend/src/components/dashboard/useConfigurationStatus.ts index ea94c0f9..960ed3cc 100644 --- a/frontend/src/components/dashboard/useConfigurationStatus.ts +++ b/frontend/src/components/dashboard/useConfigurationStatus.ts @@ -18,6 +18,7 @@ export interface ConfigurationStatus { agents: { discord: AgentStatus; slack: AgentStatus; webhook: AgentStatus }; alertRules: number; routingRules: { count: number; enabledCount: number; locked: boolean }; + suppressionRules: { total: number; enabledCount: number }; }; automation: { autoHeal: { total: number; enabled: number }; @@ -29,6 +30,7 @@ export interface ConfigurationStatus { mfaEnabled: boolean | null; ssoEnabled: boolean; ssoProvider: string | null; + trivyInstalled: boolean; scanPolicies: { total: number; enabled: number; locked: boolean }; }; thresholds: {