Vectrum
Sign In
91 lines · 3.8 KB · TypeScript
"use client";

import { useActionState } from "react";
import type { FormState } from "@/app/actions/types";
import { PlusIcon, ServerIcon, TagIcon } from "@/components/icons";
import { CopyIconButton, TextField } from "@/components/settings-fields";
import { PlateBody, PlateFooter } from "@/components/settings-plate";
import { Alert, Button } from "@/components/ui";

function Command({ children }: { children: string }) {
  return (
    <div className="flex items-start gap-2 rounded-row well py-1.5 pr-1.5 pl-3.5">
      <pre className="min-w-0 flex-1 overflow-x-auto py-1.5 font-mono text-[12px] leading-5 text-ink">{children}</pre>
      <CopyIconButton text={children} label="Copy command" toast="Copied command" />
    </div>
  );
}

/** Registers a runner, then shows its token once with the commands that start it. */
export function NewRunnerForm({
  action,
  origin,
  scopeText,
}: {
  action: (prev: FormState, formData: FormData) => Promise<FormState>;
  origin: string;
  scopeText: string;
}) {
  const [state, formAction, pending] = useActionState(action, undefined);

  if (state?.token) {
    return (
      <>
        <PlateBody className="max-w-3xl">
          <Alert kind="success">Runner registered. Copy its token now — you won’t be able to see it again.</Alert>
          <div className="flex items-center gap-2 rounded-row recipe-well-field py-1.5 pr-1.5 pl-3.5">
            <code className="flex-1 font-mono text-[12.5px] leading-5 break-all text-ink">{state.token}</code>
            <CopyIconButton text={state.token} label="Copy token" toast="Copied token" />
          </div>
          <div className="flex flex-col gap-2">
            <p className="type-title text-ink">Start it on your machine</p>
            <p className="type-body text-ink2">
              The runner is one file and needs Node.js 18+ and git. Download it, then start it with the token. Jobs run as the user
              that starts it, so use an account (or a VM or container) set aside for it.
            </p>
            <Command>{`curl -fsSLO ${origin}/vectrum-runner.mjs`}</Command>
            <Command>{`node vectrum-runner.mjs --url ${origin} --token ${state.token}`}</Command>
            <p className="type-body text-ink2">
              It shows up as online here within a few seconds. Jobs with <code className="font-mono text-[12px]">runs-on: self-hosted</code>{" "}
              run on any runner; use labels to pick particular ones.
            </p>
          </div>
        </PlateBody>
        <PlateFooter>
          <Button type="button" onClick={() => window.location.reload()}>
            Done
          </Button>
        </PlateFooter>
      </>
    );
  }

  return (
    <form action={formAction}>
      <PlateBody className="max-w-2xl gap-6">
        <p className="type-body text-ink2">{scopeText}</p>
        <TextField label="Name" icon={<ServerIcon />} name="name" required maxLength={100} description="e.g. the machine it runs on." />
        <TextField
          label="Labels"
          icon={<TagIcon />}
          name="labels"
          optional
          placeholder="docker, gpu"
          description={
            <>
              Jobs pick runners by label with <code className="font-mono text-[12px]">runs-on</code>. Every runner also has{" "}
              <code className="font-mono text-[12px]">self-hosted</code> and its OS and architecture (e.g.{" "}
              <code className="font-mono text-[12px]">linux</code>, <code className="font-mono text-[12px]">x64</code>).
            </>
          }
        />
        {state?.error && <Alert>{state.error}</Alert>}
      </PlateBody>
      <PlateFooter>
        <Button variant="primary" icon={<PlusIcon />} disabled={pending}>
          {pending ? "Registering…" : "Register Runner"}
        </Button>
      </PlateFooter>
    </form>
  );
}