Skip to content

g1t/apps/web/app/routes/settings/keys.tsx

78 lines3,107 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.

status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas1import { identity } from "../../lib/services.server";
Code button: the SSH tab shows the address it will have, disabled, with why it is not on yet2import { Form, Link } from "react-router";
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas3
4import type { Route } from "./+types/keys";
5import { page } from "../../lib/meta";
Merge branch 'worktree-agent-a985c01f43538af01'6import { ErrorText, Field, Input, SubmitButton } from "../../components/ui";
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas7import { DeleteButton } from "../../components/account-settings";
8import { assertSameOrigin, requireUser } from "../../lib/session.server";
9
10export function meta(args: Route.MetaArgs) {
11 return page(args, { title: "SSH keys · Settings · g1t" });
12}
13
14export async function loader({ request, context }: Route.LoaderArgs) {
15 const user = requireUser(context, request);
16 return { keys: await identity.listSshKeys(user) };
17}
18
19export async function action({ request, context }: Route.ActionArgs) {
20 assertSameOrigin(request);
21 const user = requireUser(context, request);
22 const form = await request.formData();
23 switch (form.get("intent")) {
24 case "add-key": {
25 const result = await identity.addSshKey(
26 user,
27 String(form.get("title") ?? ""),
28 String(form.get("key") ?? ""),
29 );
30 return result.ok ? null : { keyError: result.error.message };
31 }
32 case "delete-key":
33 await identity.removeSshKey(user, String(form.get("id") ?? ""));
34 return null;
35 }
36 return null;
37}
38
39export default function SshKeySettings({ loaderData, actionData }: Route.ComponentProps) {
40 const { keys } = loaderData;
41 return (
42 <section id="ssh-keys" className="scroll-mt-20">
Code button: the SSH tab shows the address it will have, disabled, with why it is not on yet43 <p className="mb-4 rounded-lg border border-dashed border-line p-3 text-xs text-muted">
44 Git over SSH is not on yet: it is waiting on inbound TCP on Cloudflare, which g1t has applied for. Keys you
45 add now will work as soon as it is. Until then, clone and push over HTTPS with an{" "}
46 <Link to="/settings/tokens" className="text-fg underline underline-offset-4">
47 access token
48 </Link>
49 .
50 </p>
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas51 <ul className="divide-y divide-line rounded-md border border-line empty:hidden">
52 {keys.map((key) => (
53 <li key={key.id} className="flex items-center gap-4 px-4 py-3">
54 <div className="min-w-0">
55 <p className="text-sm">{key.title}</p>
56 <p className="truncate font-mono text-xs text-muted">{key.fingerprint}</p>
57 </div>
58 <DeleteButton intent="delete-key" id={key.id} />
59 </li>
60 ))}
61 </ul>
Merge branch 'worktree-agent-a985c01f43538af01'62 {/* Keyed on the list, so a key once added leaves the fields empty for the next. */}
63 <Form key={keys.length} method="post" className={`space-y-3 ${keys.length > 0 ? "mt-4" : ""}`}>
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas64 <input type="hidden" name="intent" value="add-key" />
65 <Field label="Title (optional)">
66 <Input name="title" maxLength={100} />
67 </Field>
68 <Field label="Public key">
69 <Input name="key" placeholder="ssh-ed25519 AAAA…" required />
70 </Field>
71 <ErrorText>{actionData?.keyError}</ErrorText>
Merge branch 'worktree-agent-a985c01f43538af01'72 <SubmitButton pending="Adding…" match={{ intent: "add-key" }}>
73 Add SSH key
74 </SubmitButton>
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas75 </Form>
76 </section>
77 );
78}

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