| 1 | | − | import { ChevronRight, ShieldCheck } from "lucide-react"; |
| 1 | + | import { ChevronRight } from "lucide-react"; |
| 2 | 2 | | import { Link, data } from "react-router"; |
| 3 | 3 | | |
| 4 | 4 | | import { SEVERITIES, type SeverityCounts } from "@g1t/contracts"; |
| ⋯ |
| 8 | 8 | | import { SeverityCountsGrid, SeverityCountsInline } from "../../components/security"; |
| 9 | 9 | | import { TimeAgo } from "../../components/ui"; |
| 10 | 10 | | import { Badge } from "../../components/ui/badge"; |
| 11 | | − | import { security } from "../../lib/services.server"; |
| 11 | + | import { repos, security } from "../../lib/services.server"; |
| 12 | 12 | | import { getViewer, roleIn, unwrap } from "../../lib/session.server"; |
| 13 | 13 | | |
| 14 | 14 | | export function meta({ params, ...args }: Route.MetaArgs) { |
| ⋯ |
| 18 | 18 | | export async function loader({ params, context }: Route.LoaderArgs) { |
| 19 | 19 | | const viewer = getViewer(context); |
| 20 | 20 | | if (!roleIn(viewer, params.owner)) throw data(null, { status: 404 }); |
| 21 | | − | const projects = unwrap(await security.workspace(params.owner, viewer)); |
| 21 | + | const [scanned, current] = await Promise.all([ |
| 22 | + | security.workspace(params.owner, viewer), |
| 23 | + | repos.list(viewer, { namespace: params.owner.toLowerCase() }), |
| 24 | + | ]); |
| 25 | + | // Only repositories that are still there: a deleted one's alerts stay |
| 26 | + | // with it for its 30 days, but not on this page. |
| 27 | + | const live = new Set(current.map((repo) => repo.id)); |
| 28 | + | const projects = unwrap(scanned).filter((project) => live.has(project.repoId)); |
| 22 | 29 | | const total = Object.fromEntries(SEVERITIES.map((severity) => [severity, 0])) as SeverityCounts; |
| 23 | 30 | | for (const project of projects) { |
| 24 | 31 | | for (const severity of SEVERITIES) total[severity] += project.counts[severity]; |
| ⋯ |
| 35 | 42 | | const { projects, total } = loaderData; |
| 36 | 43 | | return ( |
| 37 | 44 | | <div className="space-y-6"> |
| 38 | | − | <div> |
| 39 | | − | <h2 className="flex items-center gap-2 text-xl font-semibold tracking-tight"> |
| 40 | | − | <ShieldCheck size={19} className="text-accent" /> |
| 41 | | − | Security across projects |
| 42 | | − | </h2> |
| 43 | | − | <p className="mt-1.5 max-w-2xl text-sm text-muted"> |
| 44 | | − | Open alerts in every project of {params.owner}: secrets found in pushes and history, and vulnerable |
| 45 | | − | dependencies. Each project's Security page has the details and the security update g1t opened for each. |
| 46 | | − | </p> |
| 47 | | − | </div> |
| 48 | 45 | | <div> |
| 49 | 46 | | <SeverityCountsGrid counts={total} /> |
| 50 | 47 | | <p className="mt-2 text-xs text-faint"> |