g1t/apps/web/app/lib/runners.server.ts
| 1 | import { env } from "cloudflare:workers"; |
| 2 | |
| 3 | import { |
| 4 | type RegistrationToken, |
| 5 | type Runner, |
| 6 | type RunnerGroup, |
| 7 | type RunnerSettings, |
| 8 | type RunnersOwner, |
| 9 | type User, |
| 10 | runnersClient, |
| 11 | } from "@g1t/contracts"; |
| 12 | |
| 13 | import { instrumented } from "./perf.server"; |
| 14 | |
| 15 | /** Self-hosted runners, kept by the actions service. */ |
| 16 | export const runners = runnersClient(instrumented("actions", env.ACTIONS)); |
| 17 | |
| 18 | export type RunnersData = { |
| 19 | runners: Runner[]; |
| 20 | /** A workspace's groups; empty for a repository. */ |
| 21 | groups: RunnerGroup[]; |
| 22 | settings: RunnerSettings | null; |
| 23 | /** The workspace's repositories, for choosing a group's. */ |
| 24 | repositories: string[]; |
| 25 | error: string | null; |
| 26 | }; |
| 27 | |
| 28 | export async function loadRunners(owner: RunnersOwner, actor: User, repositories: string[] = []): Promise<RunnersData> { |
| 29 | const [list, groups, settings] = await Promise.all([ |
| 30 | runners.list(actor, owner), |
| 31 | "workspace" in owner ? runners.groups(actor, owner.workspace) : Promise.resolve(null), |
| 32 | runners.settings(actor, owner), |
| 33 | ]); |
| 34 | return { |
| 35 | runners: list.ok ? list.value : [], |
| 36 | groups: groups?.ok ? groups.value : [], |
| 37 | settings: settings.ok ? settings.value : null, |
| 38 | repositories, |
| 39 | error: list.ok ? null : list.error.message, |
| 40 | }; |
| 41 | } |
| 42 | |
| 43 | export type RunnersAction = { |
| 44 | error?: string; |
| 45 | notice?: string; |
| 46 | /** A registration token just made: shown once, in the install steps. */ |
| 47 | token?: RegistrationToken; |
| 48 | }; |
| 49 | |
| 50 | /** Every form on the Runners page. */ |
| 51 | export async function actOnRunners(owner: RunnersOwner, actor: User, form: FormData): Promise<RunnersAction> { |
| 52 | const intent = String(form.get("intent") ?? ""); |
| 53 | const workspace = "workspace" in owner ? owner.workspace : owner.repo.namespace.toLowerCase(); |
| 54 | const list = (name: string) => |
| 55 | String(form.get(name) ?? "") |
| 56 | .split(/[\s,]+/) |
| 57 | .map((item) => item.trim()) |
| 58 | .filter(Boolean); |
| 59 | switch (intent) { |
| 60 | case "token": { |
| 61 | const made = await runners.createToken(actor, owner, String(form.get("group") ?? "") || undefined); |
| 62 | return made.ok ? { token: made.value } : { error: made.error.message }; |
| 63 | } |
| 64 | case "remove": { |
| 65 | const removed = await runners.remove(actor, owner, String(form.get("id") ?? "")); |
| 66 | return removed.ok ? { notice: `Removed ${String(form.get("name") ?? "the runner")}.` } : { error: removed.error.message }; |
| 67 | } |
| 68 | case "group": { |
| 69 | const id = String(form.get("id") ?? "") || undefined; |
| 70 | const repositories = form.get("reach") === "some" ? form.getAll("repository").map(String) : []; |
| 71 | if (form.get("reach") === "some" && repositories.length === 0) return { error: "Choose at least one repository, or every repository." }; |
| 72 | const saved = await runners.setGroup(actor, workspace, { |
| 73 | id, |
| 74 | name: String(form.get("name") ?? "").trim() || undefined, |
| 75 | repositories, |
| 76 | }); |
| 77 | return saved.ok ? { notice: `Saved the group ${saved.value.name}.` } : { error: saved.error.message }; |
| 78 | } |
| 79 | case "delete-group": { |
| 80 | const deleted = await runners.deleteGroup(actor, workspace, String(form.get("id") ?? "")); |
| 81 | return deleted.ok ? { notice: "Deleted the group; its runners joined the default group." } : { error: deleted.error.message }; |
| 82 | } |
| 83 | case "settings": { |
| 84 | const saved = await runners.setSettings(actor, owner, { |
| 85 | agents_on_self_hosted: form.get("agents") === "on", |
| 86 | agent_labels: list("agentLabels"), |
| 87 | fork_pull_requests: form.get("forks") === "on", |
| 88 | }); |
| 89 | return saved.ok ? { notice: "Saved." } : { error: saved.error.message }; |
| 90 | } |
| 91 | case "inherit": { |
| 92 | const saved = await runners.setSettings(actor, owner, { inherit: true }); |
| 93 | return saved.ok ? { notice: "This project follows the workspace's runner settings again." } : { error: saved.error.message }; |
| 94 | } |
| 95 | default: |
| 96 | return { error: "Nothing to do." }; |
| 97 | } |
| 98 | } |