Skip to content
146 linesCodeBlameRaw

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 { FileSearch } from "lucide-react";
2import { Form, useNavigation, useSearchParams } from "react-router";
3
4import type { AlertState, Severity } from "@g1t/contracts";
5
6import type { Route } from "./+types/security-code";
7import { page } from "../../lib/meta";
8import { StateFilter } from "../../components/security";
9import { ActivationPrompt, CARD, CodeAlertItem, FilterSelect, LIST, SectionHeader } from "../../components/security-suite";
10import { TimeAgo } from "../../components/ui";
11import { securitySuite } from "../../lib/services.server";
Merge main (membership, two-factor, GitHub repo roles) into tokens12import { getViewer, managesSecurity, requireUser, unwrap } from "../../lib/session.server";
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar13import { requireInsider } from "../../lib/access.server";
14import { activationPrice } from "../../lib/security-suite.server";
15import { codeFilters, countStates, keepCode } from "../../lib/security-suite";
16
17export function meta({ params, ...args }: Route.MetaArgs) {
18 return page(args, { title: `Code scanning · ${params.owner}/${params.repo} · g1t` });
19}
20
21export async function loader({ params, context, request }: Route.LoaderArgs) {
22 const viewer = getViewer(context) ?? requireUser(context, request);
Merge main (membership, two-factor, GitHub repo roles) into tokens23 const { access } = await requireInsider(context, params, "security_alerts");
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar24 const repo = { namespace: params.owner, name: params.repo };
25 const [scanning, price] = await Promise.all([securitySuite.codeScanning(repo, viewer), activationPrice(params.owner, viewer)]);
Merge main (membership, two-factor, GitHub repo roles) into tokens26 return { scanning: unwrap(scanning), price, can: access.can, owner: managesSecurity(viewer, params.owner) };
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar27}
28
29const SEVERITY_OPTIONS: [string, string][] = [
30 ["all", "Any severity"],
31 ["critical", "Critical"],
32 ["high", "High"],
33 ["medium", "Medium"],
34 ["low", "Low"],
35];
36
37export default function CodeScanning({ loaderData, params }: Route.ComponentProps) {
38 const { scanning, price, can, owner } = loaderData;
39 const base = `/${params.owner}/${params.repo}`;
40 const [search, setSearch] = useSearchParams();
41 const navigation = useNavigation();
42 const filters = codeFilters(search);
43 const tools = [...new Set(scanning.alerts.map((alert) => alert.tool))].sort();
44 const counts = countStates(scanning.alerts.filter((alert) => keepCode(alert, { ...filters, state: alert.state })));
45 const shown = scanning.alerts.filter((alert) => keepCode(alert, filters));
46 const set = (key: string, value: string | null) => {
47 const next = new URLSearchParams(search);
48 if (value) next.set(key, value);
49 else next.delete(key);
50 setSearch(next, { replace: true, preventScrollReset: true });
51 };
52 const empty = scanning.alerts.length === 0 && scanning.analyses.length === 0;
53 const settingUp = navigation.state !== "idle" && navigation.formAction?.endsWith("/code-scanning/setup");
54 return (
55 <div className="max-w-5xl space-y-8">
56 <SectionHeader
57 title="Code scanning"
58 about="Results from your static analysis tools, uploaded as SARIF. On the default branch each problem is an alert, fixed when a later analysis no longer reports it. On a pull request, new results on the lines it changes become review comments and the Code scanning check."
59 actions={
60 scanning.entitled && can.manage_settings && !scanning.configured ? (
61 <Form method="post" action={`${base}/security/code-scanning/setup`}>
You choose how g1t looks: Auto follows your system, Light and Dark stay put, set from the account menu, the phone's You and help sheet, Settings → Account or the command palette; the page is drawn in your choice from the first byte, every colour has a light value that reads at 4.5:1 or better, code and diffs are highlighted for both, diagrams and the document editor follow along, the product tour and the g1t mark's avatar stay dark, and Dark is what you see until you choose; the workspaces guide says how.62 <button type="submit" disabled={settingUp} className="rounded-md bg-fg px-3.5 py-2 text-sm font-medium text-bg hover:bg-fg-hover disabled:opacity-50">
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar63 {settingUp ? "Opening a pull request…" : "Set up code scanning"}
64 </button>
65 </Form>
66 ) : null
67 }
68 />
69 {!scanning.entitled ? (
70 <ActivationPrompt workspace={params.owner} feature="Code scanning" monthlyCents={price} isOwner={owner} />
71 ) : empty ? (
72 <div className={`${CARD} px-6 py-10 text-center`}>
73 <FileSearch size={22} className="mx-auto text-accent" />
74 <p className="mt-2 font-medium">No analyses yet</p>
75 <p className="mx-auto mt-1 max-w-xl text-sm text-muted">
76 {scanning.configured
77 ? "The code scanning workflow is on the default branch; its first run's results show here."
78 : "Set up code scanning to open a pull request adding a workflow that scans each language the repository has (Bandit, gosec, ESLint, Clippy) and uploads the results, or upload SARIF from any tool with POST /repos/{owner}/{name}/code-scanning/sarifs."}
79 </p>
80 </div>
81 ) : (
82 <section className="space-y-3">
83 <div className="flex flex-wrap items-end justify-between gap-3">
84 <StateFilter counts={counts} value={filters.state} onChange={(state: AlertState) => set("state", state === "open" ? null : state)} />
85 <div className="flex flex-wrap gap-2">
86 <FilterSelect label="Severity" value={filters.severity ?? "all"} options={SEVERITY_OPTIONS} onChange={(value) => set("severity", value === "all" ? null : (value as Severity))} />
87 <FilterSelect label="Tool" value={filters.tool ?? "all"} options={[["all", "Every tool"], ...tools.map((tool): [string, string] => [tool, tool])]} onChange={(value) => set("tool", value === "all" ? null : value)} />
88 </div>
89 </div>
90 {shown.length === 0 ? (
91 <p className="rounded-xl border border-dashed border-line px-4 py-6 text-sm text-muted">No {filters.state} alerts match.</p>
92 ) : (
93 <ul className={LIST}>
94 {shown.map((alert) => (
95 <CodeAlertItem key={alert.id} alert={alert} base={base} />
96 ))}
97 </ul>
98 )}
99 </section>
100 )}
101 {scanning.analyses.length > 0 && (
102 <section>
103 <h3 className="text-base font-semibold tracking-tight">Recent analyses</h3>
104 <div className={`${CARD} mt-2 overflow-x-auto`}>
105 <table className="w-full min-w-[36rem] text-sm">
106 <thead className="text-left text-xs text-muted">
107 <tr className="border-b border-line">
108 <th className="px-4 py-2 font-medium">Tool</th>
109 <th className="px-3 py-2 font-medium">Ref</th>
110 <th className="px-3 py-2 font-medium">Commit</th>
111 <th className="px-3 py-2 text-right font-medium">Results</th>
112 <th className="px-3 py-2 text-right font-medium">New</th>
113 <th className="px-3 py-2 text-right font-medium">Fixed</th>
114 <th className="px-3 py-2 font-medium">When</th>
115 </tr>
116 </thead>
117 <tbody className="tabular-nums">
118 {scanning.analyses.slice(0, 15).map((analysis) => (
119 <tr key={analysis.id} className="border-b border-line last:border-0">
120 <td className="px-4 py-2">{analysis.tool}</td>
121 <td className="px-3 py-2 font-mono text-xs">
122 {analysis.pull != null ? (
123 <a href={`${base}/security/pulls/${analysis.pull}`} className="hover:underline">
124 #{analysis.pull}
125 </a>
126 ) : (
127 analysis.gitRef.replace("refs/heads/", "")
128 )}
129 </td>
130 <td className="px-3 py-2 font-mono text-xs">{analysis.commitSha.slice(0, 7)}</td>
131 <td className="px-3 py-2 text-right">{analysis.results}</td>
132 <td className="px-3 py-2 text-right">{analysis.newAlerts}</td>
133 <td className="px-3 py-2 text-right">{analysis.fixedAlerts}</td>
134 <td className="px-3 py-2 text-xs text-muted">
135 <TimeAgo at={analysis.createdAt} />
136 </td>
137 </tr>
138 ))}
139 </tbody>
140 </table>
141 </div>
142 </section>
143 )}
144 </div>
145 );
146}

This file's history is long; its oldest lines are credited to the oldest commit read.