import { createFileRoute } from "@tanstack/react-router"; import { useState } from "react"; import { changeAdvisoryState, commentOnAdvisory, editAdvisory, getAdvisory, updateAdvisoryCollaborator, } from "~/fns/advisories"; import { useAction } from "~/hooks/use-action"; import { AdvisoryForm } from "~/components/security/AdvisoryForm"; import type { AdvisoryValues } from "~/components/security/AdvisoryForm"; import { SeverityBadge } from "~/components/security/SeverityBadge"; import { MarkdownEditor } from "~/components/MarkdownEditor"; import { Button } from "~/ui/Button"; import { TextField } from "~/ui/Field"; import { ErrorBanner } from "~/ui/Callout"; import { Avatar } from "~/ui/Avatar"; import { Badge } from "~/ui/Badge"; import { Markdown } from "~/ui/Markdown"; import { Panel } from "~/ui/Panel"; import { RelativeTime } from "~/ui/RelativeTime"; import { Heading } from "~/ui/Heading"; export const Route = createFileRoute("/$owner/$repo/security/advisories/$ghsaId")({ loader: ({ params }) => getAdvisory({ data: { owner: params.owner, repo: params.repo, ghsaId: params.ghsaId } }), component: AdvisoryPage, }); type Advisory = Awaited>; const NEXT_STATES = { triage: [ { state: "draft", label: "Accept as draft" }, { state: "closed", label: "Close report" }, ], draft: [ { state: "published", label: "Publish advisory" }, { state: "closed", label: "Close" }, ], published: [{ state: "closed", label: "Withdraw" }], closed: [{ state: "draft", label: "Reopen as draft" }], } as const; const ECOSYSTEMS = new Set(["npm", "pip", "go", "cargo", "rubygems", "composer", "other"] as const); function ecosystemValue(value: string | null): AdvisoryValues["ecosystem"] { for (const candidate of ECOSYSTEMS) if (candidate === value) return candidate; return null; } function toValues(advisory: Advisory): AdvisoryValues { return { title: advisory.title, description: advisory.description, severity: advisory.severity, cveId: advisory.cveId, cweIds: advisory.cweIds, ecosystem: ecosystemValue(advisory.ecosystem), packageName: advisory.packageName, affectedVersions: advisory.affectedVersions, patchedVersions: advisory.patchedVersions, }; } function Details({ advisory }: { advisory: Advisory }) { const rows: Array<[string, string | null]> = [ ["Package", advisory.packageName ? `${advisory.ecosystem ?? "other"} / ${advisory.packageName}` : null], ["Affected versions", advisory.affectedVersions], ["Patched versions", advisory.patchedVersions], ["CVE", advisory.cveId], ["CWE", advisory.cweIds], ]; return (
{rows.map(([label, value]) => (
{label}
{value ?? "—"}
))}
); } function Collaborators({ advisory }: { advisory: Advisory }) { const params = Route.useParams(); const update = useAction(updateAdvisoryCollaborator); return (

Invited people can read this private advisory and join the discussion.

    {advisory.collaborators.map((name) => (
  • {name}
  • ))}
{ event.preventDefault(); const element = event.currentTarget; const username = String(new FormData(element).get("username") ?? "").trim(); if (await update.run({ data: { ...params, username, add: true } })) element.reset(); }} >
); } function Discussion({ advisory }: { advisory: Advisory }) { const params = Route.useParams(); const comment = useAction(commentOnAdvisory); const [body, setBody] = useState(""); return ( {advisory.canComment && (
{ event.preventDefault(); if (await comment.run({ data: { ...params, body } })) setBody(""); }} > )}
); } function AdvisoryPage() { const advisory = Route.useLoaderData(); const params = Route.useParams(); const [editing, setEditing] = useState(false); const edit = useAction(editAdvisory); const transition = useAction(changeAdvisoryState); const isPrivate = advisory.state !== "published"; return ( <>
{advisory.title} {advisory.state}

{advisory.ghsaId} · {advisory.source === "report" ? "reported" : "opened"}{" "} by {advisory.author ?? "ghost"} {advisory.publishedAt && ( <> {" "} · published )}

{advisory.canManage && (
{NEXT_STATES[advisory.state].map((next) => ( ))}
)} {editing ? ( { if (await edit.run({ data: { ...params, advisory: values } })) setEditing(false); }} /> ) : ( <>
)} {(isPrivate || advisory.canManage) && } {advisory.canManage && isPrivate && } ); }