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.
| One operation mapping, owned by repos; billing reads it instead of keeping its own | 1 | /** |
| 2 | * Security, as a project's page shows it: open alerts by severity, the | |
| 3 | * secrets found in pushes and history, vulnerable dependencies with the | |
| 4 | * security update g1t opened for each, and what happened to every alert. | |
| 5 | * The page posts the intents in `routes/repo/security.tsx`'s action. | |
| 6 | */ | |
| 7 | import { | |
| 8 | Bot, | |
| 9 | CircleAlert, | |
| 10 | CircleCheck, | |
| 11 | CircleDot, | |
| 12 | CircleSlash, | |
| 13 | ExternalLink, | |
| 14 | GitBranch, | |
| 15 | GitMerge, | |
| 16 | GitPullRequest, | |
| 17 | GitPullRequestClosed, | |
| 18 | History, | |
| 19 | KeyRound, | |
| 20 | Loader, | |
| 21 | Package, | |
| A desktop release goes out with what can be built. Linux is built on g1t's machines in every release; Windows and macOS each wait on a repository variable: a machine registered for them, or for Windows a cross build on Linux once aka.ms and download.visualstudio.microsoft.com are workflow-only domains, which is where the first release stopped. A platform with no machine is skipped and the download page says it is coming, with a word for the visitor whose platform this release lacks. The guide and CONTRIBUTING say so. | 22 | PauseCircle, |
| One operation mapping, owned by repos; billing reads it instead of keeping its own | 23 | ShieldAlert, |
| 24 | ShieldCheck, | |
| 25 | } from "lucide-react"; | |
| 26 | import { type ReactNode, useEffect, useRef, useState } from "react"; | |
| 27 | import { Link, useFetcher } from "react-router"; | |
| 28 | ||
| 29 | import { | |
| 30 | type AlertActivity, | |
| 31 | type AlertState, | |
| 32 | DEPENDENCY_DISMISS_REASONS, | |
| 33 | type DismissReason, | |
| 34 | SECRET_DISMISS_REASONS, | |
| 35 | SEVERITIES, | |
| 36 | type PullStatus, | |
| 37 | type SecretFinding, | |
| 38 | type SecurityUpdate, | |
| 39 | type Severity, | |
| 40 | type SeverityCounts, | |
| 41 | type Vulnerability, | |
| 42 | dismissLabel, | |
| 43 | } from "@g1t/contracts"; | |
| 44 | ||
| 45 | import { | |
| 46 | type ActivityEntry, | |
| 47 | type PackageGroup, | |
| 48 | UPDATE_STATES, | |
| 49 | alertActivity, | |
| 50 | groupByPackage, | |
| 51 | highestFix, | |
| 52 | latestUpdate, | |
| 53 | splitSecrets, | |
| 54 | worstSeverity, | |
| 55 | } from "../lib/security-alerts"; | |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 56 | import { TimeAgo } from "./ui"; |
| 57 | import { Avatar } from "./ui/avatar"; | |
| One operation mapping, owned by repos; billing reads it instead of keeping its own | 58 | import { Badge, type BadgeTone } from "./ui/badge"; |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 59 | import { Button } from "./ui/button"; |
| 60 | import { Card } from "./ui/card"; | |
| Merge branch 'worktree-agent-ae1299e92e4462012' | 61 | import { Hint } from "./ui/hint"; |
| One operation mapping, owned by repos; billing reads it instead of keeping its own | 62 | import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "./ui/dialog"; |
| 63 | import { RadioGroup, RadioOption } from "./ui/radio-group"; | |
| 64 | ||
| 65 | type Done = { ok: boolean; error?: string } | undefined; | |
| 66 | ||
| 67 | const SEVERITY: Record<Severity, { label: string; tone: BadgeTone }> = { | |
| 68 | critical: { label: "Critical", tone: "danger" }, | |
| 69 | high: { label: "High", tone: "warn" }, | |
| 70 | medium: { label: "Medium", tone: "merged" }, | |
| 71 | low: { label: "Low", tone: "info" }, | |
| 72 | unknown: { label: "Unrated", tone: "neutral" }, | |
| 73 | }; | |
| 74 | ||
| 75 | ||
| 76 | export function SeverityBadge({ severity }: { severity: Severity }) { | |
| 77 | return <Badge tone={SEVERITY[severity].tone}>{SEVERITY[severity].label}</Badge>; | |
| 78 | } | |
| 79 | ||
| 80 | /** Open alerts by severity, one tile each. */ | |
| 81 | export function SeverityCountsGrid({ counts }: { counts: SeverityCounts }) { | |
| 82 | return ( | |
| 83 | <div className="grid grid-cols-2 gap-3 sm:grid-cols-5"> | |
| 84 | {SEVERITIES.map((severity) => ( | |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 85 | <Card key={severity} className="px-4 py-3"> |
| One operation mapping, owned by repos; billing reads it instead of keeping its own | 86 | <p className="text-xs text-muted">{SEVERITY[severity].label}</p> |
| 87 | <p className={`mt-1 text-2xl font-semibold tabular-nums ${counts[severity] > 0 && severity === "critical" ? "text-danger" : ""}`}> | |
| 88 | {counts[severity]} | |
| 89 | </p> | |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 90 | </Card> |
| One operation mapping, owned by repos; billing reads it instead of keeping its own | 91 | ))} |
| 92 | </div> | |
| 93 | ); | |
| 94 | } | |
| 95 | ||
| 96 | /** A compact row of severity counts, for a list of projects. */ | |
| 97 | export function SeverityCountsInline({ counts }: { counts: SeverityCounts }) { | |
| 98 | const shown = SEVERITIES.filter((severity) => counts[severity] > 0); | |
| 99 | if (shown.length === 0) { | |
| 100 | return ( | |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 101 | <span className="inline-flex items-center gap-1 text-xs text-success"> |
| One operation mapping, owned by repos; billing reads it instead of keeping its own | 102 | <ShieldCheck size={13} /> |
| 103 | Nothing open | |
| 104 | </span> | |
| 105 | ); | |
| 106 | } | |
| 107 | return ( | |
| 108 | <span className="flex flex-wrap gap-1.5"> | |
| 109 | {shown.map((severity) => ( | |
| 110 | <Badge key={severity} tone={SEVERITY[severity].tone}> | |
| 111 | {counts[severity]} {SEVERITY[severity].label.toLowerCase()} | |
| 112 | </Badge> | |
| 113 | ))} | |
| 114 | </span> | |
| 115 | ); | |
| 116 | } | |
| 117 | ||
| 118 | const STATE_FILTERS: { state: AlertState; label: string; icon: ReactNode }[] = [ | |
| 119 | { state: "open", label: "Open", icon: <CircleDot size={14} /> }, | |
| 120 | { state: "dismissed", label: "Dismissed", icon: <CircleSlash size={14} /> }, | |
| 121 | { state: "fixed", label: "Fixed", icon: <CircleCheck size={14} /> }, | |
| 122 | ]; | |
| 123 | ||
| 124 | /** Open, Dismissed and Fixed, with how many alerts each holds. */ | |
| 125 | export function StateFilter({ | |
| 126 | counts, | |
| 127 | value, | |
| 128 | onChange, | |
| 129 | }: { | |
| 130 | counts: Record<AlertState, number>; | |
| 131 | value: AlertState; | |
| 132 | onChange: (state: AlertState) => void; | |
| 133 | }) { | |
| 134 | return ( | |
| 135 | <div role="group" aria-label="Filter alerts" className="flex flex-wrap items-center gap-x-4 gap-y-1 text-sm"> | |
| 136 | {STATE_FILTERS.map(({ state, label, icon }) => ( | |
| 137 | <button | |
| 138 | key={state} | |
| 139 | type="button" | |
| 140 | aria-pressed={value === state} | |
| 141 | onClick={() => onChange(state)} | |
| 142 | className={`inline-flex items-center gap-1.5 transition-colors ${ | |
| 143 | value === state ? "font-medium text-fg" : "text-muted hover:text-fg" | |
| 144 | }`} | |
| 145 | > | |
| 146 | {icon} | |
| 147 | {counts[state]} {label} | |
| 148 | </button> | |
| 149 | ))} | |
| 150 | </div> | |
| 151 | ); | |
| 152 | } | |
| 153 | ||
| 154 | const TEXTAREA = | |
| 155 | "w-full rounded-md border border-line bg-bg px-3 py-2 text-sm outline-none transition-colors placeholder:text-faint hover:border-line-strong focus:border-accent-dim"; | |
| 156 | ||
| 157 | const SMALL_BUTTON = | |
| 158 | "rounded-md border border-line px-2.5 py-1 text-xs font-medium text-muted transition-colors hover:border-line-strong hover:text-fg disabled:opacity-50"; | |
| 159 | ||
| 160 | /** Dismiss an alert with one of `reasons` and an optional comment. */ | |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 161 | export function DismissDialog({ |
| One operation mapping, owned by repos; billing reads it instead of keeping its own | 162 | id, |
| 163 | title, | |
| 164 | detail, | |
| 165 | reasons, | |
| 166 | note, | |
| 167 | action, | |
| 168 | defaultReason, | |
| 169 | trigger = "Dismiss", | |
| 170 | }: { | |
| 171 | id: string; | |
| 172 | title: string; | |
| 173 | /** What the alert is, in a line of code type. */ | |
| 174 | detail: string; | |
| 175 | reasons: { reason: DismissReason; label: string; about: string }[]; | |
| 176 | note?: string; | |
| 177 | action: string; | |
| 178 | defaultReason?: DismissReason; | |
| 179 | trigger?: string; | |
| 180 | }) { | |
| 181 | const fetcher = useFetcher<Done>(); | |
| 182 | const [open, setOpen] = useState(false); | |
| 183 | const [reason, setReason] = useState<string>(defaultReason ?? ""); | |
| 184 | useEffect(() => { | |
| 185 | if (fetcher.state === "idle" && fetcher.data?.ok) setOpen(false); | |
| 186 | }, [fetcher.state, fetcher.data]); | |
| 187 | return ( | |
| 188 | <Dialog | |
| 189 | open={open} | |
| 190 | onOpenChange={(next) => { | |
| 191 | setOpen(next); | |
| 192 | if (next) setReason(defaultReason ?? ""); | |
| 193 | }} | |
| 194 | > | |
| 195 | <DialogTrigger className={SMALL_BUTTON}>{trigger}</DialogTrigger> | |
| 196 | <DialogContent> | |
| 197 | <DialogHeader> | |
| 198 | <DialogTitle>{title}</DialogTitle> | |
| 199 | <DialogDescription> | |
| 200 | Say why it can stay. The alert keeps your name, the reason and your comment, and anyone with access can reopen it. | |
| 201 | </DialogDescription> | |
| 202 | </DialogHeader> | |
| 203 | <fetcher.Form method="post" action={action} className="space-y-4"> | |
| 204 | <input type="hidden" name="intent" value="dismiss" /> | |
| 205 | <input type="hidden" name="id" value={id} /> | |
| 206 | <p className="font-mono text-xs break-all text-muted">{detail}</p> | |
| 207 | <fieldset> | |
| 208 | <legend className="text-xs font-medium text-muted">Reason</legend> | |
| 209 | <RadioGroup name="reason" value={reason} onValueChange={setReason} required className="mt-2 gap-3"> | |
| 210 | {reasons.map((option) => ( | |
| 211 | <RadioOption key={option.reason} value={option.reason} label={option.label} description={option.about} /> | |
| 212 | ))} | |
| 213 | </RadioGroup> | |
| 214 | </fieldset> | |
| 215 | {note && <p className="rounded-md border border-warn/30 bg-warn/5 px-3 py-2 text-xs text-warn">{note}</p>} | |
| 216 | <label className="block"> | |
| 217 | <span className="mb-1.5 block text-xs font-medium text-muted">Comment (optional)</span> | |
| 218 | <textarea name="comment" rows={3} maxLength={500} placeholder="What someone reading this later should know." className={TEXTAREA} /> | |
| 219 | </label> | |
| 220 | <div className="flex justify-end"> | |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 221 | <Button |
| One operation mapping, owned by repos; billing reads it instead of keeping its own | 222 | type="submit" |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 223 | disabled={fetcher.state !== "idle" || !reason}> |
| One operation mapping, owned by repos; billing reads it instead of keeping its own | 224 | {fetcher.state !== "idle" ? "Dismissing…" : "Dismiss alert"} |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 225 | </Button> |
| One operation mapping, owned by repos; billing reads it instead of keeping its own | 226 | </div> |
| 227 | {fetcher.data?.error && <p className="text-sm text-danger">{fetcher.data.error}</p>} | |
| 228 | </fetcher.Form> | |
| 229 | </DialogContent> | |
| 230 | </Dialog> | |
| 231 | ); | |
| 232 | } | |
| 233 | ||
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 234 | export function ReopenButton({ id, action }: { id: string; action: string }) { |
| One operation mapping, owned by repos; billing reads it instead of keeping its own | 235 | const fetcher = useFetcher<Done>(); |
| 236 | return ( | |
| 237 | <span className="flex flex-col items-end gap-1"> | |
| 238 | <button | |
| 239 | type="button" | |
| 240 | disabled={fetcher.state !== "idle"} | |
| 241 | onClick={() => fetcher.submit({ intent: "reopen", id }, { method: "post", action })} | |
| 242 | className={SMALL_BUTTON} | |
| 243 | > | |
| 244 | {fetcher.state !== "idle" ? "Reopening…" : "Reopen"} | |
| 245 | </button> | |
| 246 | {fetcher.data?.error && <span className="text-xs text-danger">{fetcher.data.error}</span>} | |
| 247 | </span> | |
| 248 | ); | |
| 249 | } | |
| 250 | ||
| 251 | /** What happened to an alert, oldest first, folded away until asked for. */ | |
| 252 | function ActivityLog({ entries, base, open }: { entries: ActivityEntry[]; base: string; open?: boolean }) { | |
| 253 | if (entries.length === 0) return null; | |
| 254 | return ( | |
| 255 | <details className="group mt-2" open={open}> | |
| 256 | <summary className="inline-flex cursor-pointer list-none items-center gap-1.5 text-xs text-muted hover:text-fg [&::-webkit-details-marker]:hidden"> | |
| 257 | <History size={12} /> | |
| 258 | <span className="group-open:hidden">Show activity ({entries.length})</span> | |
| 259 | <span className="hidden group-open:inline">Hide activity</span> | |
| 260 | </summary> | |
| 261 | <ol className="mt-2 space-y-2 border-l border-line pl-3"> | |
| 262 | {entries.map((entry) => ( | |
| 263 | <li key={entry.key} className="text-xs"> | |
| 264 | <p className="flex flex-wrap items-center gap-x-1.5 gap-y-1 text-muted"> | |
| 265 | {entry.actor ? ( | |
| 266 | <> | |
| 267 | <Avatar name={entry.actor} size={16} /> | |
| 268 | <span className="font-medium text-fg">{entry.actor}</span> | |
| 269 | </> | |
| 270 | ) : ( | |
| 271 | <CircleDot size={12} className="text-faint" /> | |
| 272 | )} | |
| 273 | <span>{entry.text}</span> | |
| 274 | {entry.ref && ( | |
| 275 | <Link | |
| 276 | to={`${base}/${entry.ref.kind === "pull" ? "pull" : "issues"}/${entry.ref.number}`} | |
| 277 | className="font-medium text-fg-soft hover:text-fg hover:underline" | |
| 278 | > | |
| 279 | #{entry.ref.number} | |
| 280 | </Link> | |
| 281 | )} | |
| 282 | {entry.reason && <Badge>{dismissLabel(entry.reason)}</Badge>} | |
| 283 | <span className="text-faint"> | |
| 284 | <TimeAgo at={entry.at} /> | |
| 285 | </span> | |
| 286 | </p> | |
| 287 | {entry.comment && <p className="mt-1 text-fg-soft wrap-anywhere">“{entry.comment}”</p>} | |
| 288 | </li> | |
| 289 | ))} | |
| 290 | </ol> | |
| 291 | </details> | |
| 292 | ); | |
| 293 | } | |
| 294 | ||
| 295 | function useFocus<T extends HTMLElement>(focused: boolean) { | |
| 296 | const ref = useRef<T>(null); | |
| 297 | useEffect(() => { | |
| 298 | if (focused) ref.current?.scrollIntoView({ block: "center" }); | |
| 299 | }, [focused]); | |
| 300 | return ref; | |
| 301 | } | |
| 302 | ||
| 303 | const FOCUSED = "bg-accent/5 ring-1 ring-accent/40 ring-inset"; | |
| 304 | ||
| 305 | function secretBadge(finding: SecretFinding): { label: string; tone: BadgeTone; about: string } { | |
| 306 | if (finding.state === "dismissed") { | |
| 307 | return { | |
| 308 | label: "Dismissed", | |
| 309 | tone: "neutral", | |
| 310 | about: finding.status === "allowed" ? "Pushes carrying it go through." : "Dismissed.", | |
| 311 | }; | |
| 312 | } | |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 313 | if (finding.state === "fixed") return { label: "Revoked", tone: "success", about: "Revoked or rotated." }; |
| One operation mapping, owned by repos; billing reads it instead of keeping its own | 314 | if (finding.status === "blocked") { |
| 315 | return { label: "Push blocked", tone: finding.testValue ? "neutral" : "warn", about: "A push carrying it was refused, so it never landed." }; | |
| 316 | } | |
| 317 | return { | |
| 318 | label: "In history", | |
| 319 | tone: finding.testValue ? "neutral" : "danger", | |
| 320 | about: "In the repository's history. Rotate it with whoever issued it, then dismiss it as revoked.", | |
| 321 | }; | |
| 322 | } | |
| 323 | ||
| 324 | function SecretItem({ | |
| 325 | finding, | |
| 326 | activity, | |
| 327 | base, | |
| 328 | action, | |
| 329 | focused, | |
| 330 | canDismiss, | |
| 331 | }: { | |
| 332 | finding: SecretFinding; | |
| 333 | activity: AlertActivity[]; | |
| 334 | base: string; | |
| 335 | action: string; | |
| 336 | focused: boolean; | |
| 337 | canDismiss: boolean; | |
| 338 | }) { | |
| 339 | const ref = useFocus<HTMLLIElement>(focused); | |
| 340 | const badge = secretBadge(finding); | |
| 341 | const landed = finding.source === "history" || finding.status === "open"; | |
| 342 | const where = `${finding.path}:${finding.line}`; | |
| 343 | return ( | |
| 344 | <li ref={ref} id={finding.id} className={`flex flex-col gap-3 px-4 py-3 sm:flex-row sm:items-start ${focused ? FOCUSED : ""}`}> | |
| 345 | <KeyRound size={15} className="mt-0.5 hidden shrink-0 text-muted sm:block" /> | |
| 346 | <div className="min-w-0 grow"> | |
| 347 | <div className="flex flex-wrap items-center gap-2"> | |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 348 | <Link to={`${base}/security/secret-scanning/${finding.id}`} className="text-sm font-medium first-letter:uppercase hover:underline"> |
| 349 | {finding.label} | |
| 350 | </Link> | |
| Merge branch 'worktree-agent-ae1299e92e4462012' | 351 | <Hint label={badge.about}> |
| 352 | <Badge tone={badge.tone}>{badge.label}</Badge> | |
| 353 | </Hint> | |
| 354 | {finding.testValue && ( | |
| 355 | <Hint label={finding.testValue}> | |
| 356 | <Badge>Likely test value</Badge> | |
| 357 | </Hint> | |
| 358 | )} | |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 359 | {finding.validity === "active" && ( |
| Merge branch 'worktree-agent-ae1299e92e4462012' | 360 | <Hint label="Its issuer says it still works"> |
| 361 | <Badge tone="danger">Active</Badge> | |
| 362 | </Hint> | |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 363 | )} |
| Merge branch 'worktree-agent-ae1299e92e4462012' | 364 | {finding.validity === "inactive" && ( |
| 365 | <Hint label="Its issuer refused it: revoked or expired"> | |
| 366 | <Badge>Inactive</Badge> | |
| 367 | </Hint> | |
| 368 | )} | |
| 369 | {finding.bypass && ( | |
| 370 | <Hint label={`Bypassed by ${finding.bypass.by}`}> | |
| 371 | <Badge tone="warn">Bypassed</Badge> | |
| 372 | </Hint> | |
| 373 | )} | |
| One operation mapping, owned by repos; billing reads it instead of keeping its own | 374 | {finding.state === "dismissed" && finding.dismissedReason && <Badge>{dismissLabel(finding.dismissedReason)}</Badge>} |
| 375 | </div> | |
| 376 | <p className="mt-1 truncate font-mono text-xs"> | |
| 377 | {landed ? ( | |
| 378 | <Link to={`${base}/blob/${finding.commit}/${finding.path}#L${finding.line}`} className="text-fg-soft hover:text-fg hover:underline"> | |
| 379 | {where} | |
| 380 | </Link> | |
| 381 | ) : ( | |
| 382 | <span className="text-fg-soft">{where}</span> | |
| 383 | )} | |
| 384 | <span className="text-faint"> · {finding.preview}</span> | |
| 385 | </p> | |
| 386 | {finding.testValue && <p className="mt-1 text-xs text-muted">{finding.testValue}</p>} | |
| 387 | <p className="mt-1.5 flex flex-wrap gap-x-3 gap-y-1 text-xs text-faint"> | |
| 388 | <span> | |
| 389 | {finding.source === "push" ? "in a push" : "in history"} | |
| 390 | {finding.foundBy && <> by {finding.foundBy}</>}, commit{" "} | |
| 391 | {landed ? ( | |
| 392 | <Link to={`${base}/commit/${finding.commit}`} className="font-mono hover:text-fg"> | |
| 393 | {finding.commit.slice(0, 7)} | |
| 394 | </Link> | |
| 395 | ) : ( | |
| 396 | <span className="font-mono">{finding.commit.slice(0, 7)}</span> | |
| 397 | )} | |
| 398 | </span> | |
| 399 | <span> | |
| 400 | found <TimeAgo at={finding.foundAt} /> | |
| 401 | </span> | |
| 402 | </p> | |
| 403 | <ActivityLog entries={alertActivity(finding, activity)} base={base} open={focused && finding.state !== "open"} /> | |
| 404 | </div> | |
| 405 | {canDismiss && ( | |
| 406 | <div className="flex shrink-0 items-center gap-1.5"> | |
| 407 | {finding.state === "open" ? ( | |
| 408 | <DismissDialog | |
| 409 | id={finding.id} | |
| 410 | title={`Dismiss ${finding.label}`} | |
| 411 | detail={`${where} · ${finding.preview}`} | |
| 412 | reasons={SECRET_DISMISS_REASONS} | |
| 413 | note={ | |
| 414 | finding.status === "blocked" && !finding.testValue | |
| 415 | ? "Dismissing it lets the same push through, unless you dismiss it as revoked." | |
| 416 | : undefined | |
| 417 | } | |
| 418 | action={action} | |
| 419 | /> | |
| 420 | ) : ( | |
| 421 | <ReopenButton id={finding.id} action={action} /> | |
| 422 | )} | |
| 423 | </div> | |
| 424 | )} | |
| 425 | </li> | |
| 426 | ); | |
| 427 | } | |
| 428 | ||
| 429 | function Empty({ title, children }: { title: string; children: ReactNode }) { | |
| 430 | return ( | |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 431 | <Card tone="plain" className="border-dashed px-6 py-10 text-center"> |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 432 | <ShieldCheck size={22} className="mx-auto text-success" /> |
| One operation mapping, owned by repos; billing reads it instead of keeping its own | 433 | <p className="mt-2 font-medium">{title}</p> |
| 434 | <p className="mt-1 text-sm text-muted">{children}</p> | |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 435 | </Card> |
| One operation mapping, owned by repos; billing reads it instead of keeping its own | 436 | ); |
| 437 | } | |
| 438 | ||
| 439 | const LIST = "divide-y divide-line overflow-hidden rounded-xl border border-line bg-surface"; | |
| 440 | ||
| 441 | /** The secret alerts in one state: on Open, real ones first, then likely test values. */ | |
| 442 | export function SecretsList({ | |
| 443 | secrets, | |
| 444 | state, | |
| 445 | activity, | |
| 446 | base, | |
| 447 | action, | |
| 448 | focus, | |
| 449 | canDismiss, | |
| 450 | }: { | |
| 451 | /** Already filtered to `state`. */ | |
| 452 | secrets: SecretFinding[]; | |
| 453 | state: AlertState; | |
| 454 | activity: AlertActivity[]; | |
| 455 | base: string; | |
| 456 | action: string; | |
| 457 | focus: string | null; | |
| 458 | /** Whether the viewer may dismiss and reopen secret alerts (Admin). */ | |
| 459 | canDismiss: boolean; | |
| 460 | }) { | |
| 461 | const item = (finding: SecretFinding) => ( | |
| 462 | <SecretItem | |
| 463 | key={finding.id} | |
| 464 | finding={finding} | |
| 465 | activity={activity} | |
| 466 | base={base} | |
| 467 | action={action} | |
| 468 | focused={finding.id === focus} | |
| 469 | canDismiss={canDismiss} | |
| 470 | /> | |
| 471 | ); | |
| 472 | if (secrets.length === 0) { | |
| 473 | return state === "open" ? ( | |
| 474 | <Empty title="No open secret alerts"> | |
| 475 | Pushes that add a key or a token are refused before they land, and the history is scanned once in the background. | |
| 476 | </Empty> | |
| 477 | ) : ( | |
| 478 | <Empty title={state === "dismissed" ? "No dismissed secret alerts" : "No revoked secrets"}> | |
| 479 | {state === "dismissed" | |
| 480 | ? "Alerts someone dismissed as a false positive, a test value or accepted are listed here." | |
| 481 | : "Secrets dismissed as revoked are listed here."} | |
| 482 | </Empty> | |
| 483 | ); | |
| 484 | } | |
| 485 | if (state !== "open") return <ul className={LIST}>{secrets.map(item)}</ul>; | |
| 486 | const { real, tests } = splitSecrets(secrets); | |
| 487 | return ( | |
| 488 | <div className="space-y-5"> | |
| 489 | {real.length > 0 ? ( | |
| 490 | <ul className={LIST}>{real.map(item)}</ul> | |
| 491 | ) : ( | |
| 492 | <Empty title="No secrets that look real">Only likely test values are open, and they never block a push.</Empty> | |
| 493 | )} | |
| 494 | {tests.length > 0 && ( | |
| 495 | <section> | |
| 496 | <h4 className="text-sm font-medium">Likely test values</h4> | |
| 497 | <p className="mt-0.5 mb-2 text-xs text-muted"> | |
| 498 | These look made for tests or documentation. They never block a push and are not counted as critical; dismiss them | |
| 499 | to clear the list. | |
| 500 | </p> | |
| 501 | <ul className={LIST}>{tests.map(item)}</ul> | |
| 502 | </section> | |
| 503 | )} | |
| 504 | </div> | |
| 505 | ); | |
| 506 | } | |
| 507 | ||
| 508 | /** Where the legacy upgrade issue for a package stands, as the page loads it. */ | |
| 509 | export type UpgradeFix = { | |
| 510 | number: number; | |
| 511 | state: "open" | "closed"; | |
| 512 | /** The newest pull request for the issue, if any. */ | |
| 513 | pull: { number: number; status: PullStatus; agent: string | null } | null; | |
| 514 | resolvedBy: number | null; | |
| 515 | }; | |
| 516 | ||
| 517 | /** A security update's pull request as the page loads it. */ | |
| 518 | export type PullInfo = { number: number; status: PullStatus; title: string }; | |
| 519 | ||
| 520 | function FixLink({ issue, fix, base }: { issue: number; fix: UpgradeFix | undefined; base: string }) { | |
| 521 | const pull = fix?.pull; | |
| 522 | return ( | |
| 523 | <span className="flex flex-wrap items-center gap-x-2 gap-y-1 text-xs"> | |
| 524 | <span className="text-muted">Upgrade issue</span> | |
| 525 | <Link to={`${base}/issues/${issue}`} className="inline-flex items-center gap-1 text-fg-soft hover:text-fg"> | |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 526 | {fix?.state === "closed" ? <CircleCheck size={12} className="text-merged" /> : <CircleDot size={12} className="text-success" />}#{issue} |
| One operation mapping, owned by repos; billing reads it instead of keeping its own | 527 | </Link> |
| 528 | {pull && ( | |
| 529 | <Link to={`${base}/pull/${pull.number}`} className="inline-flex items-center gap-1 text-muted hover:text-fg"> | |
| 530 | {pull.agent ? <Bot size={12} /> : <GitPullRequest size={12} />}#{pull.number} {pull.status === "draft" ? "in progress" : pull.status} | |
| 531 | </Link> | |
| 532 | )} | |
| 533 | </span> | |
| 534 | ); | |
| 535 | } | |
| 536 | ||
| 537 | const PULL_ICON: Record<PullStatus, ReactNode> = { | |
| 538 | draft: <GitPullRequest size={13} className="text-muted" />, | |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 539 | open: <GitPullRequest size={13} className="text-success" />, |
| One operation mapping, owned by repos; billing reads it instead of keeping its own | 540 | merged: <GitMerge size={13} className="text-merged" />, |
| 541 | closed: <GitPullRequestClosed size={13} className="text-danger" />, | |
| 542 | }; | |
| 543 | ||
| A desktop release goes out with what can be built. Linux is built on g1t's machines in every release; Windows and macOS each wait on a repository variable: a machine registered for them, or for Windows a cross build on Linux once aka.ms and download.visualstudio.microsoft.com are workflow-only domains, which is where the first release stopped. A platform with no machine is skipped and the download page says it is coming, with a word for the visitor whose platform this release lacks. The guide and CONTRIBUTING say so. | 544 | /** |
| 545 | * Where g1t's security update for a package stands. On a mirror g1t does | |
| 546 | * not lead (`updatesWait`, the reason), an update that has not opened a | |
| 547 | * pull request waits: nothing is made or pushed there until a takeover. | |
| 548 | */ | |
| 549 | function UpdateStatus({ update, name, pulls, base, updatesWait }: { | |
| 550 | update: SecurityUpdate; | |
| 551 | name: string; | |
| 552 | pulls: Record<number, PullInfo>; | |
| 553 | base: string; | |
| 554 | updatesWait?: string | null; | |
| 555 | }) { | |
| One operation mapping, owned by repos; billing reads it instead of keeping its own | 556 | const meta = UPDATE_STATES[update.state]; |
| 557 | const pull = update.pull != null ? pulls[update.pull] : undefined; | |
| A desktop release goes out with what can be built. Linux is built on g1t's machines in every release; Windows and macOS each wait on a repository variable: a machine registered for them, or for Windows a cross build on Linux once aka.ms and download.visualstudio.microsoft.com are workflow-only domains, which is where the first release stopped. A platform with no machine is skipped and the download page says it is coming, with a word for the visitor whose platform this release lacks. The guide and CONTRIBUTING say so. | 558 | if (updatesWait && (update.state === "requested" || update.state === "failed" || update.state === "needs_code")) { |
| 559 | return ( | |
| 560 | <Card tone="plain" radius="lg" className="mt-2.5 bg-bg/40 px-3 py-2 text-xs sm:ml-7"> | |
| 561 | <div className="flex flex-wrap items-center gap-x-2.5 gap-y-1.5"> | |
| 562 | <PauseCircle size={13} className="text-muted" /> | |
| 563 | <Badge>Security update waiting</Badge> | |
| 564 | <span className="text-faint"> | |
| 565 | <TimeAgo at={update.updatedAt} /> | |
| 566 | </span> | |
| 567 | </div> | |
| 568 | <p className="mt-1 text-muted wrap-anywhere"> | |
| 569 | Raising {name} to {update.target} is on hold until {base.replace(/^\//, "")} is taken over or moved to g1t. | |
| 570 | </p> | |
| 571 | </Card> | |
| 572 | ); | |
| 573 | } | |
| One operation mapping, owned by repos; billing reads it instead of keeping its own | 574 | const about: Record<SecurityUpdate["state"], ReactNode> = { |
| 575 | requested: <>A sandbox is raising {name} to {update.target}.</>, | |
| 576 | open: <>It raises {name} to {update.target} and lands through your branch's required checks.</>, | |
| 577 | merged: <>{name} was raised to {update.target}.</>, | |
| 578 | closed: <>The pull request was closed without merging.</>, | |
| 579 | superseded: <>A newer update replaced it, or the package is no longer vulnerable, so g1t closed it.</>, | |
| 580 | needs_code: ( | |
| 581 | <> | |
| g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent | 582 | Raising {name} to {update.target} needs code changes, so g1t opened an issue to make them |
| One operation mapping, owned by repos; billing reads it instead of keeping its own | 583 | {update.issue != null && ( |
| 584 | <> | |
| 585 | :{" "} | |
| 586 | <Link to={`${base}/issues/${update.issue}`} className="font-medium text-fg-soft hover:text-fg hover:underline"> | |
| 587 | #{update.issue} | |
| 588 | </Link> | |
| 589 | </> | |
| 590 | )} | |
| 591 | . | |
| 592 | </> | |
| 593 | ), | |
| 594 | failed: <>{update.error ?? "g1t could not make the change."}</>, | |
| 595 | }; | |
| 596 | return ( | |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 597 | <Card tone="plain" radius="lg" className="mt-2.5 bg-bg/40 px-3 py-2 text-xs sm:ml-7"> |
| One operation mapping, owned by repos; billing reads it instead of keeping its own | 598 | <div className="flex flex-wrap items-center gap-x-2.5 gap-y-1.5"> |
| 599 | {update.state === "requested" ? ( | |
| 600 | <Loader size={13} className="animate-spin text-info motion-reduce:animate-none" /> | |
| 601 | ) : update.state === "failed" || update.state === "needs_code" ? ( | |
| 602 | <CircleAlert size={13} className={update.state === "failed" ? "text-danger" : "text-warn"} /> | |
| 603 | ) : ( | |
| 604 | PULL_ICON[pull?.status ?? (update.state === "merged" ? "merged" : update.state === "open" ? "open" : "closed")] | |
| 605 | )} | |
| 606 | <Badge tone={meta.tone}>{meta.label}</Badge> | |
| 607 | {update.pull != null && ( | |
| 608 | <Link to={`${base}/pull/${update.pull}`} className="font-medium text-fg-soft hover:text-fg hover:underline"> | |
| 609 | #{update.pull} | |
| 610 | {pull && <span className="font-normal text-muted"> {pull.status === "draft" ? "draft" : pull.status}</span>} | |
| 611 | </Link> | |
| 612 | )} | |
| 613 | {update.branch && ( | |
| 614 | <span className="inline-flex min-w-0 items-center gap-1 font-mono text-muted"> | |
| 615 | <GitBranch size={12} className="shrink-0" /> | |
| 616 | <span className="truncate">{update.branch}</span> | |
| 617 | </span> | |
| 618 | )} | |
| 619 | <span className="text-faint"> | |
| 620 | <TimeAgo at={update.updatedAt} /> | |
| 621 | </span> | |
| 622 | </div> | |
| 623 | <p className={`mt-1 ${update.state === "failed" ? "text-danger" : "text-muted"} wrap-anywhere`}>{about[update.state]}</p> | |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 624 | </Card> |
| One operation mapping, owned by repos; billing reads it instead of keeping its own | 625 | ); |
| 626 | } | |
| 627 | ||
| 628 | export function VulnerabilityList({ | |
| 629 | vulnerabilities, | |
| 630 | state, | |
| 631 | activity, | |
| 632 | fixes, | |
| 633 | pulls, | |
| 634 | upkeep, | |
| A desktop release goes out with what can be built. Linux is built on g1t's machines in every release; Windows and macOS each wait on a repository variable: a machine registered for them, or for Windows a cross build on Linux once aka.ms and download.visualstudio.microsoft.com are workflow-only domains, which is where the first release stopped. A platform with no machine is skipped and the download page says it is coming, with a word for the visitor whose platform this release lacks. The guide and CONTRIBUTING say so. | 635 | updatesWait, |
| One operation mapping, owned by repos; billing reads it instead of keeping its own | 636 | base, |
| 637 | action, | |
| 638 | focus, | |
| 639 | canDismiss, | |
| 640 | }: { | |
| 641 | /** Already filtered to `state`. */ | |
| 642 | vulnerabilities: Vulnerability[]; | |
| 643 | state: AlertState; | |
| 644 | activity: AlertActivity[]; | |
| 645 | fixes: Record<number, UpgradeFix>; | |
| 646 | pulls: Record<number, PullInfo>; | |
| 647 | /** Whether security updates are on. */ | |
| 648 | upkeep: boolean; | |
| A desktop release goes out with what can be built. Linux is built on g1t's machines in every release; Windows and macOS each wait on a repository variable: a machine registered for them, or for Windows a cross build on Linux once aka.ms and download.visualstudio.microsoft.com are workflow-only domains, which is where the first release stopped. A platform with no machine is skipped and the download page says it is coming, with a word for the visitor whose platform this release lacks. The guide and CONTRIBUTING say so. | 649 | /** Why no update opens now: the repository is a mirror g1t does not lead (lib/mirror.ts). */ |
| 650 | updatesWait?: string | null; | |
| One operation mapping, owned by repos; billing reads it instead of keeping its own | 651 | base: string; |
| 652 | action: string; | |
| 653 | focus: string | null; | |
| 654 | /** Whether the viewer may dismiss and reopen dependency alerts (Write). */ | |
| 655 | canDismiss: boolean; | |
| 656 | }) { | |
| 657 | const packages = groupByPackage(vulnerabilities); | |
| 658 | if (packages.length === 0) { | |
| 659 | return state === "open" ? ( | |
| 660 | <Empty title="No known vulnerabilities"> | |
| 661 | Every package the lockfiles resolve is checked against the OSV database on each push to the default branch, and daily. | |
| 662 | </Empty> | |
| 663 | ) : ( | |
| 664 | <Empty title={state === "dismissed" ? "No dismissed dependency alerts" : "Nothing fixed yet"}> | |
| 665 | {state === "dismissed" | |
| 666 | ? "Alerts someone dismissed, with their reason, are listed here." | |
| 667 | : "Alerts whose package was upgraded, or is no longer vulnerable, are listed here."} | |
| 668 | </Empty> | |
| 669 | ); | |
| 670 | } | |
| 671 | return ( | |
| 672 | <ul className={LIST}> | |
| 673 | {packages.map((group) => ( | |
| 674 | <PackageItem | |
| 675 | key={group.key} | |
| 676 | group={group} | |
| 677 | activity={activity} | |
| 678 | fixes={fixes} | |
| 679 | pulls={pulls} | |
| 680 | upkeep={upkeep} | |
| A desktop release goes out with what can be built. Linux is built on g1t's machines in every release; Windows and macOS each wait on a repository variable: a machine registered for them, or for Windows a cross build on Linux once aka.ms and download.visualstudio.microsoft.com are workflow-only domains, which is where the first release stopped. A platform with no machine is skipped and the download page says it is coming, with a word for the visitor whose platform this release lacks. The guide and CONTRIBUTING say so. | 681 | updatesWait={updatesWait} |
| One operation mapping, owned by repos; billing reads it instead of keeping its own | 682 | base={base} |
| 683 | action={action} | |
| 684 | focus={focus} | |
| 685 | canDismiss={canDismiss} | |
| 686 | /> | |
| 687 | ))} | |
| 688 | </ul> | |
| 689 | ); | |
| 690 | } | |
| 691 | ||
| 692 | function PackageItem({ | |
| 693 | group, | |
| 694 | activity, | |
| 695 | fixes, | |
| 696 | pulls, | |
| 697 | upkeep, | |
| A desktop release goes out with what can be built. Linux is built on g1t's machines in every release; Windows and macOS each wait on a repository variable: a machine registered for them, or for Windows a cross build on Linux once aka.ms and download.visualstudio.microsoft.com are workflow-only domains, which is where the first release stopped. A platform with no machine is skipped and the download page says it is coming, with a word for the visitor whose platform this release lacks. The guide and CONTRIBUTING say so. | 698 | updatesWait, |
| One operation mapping, owned by repos; billing reads it instead of keeping its own | 699 | base, |
| 700 | action, | |
| 701 | focus, | |
| 702 | canDismiss, | |
| 703 | }: { | |
| 704 | group: PackageGroup; | |
| 705 | activity: AlertActivity[]; | |
| 706 | fixes: Record<number, UpgradeFix>; | |
| 707 | pulls: Record<number, PullInfo>; | |
| 708 | upkeep: boolean; | |
| A desktop release goes out with what can be built. Linux is built on g1t's machines in every release; Windows and macOS each wait on a repository variable: a machine registered for them, or for Windows a cross build on Linux once aka.ms and download.visualstudio.microsoft.com are workflow-only domains, which is where the first release stopped. A platform with no machine is skipped and the download page says it is coming, with a word for the visitor whose platform this release lacks. The guide and CONTRIBUTING say so. | 709 | updatesWait?: string | null; |
| One operation mapping, owned by repos; billing reads it instead of keeping its own | 710 | base: string; |
| 711 | action: string; | |
| 712 | focus: string | null; | |
| 713 | canDismiss: boolean; | |
| 714 | }) { | |
| 715 | const versions = [...new Set(group.vulns.map((vuln) => vuln.version))]; | |
| 716 | const manifests = [...new Set(group.vulns.map((vuln) => vuln.manifest))]; | |
| 717 | const target = highestFix(group.vulns); | |
| 718 | const update = latestUpdate(group.vulns); | |
| 719 | const issue = update ? null : (group.vulns.find((vuln) => vuln.issue != null)?.issue ?? null); | |
| 720 | return ( | |
| 721 | <li className="px-4 py-3"> | |
| 722 | <div className="flex items-start gap-3"> | |
| 723 | <Package size={15} className="mt-0.5 hidden shrink-0 text-muted sm:block" /> | |
| 724 | <div className="min-w-0 grow"> | |
| 725 | <div className="flex flex-wrap items-center gap-2"> | |
| 726 | <span className="font-mono text-sm font-medium break-all">{group.name}</span> | |
| 727 | <span className="font-mono text-xs text-muted">{versions.join(", ")}</span> | |
| 728 | <Badge>{group.ecosystem}</Badge> | |
| 729 | <SeverityBadge severity={worstSeverity(group.vulns)} /> | |
| 730 | </div> | |
| 731 | <p className="mt-1 text-xs text-faint wrap-anywhere"> | |
| 732 | {target ? <>Fixed in {target}</> : "No patched version"} · locked in{" "} | |
| 733 | <span className="font-mono">{manifests.join(", ")}</span> | |
| 734 | </p> | |
| 735 | </div> | |
| 736 | </div> | |
| A desktop release goes out with what can be built. Linux is built on g1t's machines in every release; Windows and macOS each wait on a repository variable: a machine registered for them, or for Windows a cross build on Linux once aka.ms and download.visualstudio.microsoft.com are workflow-only domains, which is where the first release stopped. A platform with no machine is skipped and the download page says it is coming, with a word for the visitor whose platform this release lacks. The guide and CONTRIBUTING say so. | 737 | {update && <UpdateStatus update={update} name={group.name} pulls={pulls} base={base} updatesWait={updatesWait} />} |
| One operation mapping, owned by repos; billing reads it instead of keeping its own | 738 | {issue != null && ( |
| 739 | <div className="mt-2 sm:ml-7"> | |
| 740 | <FixLink issue={issue} fix={fixes[issue]} base={base} /> | |
| 741 | </div> | |
| 742 | )} | |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 743 | <Card asChild tone="plain" radius="lg" className="mt-2.5 space-y-px overflow-hidden sm:ml-7"> |
| 744 | <ul> | |
| 745 | {group.vulns.map((vuln) => ( | |
| 746 | <AdvisoryItem | |
| 747 | key={vuln.id} | |
| 748 | vuln={vuln} | |
| 749 | showManifest={manifests.length > 1} | |
| 750 | activity={activity} | |
| 751 | upkeep={upkeep} | |
| A desktop release goes out with what can be built. Linux is built on g1t's machines in every release; Windows and macOS each wait on a repository variable: a machine registered for them, or for Windows a cross build on Linux once aka.ms and download.visualstudio.microsoft.com are workflow-only domains, which is where the first release stopped. A platform with no machine is skipped and the download page says it is coming, with a word for the visitor whose platform this release lacks. The guide and CONTRIBUTING say so. | 752 | updatesWait={updatesWait} |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 753 | base={base} |
| 754 | action={action} | |
| 755 | focused={vuln.id === focus} | |
| 756 | canDismiss={canDismiss} | |
| 757 | /> | |
| 758 | ))} | |
| 759 | </ul> | |
| 760 | </Card> | |
| One operation mapping, owned by repos; billing reads it instead of keeping its own | 761 | </li> |
| 762 | ); | |
| 763 | } | |
| 764 | ||
| 765 | function AdvisoryItem({ | |
| 766 | vuln, | |
| 767 | showManifest, | |
| 768 | activity, | |
| 769 | upkeep, | |
| A desktop release goes out with what can be built. Linux is built on g1t's machines in every release; Windows and macOS each wait on a repository variable: a machine registered for them, or for Windows a cross build on Linux once aka.ms and download.visualstudio.microsoft.com are workflow-only domains, which is where the first release stopped. A platform with no machine is skipped and the download page says it is coming, with a word for the visitor whose platform this release lacks. The guide and CONTRIBUTING say so. | 770 | updatesWait, |
| One operation mapping, owned by repos; billing reads it instead of keeping its own | 771 | base, |
| 772 | action, | |
| 773 | focused, | |
| 774 | canDismiss, | |
| 775 | }: { | |
| 776 | vuln: Vulnerability; | |
| 777 | showManifest: boolean; | |
| 778 | activity: AlertActivity[]; | |
| 779 | upkeep: boolean; | |
| A desktop release goes out with what can be built. Linux is built on g1t's machines in every release; Windows and macOS each wait on a repository variable: a machine registered for them, or for Windows a cross build on Linux once aka.ms and download.visualstudio.microsoft.com are workflow-only domains, which is where the first release stopped. A platform with no machine is skipped and the download page says it is coming, with a word for the visitor whose platform this release lacks. The guide and CONTRIBUTING say so. | 780 | updatesWait?: string | null; |
| One operation mapping, owned by repos; billing reads it instead of keeping its own | 781 | base: string; |
| 782 | action: string; | |
| 783 | focused: boolean; | |
| 784 | canDismiss: boolean; | |
| 785 | }) { | |
| 786 | const ref = useFocus<HTMLLIElement>(focused); | |
| 787 | const osv = `https://osv.dev/vulnerability/${vuln.osvId}`; | |
| 788 | const dismiss = (reason?: DismissReason, trigger?: string) => ( | |
| 789 | <DismissDialog | |
| 790 | id={vuln.id} | |
| 791 | title={`Dismiss ${vuln.advisory}`} | |
| 792 | detail={`${vuln.package} ${vuln.version} · ${vuln.manifest}`} | |
| 793 | reasons={DEPENDENCY_DISMISS_REASONS} | |
| 794 | action={action} | |
| 795 | defaultReason={reason} | |
| 796 | trigger={trigger} | |
| 797 | /> | |
| 798 | ); | |
| 799 | return ( | |
| 800 | <li ref={ref} id={vuln.id} className={`bg-bg/30 px-3 py-2.5 not-first:border-t not-first:border-line ${focused ? FOCUSED : ""}`}> | |
| 801 | <div className="flex flex-col gap-2 sm:flex-row sm:items-start"> | |
| 802 | <div className="min-w-0 grow text-xs"> | |
| 803 | <p className="flex flex-wrap items-center gap-x-2 gap-y-1"> | |
| 804 | <a href={osv} target="_blank" rel="noreferrer" className="inline-flex items-center gap-1 font-mono text-fg-soft hover:text-fg"> | |
| 805 | {vuln.advisory} | |
| 806 | <ExternalLink size={10} /> | |
| 807 | </a> | |
| 808 | <SeverityBadge severity={vuln.severity} /> | |
| 809 | {vuln.fixedVersion && <span className="text-faint">fixed in {vuln.fixedVersion}</span>} | |
| 810 | {showManifest && <span className="font-mono text-faint">{vuln.manifest}</span>} | |
| 811 | {vuln.state === "dismissed" && vuln.dismissedReason && <Badge>{dismissLabel(vuln.dismissedReason)}</Badge>} | |
| 812 | </p> | |
| 813 | <p className="mt-1 text-muted wrap-anywhere">{vuln.summary}</p> | |
| 814 | </div> | |
| 815 | {canDismiss && vuln.state !== "fixed" && ( | |
| 816 | <div className="flex shrink-0 items-center gap-1.5"> | |
| 817 | {vuln.state === "open" ? dismiss() : <ReopenButton id={vuln.id} action={action} />} | |
| 818 | </div> | |
| 819 | )} | |
| 820 | </div> | |
| 821 | {vuln.state === "open" && !vuln.fixedVersion && ( | |
| 822 | <div className="mt-2 rounded-md border border-warn/30 bg-warn/5 px-3 py-2 text-xs"> | |
| 823 | <p className="flex items-center gap-1.5 font-medium text-warn"> | |
| 824 | <ShieldAlert size={13} /> | |
| 825 | No patched version available | |
| 826 | </p> | |
| 827 | <p className="mt-1 text-muted"> | |
| 828 | No release fixes this yet. Dependencies are checked again daily | |
| A desktop release goes out with what can be built. Linux is built on g1t's machines in every release; Windows and macOS each wait on a repository variable: a machine registered for them, or for Windows a cross build on Linux once aka.ms and download.visualstudio.microsoft.com are workflow-only domains, which is where the first release stopped. A platform with no machine is skipped and the download page says it is coming, with a word for the visitor whose platform this release lacks. The guide and CONTRIBUTING say so. | 829 | {updatesWait |
| 830 | ? ". A fix is applied once the repository is taken over or moved to g1t." | |
| 831 | : upkeep | |
| 832 | ? ", and g1t opens a pull request when a fix is published." | |
| 833 | : "; turn on security updates and g1t opens a pull request when a fix is published."} | |
| One operation mapping, owned by repos; billing reads it instead of keeping its own | 834 | </p> |
| 835 | <div className="mt-2 flex flex-wrap items-center gap-2"> | |
| 836 | <a href={osv} target="_blank" rel="noreferrer" className="inline-flex items-center gap-1 text-fg-soft hover:text-fg hover:underline"> | |
| 837 | Read the advisory | |
| 838 | <ExternalLink size={10} /> | |
| 839 | </a> | |
| 840 | {canDismiss && dismiss("tolerable_risk", "Dismiss as tolerable risk")} | |
| 841 | </div> | |
| 842 | </div> | |
| 843 | )} | |
| 844 | <ActivityLog entries={alertActivity(vuln, activity)} base={base} open={focused && vuln.state !== "open"} /> | |
| 845 | </li> | |
| 846 | ); | |
| 847 | } | |
| 848 | ||
| 849 | export function ScanSummary({ | |
| 850 | scan, | |
| 851 | }: { | |
| 852 | scan: { history: string; commitsScanned: number; historyFinishedAt: string | null; dependenciesScannedAt: string | null; dependenciesError: string | null; lockfiles: string[] }; | |
| 853 | }) { | |
| 854 | const history = | |
| 855 | scan.history === "done" ? ( | |
| 856 | <> | |
| 857 | History scanned: {scan.commitsScanned.toLocaleString()} commits | |
| 858 | {scan.historyFinishedAt && ( | |
| 859 | <> | |
| 860 | , <TimeAgo at={scan.historyFinishedAt} /> | |
| 861 | </> | |
| 862 | )} | |
| 863 | </> | |
| 864 | ) : scan.history === "stopped" ? ( | |
| 865 | "History scan paused: the workspace reached its spending limit" | |
| 866 | ) : scan.history === "running" ? ( | |
| 867 | `Scanning history: ${scan.commitsScanned.toLocaleString()} commits so far` | |
| 868 | ) : ( | |
| 869 | "History scan queued" | |
| 870 | ); | |
| 871 | return ( | |
| 872 | <div className="flex flex-wrap gap-x-5 gap-y-1 text-xs text-muted"> | |
| 873 | <span className="inline-flex items-center gap-1.5"> | |
| 874 | <KeyRound size={12} /> | |
| 875 | {history} | |
| 876 | </span> | |
| 877 | <span className="inline-flex items-center gap-1.5"> | |
| 878 | <Package size={12} /> | |
| 879 | {scan.dependenciesScannedAt ? ( | |
| 880 | <> | |
| 881 | Dependencies read <TimeAgo at={scan.dependenciesScannedAt} /> | |
| 882 | {scan.lockfiles.length > 0 ? <> from {scan.lockfiles.join(", ")}</> : " (no lockfiles found)"} | |
| 883 | </> | |
| 884 | ) : ( | |
| 885 | "Dependencies not read yet" | |
| 886 | )} | |
| 887 | </span> | |
| 888 | {scan.dependenciesError && ( | |
| 889 | <span className="inline-flex items-center gap-1.5 text-warn"> | |
| 890 | <ShieldAlert size={12} /> | |
| 891 | {scan.dependenciesError} | |
| 892 | </span> | |
| 893 | )} | |
| 894 | </div> | |
| 895 | ); | |
| 896 | } |
This file's history is long; its oldest lines are credited to the oldest commit read.