diff --git a/web/app/(app)/vulnerabilities/page.tsx b/web/app/(app)/vulnerabilities/page.tsx index 6f15535..1ed48d3 100644 --- a/web/app/(app)/vulnerabilities/page.tsx +++ b/web/app/(app)/vulnerabilities/page.tsx @@ -4,7 +4,7 @@ import { useMemo, useState } from "react"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { api, vulnerabilities, type FindingState, type Severity, type VulnFinding } from "@/lib/api"; import { useAuth } from "@/components/AuthProvider"; -import { Button } from "@/components/ui"; +import { Button, Card } from "@/components/ui"; import { AcceptDialog } from "@/components/vulnerabilities/AcceptDialog"; import { DBFreshness } from "@/components/vulnerabilities/DBFreshness"; import { FindingRow } from "@/components/vulnerabilities/FindingRow"; @@ -78,12 +78,12 @@ export default function VulnerabilitiesPage() { const total = SEVERITY_ORDER.reduce((n, s) => n + (counts[s] ?? 0), 0); return ( -
-
+
+
-

Vulnerabilities

+

Vulnerabilities

- Installed packages matched against distribution security advisories. + {total} open finding{total !== 1 ? "s" : ""} · installed packages matched against distribution security advisories

{isAdmin && ( @@ -93,32 +93,36 @@ export default function VulnerabilitiesPage() { )}
- +
+ +
-
+ {/* Severity counts double as the filter. They are always the whole + fleet's open counts, never the filtered view's, so switching + state cannot make the fleet look better than it is. */} +
{SEVERITY_ORDER.map((s) => ( ))} - - {total} open -
-
+
{STATES.map((s) => (
- {groups.isLoading &&

Loading…

} - {groups.error &&

{(groups.error as Error).message}

} - - {groups.data && groups.data.length === 0 && ( -
-

No {state} findings.

-

- Servers report packages hourly. A server whose distribution has no advisory feed is reported as unsupported on its own - page rather than counted here. -

+ {groups.error && ( +
+ {(groups.error as Error).message}
)} -
- {groups.data?.map((g) => ( - unaccept.mutate(f.id)} - onApplyUpdates={(serverId) => applyUpdates.mutate(serverId)} - applying={applyUpdates.isPending ? (applyUpdates.variables as string) : undefined} - /> - ))} -
+ + {groups.isLoading ? ( +
+
+
+ ) : groups.data && groups.data.length > 0 ? ( + groups.data.map((g) => ( + unaccept.mutate(f.id)} + onApplyUpdates={(serverId) => applyUpdates.mutate(serverId)} + applying={applyUpdates.isPending ? (applyUpdates.variables as string) : undefined} + /> + )) + ) : ( +
+

+ No {state} findings{severity ? ` at ${severity} severity` : ""}. +

+

+ Servers report their packages hourly. A server whose distribution has no advisory feed is reported as unsupported + on its own page rather than counted as clean here. +

+
+ )} + {accepting && ( f.fixed_in); return ( -
+ // A row inside the page's one bordered container, not a card of its + // own — the same stack idiom as the monitors and workflows lists. +
{open && ( -
+
{group.findings.map((f) => ( -
+
{serverName(f.server_id)}