Skip to content

g1t/apps/web/app/routes/workspace/actions-settings.tsx

111 lines5,435 bytesCodeBlameRaw

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.

Merge branch 'worktree-agent-a3abfcce648e87dca'1import { Form } from "react-router";
2
3import type { WorkspaceActionsSettings } from "@g1t/contracts";
4
5import type { Route } from "./+types/actions-settings";
6import { SettingsSection as Section } from "../../components/settings-section";
7import { ErrorText, SubmitButton } from "../../components/ui";
8import { CheckboxOption } from "../../components/ui/checkbox";
9import { RadioGroup, RadioOption } from "../../components/ui/radio-group";
10import { page } from "../../lib/meta";
11import { actions } from "../../lib/services.server";
12import { assertSameOrigin, getViewer, requireUser, roleIn, unwrap } from "../../lib/session.server";
13
14export function meta({ params, ...args }: Route.MetaArgs) {
15 return page(args, { title: `Actions · ${params.owner} · g1t` });
16}
17
18export async function loader({ params, context }: Route.LoaderArgs) {
19 const viewer = getViewer(context);
20 // Members may see the policy; owners change it.
21 const role = roleIn(viewer, params.owner);
22 if (!role) throw new Response(null, { status: 404 });
23 const settings = unwrap(await actions.workspaceActionsSettings(params.owner.toLowerCase(), viewer));
24 return { settings, owner: role === "owner" };
25}
26
27export async function action({ request, params, context }: Route.ActionArgs) {
28 assertSameOrigin(request);
29 if (roleIn(getViewer(context), params.owner) !== "owner") throw new Response(null, { status: 404 });
30 const user = requireUser(context, request);
31 const form = await request.formData();
32 const level = (name: string): "read" | "write" => (form.get(name) === "write" ? "write" : "read");
33 const change: Partial<WorkspaceActionsSettings> = {
34 defaultPermissions: level("defaultPermissions"),
35 maxPermissions: level("maxPermissions"),
36 canApprovePullRequests: form.get("canApprovePullRequests") === "on",
37 };
38 const saved = await actions.setWorkspaceActionsSettings(user, params.owner.toLowerCase(), change);
39 return saved.ok ? { saved: true, error: null } : { saved: false, error: saved.error.message };
40}
41
42export default function WorkspaceActionsSettingsPage({ loaderData, actionData }: Route.ComponentProps) {
43 const { settings, owner } = loaderData;
44 return (
45 <div className="space-y-6">
46 <header>
47 <h1 className="text-xl font-semibold tracking-tight">Actions</h1>
48 <p className="mt-1 max-w-3xl text-sm text-muted">
49 What the token of each workflow job in this workspace's repositories may do when its workflow writes no{" "}
50 <code className="font-mono text-xs">permissions:</code>. Each repository can choose for itself under its Settings,
51 Actions, within these limits.
52 </p>
53 </header>
54 <Form method="post" className="max-w-4xl space-y-8">
55 <fieldset disabled={!owner} className="space-y-8">
56 <Section title="Default for new repositories" about="What a repository made from now on gets, until it chooses.">
57 <RadioGroup name="defaultPermissions" defaultValue={settings.defaultPermissions} className="gap-3">
58 <RadioOption
59 value="read"
60 label="Read repository contents and packages"
61 description={
62 <>
63 <code className="font-mono">contents: read</code> and <code className="font-mono">packages: read</code>.
64 The default.
65 </>
66 }
67 />
68 <RadioOption value="write" label="Read and write" description="Every permission a job's token can have." />
69 </RadioGroup>
70 <p className="text-sm text-muted">
71 Repositories made before restricted tokens keep read and write until someone chooses otherwise.
72 </p>
73 </Section>
74
75 <Section title="Most a repository may choose" about="No repository's default goes past this.">
76 <RadioGroup name="maxPermissions" defaultValue={settings.maxPermissions} className="gap-3">
77 <RadioOption value="write" label="Read and write" description="Each repository chooses. The default." />
78 <RadioOption
79 value="read"
80 label="Read only"
81 description="Every repository's jobs get read-only tokens unless their workflow writes permissions:."
82 />
83 </RadioGroup>
84 </Section>
85
86 <Section
87 title="Pull requests"
88 about="Whether a job's token may open pull requests and approve them. Off by default."
89 >
90 <CheckboxOption
91 name="canApprovePullRequests"
92 defaultChecked={settings.canApprovePullRequests}
93 label="Allow repositories to let g1t Actions create and approve pull requests"
94 description="Each repository still turns it on for itself, under its Settings, Actions."
95 />
96 </Section>
97 </fieldset>
98
99 {owner ? (
100 <div className="sticky bottom-0 -mx-4 flex flex-wrap items-center gap-4 border-t border-line bg-bg/90 px-4 py-4 backdrop-blur">
101 <SubmitButton pending="Saving…">Save settings</SubmitButton>
102 {actionData?.saved && <span className="text-sm text-muted">Saved.</span>}
103 <ErrorText>{actionData?.error}</ErrorText>
104 </div>
105 ) : (
106 <p className="text-sm text-muted">Only the workspace's owners can change these.</p>
107 )}
108 </Form>
109 </div>
110 );
111}

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