Skip to content

g1t/apps/web/app/routes/repo/security-code-alert.tsx

154 lines7,560 bytesCodeBlame

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1import { ArrowLeft, ExternalLink } from "lucide-react";
2import ReactMarkdown from "react-markdown";
3import { Link } from "react-router";
4
5import { CODE_DISMISS_REASONS, type DismissReason, dismissLabel } from "@g1t/contracts";
6
7import type { Route } from "./+types/security-code-alert";
8import { page } from "../../lib/meta";
9import { DismissDialog, ReopenButton, SeverityBadge } from "../../components/security";
10import { CARD, FixWithG1t, shortRule } from "../../components/security-suite";
11import { TimeAgo } from "../../components/ui";
12import { Badge } from "../../components/ui/badge";
13import { securitySuite } from "../../lib/services.server";
14import { assertSameOrigin, getViewer, requireUser, unwrap } from "../../lib/session.server";
15import { refusal, requireInsider } from "../../lib/access.server";
16
17export function meta({ params, ...args }: Route.MetaArgs) {
18 return page(args, { title: `Code scanning alert #${params.number} · ${params.owner}/${params.repo} · g1t` });
19}
20
21export async function loader({ params, context, request }: Route.LoaderArgs) {
22 const viewer = getViewer(context) ?? requireUser(context, request);
23 const { access } = await requireInsider(context, params, "push");
24 const repo = { namespace: params.owner, name: params.repo };
25 return { detail: unwrap(await securitySuite.codeAlert(repo, Number(params.number) || 0, viewer)), can: access.can };
26}
27
28const REASONS = new Set(["false_positive", "wont_fix", "used_in_tests"]);
29
30export async function action({ params, context, request }: Route.ActionArgs) {
31 assertSameOrigin(request);
32 const user = requireUser(context, request);
33 const repo = { namespace: params.owner, name: params.repo };
34 const form = await request.formData();
35 const intent = String(form.get("intent") ?? "");
36 const refused = await refusal(context, params, intent === "fix" ? "run" : "push");
37 if (refused) return { ok: false, error: refused };
38 const number = Number(params.number) || 0;
39 if (intent === "dismiss") {
40 const reason = String(form.get("reason") ?? "");
41 if (!REASONS.has(reason)) return { ok: false, error: "Choose a reason." };
42 const done = await securitySuite.setCodeAlertState(user, repo, number, "dismissed", reason as DismissReason, String(form.get("comment") ?? "").trim().slice(0, 500));
43 return done.ok ? { ok: true } : { ok: false, error: done.error.message };
44 }
45 if (intent === "reopen") {
46 const done = await securitySuite.setCodeAlertState(user, repo, number, "open", null, "");
47 return done.ok ? { ok: true } : { ok: false, error: done.error.message };
48 }
49 if (intent === "fix") {
50 const done = await securitySuite.fixAlert(user, repo, String(form.get("id") ?? ""));
51 return done.ok ? { ok: true, issue: done.value.issue, message: done.value.message } : { ok: false, error: done.error.message };
52 }
53 return { ok: false, error: "Unknown action." };
54}
55
56export default function CodeAlertPage({ loaderData, params }: Route.ComponentProps) {
57 const { detail, can } = loaderData;
58 const { alert } = detail;
59 const base = `/${params.owner}/${params.repo}`;
60 const action = `${base}/security/code-scanning/${alert.number}`;
61 const where = alert.path ? `${alert.path}${alert.startLine ? `:${alert.startLine}` : ""}` : null;
62 return (
63 <div className="max-w-4xl space-y-6">
64 <Link to={`${base}/security/code-scanning`} className="inline-flex items-center gap-1.5 text-sm text-muted hover:text-fg">
65 <ArrowLeft size={14} /> Code scanning
66 </Link>
67 <div className="flex flex-wrap items-start justify-between gap-4">
68 <div className="min-w-0">
69 <h2 className="text-xl font-semibold tracking-tight break-words">{alert.ruleName && alert.ruleName !== alert.ruleId ? alert.ruleName : shortRule(alert.ruleId)}</h2>
70 <p className="mt-1.5 flex flex-wrap items-center gap-2 text-sm text-muted">
71 <span>#{alert.number}</span>
72 <SeverityBadge severity={alert.severity} />
73 <Badge tone={alert.state === "open" ? "warn" : "neutral"}>{alert.state === "open" ? "Open" : alert.state === "fixed" ? "Fixed" : "Dismissed"}</Badge>
74 {alert.dismissedReason && <Badge>{dismissLabel(alert.dismissedReason)}</Badge>}
75 <span>{alert.tool}</span>
76 </p>
77 </div>
78 {can.push && (
79 <div className="flex items-center gap-2">
80 {alert.state === "open" && can.run && <FixWithG1t id={alert.id} action={action} issue={alert.issue} base={base} />}
81 {alert.state === "open" ? (
82 <DismissDialog id={alert.id} title={`Dismiss alert #${alert.number}`} detail={`${where ?? alert.ruleId}`} reasons={CODE_DISMISS_REASONS} action={action} />
83 ) : alert.state === "dismissed" ? (
84 <ReopenButton id={alert.id} action={action} />
85 ) : null}
86 </div>
87 )}
88 </div>
89
90 <section className={`${CARD} p-4`}>
91 <p className="text-sm">{alert.message}</p>
92 {where && (
93 <p className="mt-2 font-mono text-xs">
94 <Link to={`${base}/blob/${alert.lastCommit}/${alert.path}#L${alert.startLine ?? 1}`} className="text-fg-soft hover:underline">
95 {where}
96 </Link>
97 <span className="text-faint"> at {alert.lastCommit.slice(0, 7)}</span>
98 </p>
99 )}
100 <p className="mt-3 flex flex-wrap gap-x-4 gap-y-1 text-xs text-faint">
101 <span className="font-mono break-all">{alert.ruleId}</span>
102 <span>
103 first found <TimeAgo at={alert.createdAt} />
104 </span>
105 {alert.fixedAt && (
106 <span>
107 fixed <TimeAgo at={alert.fixedAt} />
108 </span>
109 )}
110 {alert.tags.length > 0 && <span>{alert.tags.slice(0, 6).join(", ")}</span>}
111 </p>
112 {alert.dismissedBy && (
113 <p className="mt-2 text-sm text-muted">
114 Dismissed by {alert.dismissedBy}
115 {alert.dismissedComment && <>: “{alert.dismissedComment}”</>}
116 </p>
117 )}
118 </section>
119
120 {(alert.help || alert.ruleDescription) && (
121 <section>
122 <h3 className="text-base font-semibold tracking-tight">About this rule</h3>
123 <div className="prose-g1t mt-2 text-sm text-muted [&_a]:underline [&_p]:mt-2">
124 <ReactMarkdown>{alert.help ?? alert.ruleDescription ?? ""}</ReactMarkdown>
125 </div>
126 {alert.helpUri && (
127 <a href={alert.helpUri} className="mt-2 inline-flex items-center gap-1 text-sm text-muted hover:text-fg" rel="noreferrer">
128 The rule's documentation <ExternalLink size={12} />
129 </a>
130 )}
131 </section>
132 )}
133
134 {detail.analyses.length > 0 && (
135 <section>
136 <h3 className="text-base font-semibold tracking-tight">Reported by</h3>
137 <ul className={`${CARD} mt-2 divide-y divide-line`}>
138 {detail.analyses.map((analysis) => (
139 <li key={analysis.id} className="flex flex-wrap items-center gap-x-3 gap-y-1 px-4 py-2.5 text-sm">
140 <span>{analysis.tool}</span>
141 <span className="font-mono text-xs text-muted">
142 {analysis.pull != null ? `pull request #${analysis.pull}` : analysis.gitRef.replace("refs/heads/", "")} · {analysis.commitSha.slice(0, 7)}
143 </span>
144 <span className="text-xs text-faint">
145 <TimeAgo at={analysis.createdAt} />
146 </span>
147 </li>
148 ))}
149 </ul>
150 </section>
151 )}
152 </div>
153 );
154}