feat: dashboard unified widgets for all services
Implement 8 rich dashboard widgets replacing placeholder stat cards: - ThreatScoreWidget: SVG circular gauge (0-100) with color-coded score - AlertFeedWidget: Real-time alert stream with mark-as-read actions - ExposureWidget: DarkWatch exposure summary with run-scan button - VoicePrintWidget: Enrollment/analysis counts with mini bar chart - SpamShieldWidget: Blocked calls/SMS stats with custom rules - HomeTitleWidget: Watched properties and recent changes - RemoveBrokersWidget: Broker registry progress with completion bar - QuickActionsWidget: Shortcut buttons for common tasks Update dashboard route with responsive 2-column grid layout, auto-refresh via 60-second intervals, Suspense boundaries, and skeleton loading states. Update tests for new widget layout.
This commit is contained in:
@@ -3,6 +3,39 @@ import { render } from "solid-js/web";
|
||||
import { MetaProvider } from "@solidjs/meta";
|
||||
import type { JSX } from "solid-js";
|
||||
|
||||
vi.mock("~/lib/api", () => ({
|
||||
api: {
|
||||
correlation: {
|
||||
getStats: { query: vi.fn().mockResolvedValue({ threatScore: 25, totalAlerts: 5, bySeverity: {}, bySource: {}, activeGroups: 2, resolvedCount: 3, falsePositiveCount: 1, threatBreakdown: [] }) },
|
||||
getAlerts: { query: vi.fn().mockResolvedValue({ items: [
|
||||
{ id: "1", title: "New credential leak detected", severity: "HIGH", source: "DARKWATCH", createdAt: new Date().toISOString() },
|
||||
{ id: "2", title: "Suspicious call", severity: "WARNING", source: "SPAMSHIELD", createdAt: new Date().toISOString() },
|
||||
], total: 2, page: 1, limit: 10, totalPages: 1 }) },
|
||||
resolveAlert: { mutate: vi.fn().mockResolvedValue({}) },
|
||||
},
|
||||
darkwatch: {
|
||||
getExposures: { query: vi.fn().mockResolvedValue({ items: [], total: 0, page: 1, limit: 1, totalPages: 0 }) },
|
||||
runScan: { mutate: vi.fn().mockResolvedValue({}) },
|
||||
},
|
||||
voiceprint: {
|
||||
getEnrollments: { query: vi.fn().mockResolvedValue([]) },
|
||||
getAnalyses: { query: vi.fn().mockResolvedValue({ items: [], total: 0, page: 1, limit: 10, totalPages: 0 }) },
|
||||
},
|
||||
spamshield: {
|
||||
getStats: { query: vi.fn().mockResolvedValue({ period: "week", totalDetections: 10, spamCount: 8, notSpamCount: 2, accuracy: 80, activeRules: 3 }) },
|
||||
getRules: { query: vi.fn().mockResolvedValue({ userRules: [], globalRules: [] }) },
|
||||
},
|
||||
hometitle: {
|
||||
getProperties: { query: vi.fn().mockResolvedValue([]) },
|
||||
getAlerts: { query: vi.fn().mockResolvedValue([]) },
|
||||
},
|
||||
removebrokers: {
|
||||
getStats: { query: vi.fn().mockResolvedValue({ total: 0, byStatus: {}, totalListings: 0, listingsRemoved: 0, completionRate: 0 }) },
|
||||
getBrokerRegistry: { query: vi.fn().mockResolvedValue([]) },
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("@solidjs/router", () => ({
|
||||
A: (props: { href?: string; children?: JSX.Element; class?: string; onClick?: () => void }) => (
|
||||
<a href={props.href || "#"} class={props.class} onClick={props.onClick}>
|
||||
@@ -199,14 +232,7 @@ describe("AdsPage", () => {
|
||||
describe("DashboardPage", () => {
|
||||
it("renders dashboard title", () => {
|
||||
mount(() => <DashboardPage />);
|
||||
expect(document.body.textContent).toContain("Overview");
|
||||
});
|
||||
|
||||
it("renders stat cards with data from hooks", () => {
|
||||
mount(() => <DashboardPage />);
|
||||
expect(document.body.textContent).toContain("Plan Tier");
|
||||
expect(document.body.textContent).toContain("Total Alerts");
|
||||
expect(document.body.textContent).toContain("Active Threats");
|
||||
expect(document.body.textContent).toContain("Dashboard");
|
||||
});
|
||||
|
||||
it("renders sidebar with navigation links", () => {
|
||||
@@ -220,20 +246,22 @@ describe("DashboardPage", () => {
|
||||
expect(document.body.textContent).toContain("Settings");
|
||||
});
|
||||
|
||||
it("renders activity feed", () => {
|
||||
it("renders widget headers", async () => {
|
||||
mount(() => <DashboardPage />);
|
||||
expect(document.body.textContent).toContain("Recent Activity");
|
||||
expect(document.body.textContent).toContain("New credential leak detected");
|
||||
expect(document.body.textContent).toContain("VoicePrint scan completed");
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
expect(document.body.textContent).toContain("Threat Score");
|
||||
expect(document.body.textContent).toContain("Alert Feed");
|
||||
expect(document.body.textContent).toContain("Quick Actions");
|
||||
});
|
||||
|
||||
it("renders quick actions", () => {
|
||||
it("renders QuickActionsWidget with action buttons", () => {
|
||||
mount(() => <DashboardPage />);
|
||||
expect(document.body.textContent).toContain("Quick Actions");
|
||||
expect(document.body.textContent).toContain("Run Scan");
|
||||
expect(document.body.textContent).toContain("View Alerts");
|
||||
expect(document.body.textContent).toContain("Add Member");
|
||||
expect(document.body.textContent).toContain("Run Report");
|
||||
expect(document.body.textContent).toContain("Add to Watchlist");
|
||||
expect(document.body.textContent).toContain("Upload Voice Sample");
|
||||
expect(document.body.textContent).toContain("Check Number");
|
||||
expect(document.body.textContent).toContain("Add Property");
|
||||
expect(document.body.textContent).toContain("Start Removal");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user