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 bar | 1 | import { Link, data, useFetcher, useSearchParams } from "react-router"; |
| 2 | ||
| 3 | import type { BypassRequest } from "@g1t/contracts"; | |
| 4 | ||
| 5 | import type { Route } from "./+types/security-bypass"; | |
| 6 | import { page } from "../../lib/meta"; | |
| 7 | import { CARD, FilterSelect } from "../../components/security-suite"; | |
| 8 | import { WorkspaceSecurityHeading, WorkspaceSecurityTabs } from "../../components/workspace-security-tabs"; | |
| 9 | import { TimeAgo } from "../../components/ui"; | |
| 10 | import { Badge } from "../../components/ui/badge"; | |
| 11 | import { securitySuite } from "../../lib/services.server"; | |
| 12 | import { assertSameOrigin, getViewer, requireUser, roleIn, unwrap } from "../../lib/session.server"; | |
| 13 | ||
| 14 | export function meta({ params, ...args }: Route.MetaArgs) { | |
| 15 | return page(args, { title: `Bypass requests · ${params.owner} · g1t` }); | |
| 16 | } | |
| 17 | ||
| 18 | const STATES = new Set(["pending", "approved", "denied", "cancelled"]); | |
| 19 | ||
| 20 | export async function loader({ params, context, request }: Route.LoaderArgs) { | |
| 21 | const viewer = getViewer(context); | |
| 22 | if (!roleIn(viewer, params.owner)) throw data(null, { status: 404 }); | |
| 23 | const state = new URL(request.url).searchParams.get("state") ?? "pending"; | |
| 24 | const [list, pending] = await Promise.all([ | |
| 25 | securitySuite.bypassRequests(params.owner, viewer, STATES.has(state) ? state : null), | |
| 26 | securitySuite.bypassRequests(params.owner, viewer, "pending"), | |
| 27 | ]); | |
| 28 | return { requests: unwrap(list), pending: pending.ok ? pending.value.length : 0, me: viewer?.username ?? "" }; | |
| 29 | } | |
| 30 | ||
| 31 | export async function action({ params, context, request }: Route.ActionArgs) { | |
| 32 | assertSameOrigin(request); | |
| 33 | const user = requireUser(context, request); | |
| 34 | const form = await request.formData(); | |
| 35 | const decision = String(form.get("decision") ?? ""); | |
| 36 | if (decision !== "approve" && decision !== "deny" && decision !== "cancel") return { ok: false, error: "Choose approve or deny." }; | |
| 37 | const done = await securitySuite.reviewBypass(user, params.owner, String(form.get("id") ?? ""), decision, String(form.get("comment") ?? "").trim().slice(0, 500)); | |
| 38 | return done.ok ? { ok: true } : { ok: false, error: done.error.message }; | |
| 39 | } | |
| 40 | ||
| 41 | const STATE_TONE = { pending: "warn", approved: "accent", denied: "danger", cancelled: "neutral" } as const; | |
| 42 | ||
| 43 | function Review({ request, mine }: { request: BypassRequest; mine: boolean }) { | |
| 44 | const fetcher = useFetcher<{ ok: boolean; error?: string }>(); | |
| 45 | const busy = fetcher.state !== "idle"; | |
| 46 | return ( | |
| 47 | <fetcher.Form method="post" className="mt-3 flex flex-col gap-2 sm:flex-row sm:items-center"> | |
| 48 | <input type="hidden" name="id" value={request.id} /> | |
| 49 | {!mine && ( | |
| 50 | <input | |
| 51 | name="comment" | |
| 52 | maxLength={500} | |
| 53 | placeholder="Comment (optional)" | |
| 54 | className="h-8 min-w-0 grow rounded-md border border-line bg-bg px-2.5 text-[0.8125rem] outline-none hover:border-line-strong focus:border-accent-dim" | |
| 55 | /> | |
| 56 | )} | |
| 57 | <div className="flex gap-2"> | |
| 58 | {mine ? ( | |
| 59 | <button type="submit" name="decision" value="cancel" disabled={busy} className="rounded-md border border-line px-2.5 py-1 text-xs font-medium text-muted hover:text-fg disabled:opacity-50"> | |
| 60 | Cancel request | |
| 61 | </button> | |
| 62 | ) : ( | |
| 63 | <> | |
| 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. | 64 | <button type="submit" name="decision" value="approve" disabled={busy} className="rounded-md bg-fg px-3 py-1 text-xs 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 bar | 65 | Approve |
| 66 | </button> | |
| 67 | <button type="submit" name="decision" value="deny" disabled={busy} className="rounded-md border border-danger/40 px-3 py-1 text-xs font-medium text-danger hover:bg-danger/10 disabled:opacity-50"> | |
| 68 | Deny | |
| 69 | </button> | |
| 70 | </> | |
| 71 | )} | |
| 72 | </div> | |
| 73 | {fetcher.data?.error && <span className="text-xs text-danger">{fetcher.data.error}</span>} | |
| 74 | </fetcher.Form> | |
| 75 | ); | |
| 76 | } | |
| 77 | ||
| 78 | export default function BypassRequests({ loaderData, params }: Route.ComponentProps) { | |
| 79 | const { requests, pending, me } = loaderData; | |
| 80 | const [search, setSearch] = useSearchParams(); | |
| 81 | const state = search.get("state") ?? "pending"; | |
| 82 | return ( | |
| 83 | <div> | |
| 84 | <WorkspaceSecurityHeading | |
| 85 | title="Bypass requests" | |
| 86 | about="With delegated bypass on, people who push a blocked secret ask to push it anyway, and the workspace's owners and the repository's admins decide. Each decision is recorded on the alert and in the audit log." | |
| 87 | /> | |
| 88 | <WorkspaceSecurityTabs owner={params.owner} pending={pending} /> | |
| 89 | <div className="mb-4"> | |
| 90 | <FilterSelect | |
| 91 | label="State" | |
| 92 | value={STATES.has(state) ? state : "all"} | |
| 93 | options={[["pending", "Pending"], ["approved", "Approved"], ["denied", "Denied"], ["cancelled", "Cancelled"], ["all", "All"]]} | |
| 94 | onChange={(value) => setSearch(value === "pending" ? {} : { state: value }, { replace: true })} | |
| 95 | /> | |
| 96 | </div> | |
| 97 | {requests.length === 0 ? ( | |
| 98 | <p className="rounded-xl border border-dashed border-line px-4 py-6 text-sm text-muted"> | |
| 99 | No {state === "all" ? "" : `${state} `}requests. Delegated bypass is turned on in this workspace's Security settings. | |
| 100 | </p> | |
| 101 | ) : ( | |
| 102 | <ul className="space-y-3"> | |
| 103 | {requests.map((request) => ( | |
| 104 | <li key={request.id} className={`${CARD} p-4`}> | |
| 105 | <div className="flex flex-wrap items-center gap-2"> | |
| 106 | <Link to={`/${request.workspace}/${request.repo}/security/secret-scanning/${request.secretId}`} className="text-sm font-medium first-letter:uppercase hover:underline"> | |
| 107 | {request.label} | |
| 108 | </Link> | |
| 109 | <Badge tone={STATE_TONE[request.state]}>{request.state}</Badge> | |
| 110 | <span className="font-mono text-xs text-faint"> | |
| 111 | {request.repo} · {request.path}:{request.line} | |
| 112 | </span> | |
| 113 | </div> | |
| 114 | <p className="mt-1.5 text-sm text-muted"> | |
| 115 | {request.requester} asked <TimeAgo at={request.createdAt} />: {request.reason.replaceAll("_", " ")} | |
| 116 | {request.comment && <> · “{request.comment}”</>} | |
| 117 | </p> | |
| 118 | {request.reviewer && ( | |
| 119 | <p className="mt-1 text-xs text-faint"> | |
| 120 | {request.state} by {request.reviewer} | |
| 121 | {request.reviewComment && <>: “{request.reviewComment}”</>} | |
| 122 | </p> | |
| 123 | )} | |
| 124 | {request.state === "pending" && <Review request={request} mine={request.requester.toLowerCase() === me.toLowerCase()} />} | |
| 125 | </li> | |
| 126 | ))} | |
| 127 | </ul> | |
| 128 | )} | |
| 129 | </div> | |
| 130 | ); | |
| 131 | } |