Service principals table
Machine identities with their scopes, last use and a button to disable one. It loads its data on the server through the block's client.
pnpm dev:example) to see it.Add the components
terminal npx shadcn add alert badge button card empty skeleton tableInstall the glue
Install the block with
npx better-supabase add service-principals. The glue items are copied into your app, so edit them as you need.None: the page calls the block's client on the server.
Write the component
React and shadcn
View in the exampleservice-principals-table.tsx type PrincipalsState = | { readonly kind: "ok"; readonly principals: readonly ServicePrincipalRow[] } | { readonly kind: "error" } | { readonly kind: "no-organization" }; async function loadPrincipalsState(): Promise<PrincipalsState> { try { const principals = await loadServicePrincipals(); return principals ? { kind: "ok", principals } : { kind: "no-organization" }; } catch (error) { unstable_rethrow(error); return { kind: "error" }; } } // oxlint-disable-next-line typescript/require-await -- a Server Action must be async, and refresh() returns nothing to await async function retry() { "use server"; refresh(); } /** * Render inside `<Suspense>`: the machine identities workflows and webhooks * run as, with their role. Members with `service_principals.manage` add * and disable them. Members without `service_principals.read` see nothing. */ export async function ServicePrincipalsTable() { const [session, t, format] = await Promise.all([ getSession(), getExtracted("servicePrincipals"), getFormatter(), ]); if (!can(session, "service_principals.read")) { return null; } const state = await loadPrincipalsState(); if (state.kind === "no-organization") { return null; } const manage = can(session, "service_principals.manage"); const roles = new Map([ ["owner", t("Owner")], ["admin", t("Admin")], ["member", t("Member")], ]); return ( <Card data-recipe="service-principals-table"> <CardHeader> <CardTitle>{t("Service principals")}</CardTitle> <CardDescription> {t( "Machine identities that workflows and webhooks run as. They can't sign in.", )} </CardDescription> {manage && state.kind === "ok" ? ( <CardAction> <CreateServicePrincipalDialog /> </CardAction> ) : null} </CardHeader> <CardContent> {state.kind === "error" ? ( <Alert variant="destructive"> <TriangleAlertIcon /> <AlertTitle>{t("The service principals didn't load")}</AlertTitle> <AlertDescription>{t("Try again in a moment.")}</AlertDescription> <AlertAction> <form action={retry}> <Button size="sm" variant="outline" type="submit"> {t("Retry")} </Button> </form> </AlertAction> </Alert> ) : state.principals.length === 0 ? ( <Empty className="border"> <EmptyHeader> <EmptyMedia variant="icon"> <BotIcon /> </EmptyMedia> <EmptyTitle>{t("No service principals yet")}</EmptyTitle> <EmptyDescription> {manage ? t("Add one so automations don't run as a person.") : t("An admin can add one for automations.")} </EmptyDescription> </EmptyHeader> {manage ? ( <EmptyContent> <CreateServicePrincipalDialog /> </EmptyContent> ) : null} </Empty> ) : ( <Table> <TableHeader> <TableRow> <TableHead>{t("Name")}</TableHead> <TableHead>{t("Role")}</TableHead> <TableHead>{t("Status")}</TableHead> <TableHead>{t("Created")}</TableHead> {manage ? ( <TableHead className="text-right">{t("Actions")}</TableHead> ) : null} </TableRow> </TableHeader> <TableBody> {state.principals.map((principal) => ( <TableRow key={principal.id}> <TableCell> <div className="flex items-center gap-2"> <span className="text-sm font-medium"> {principal.name} </span> {principal.isDefault ? ( <Badge variant="primary-light">{t("Default")}</Badge> ) : null} </div> </TableCell> <TableCell className="text-sm"> {roles.get(principal.role) ?? principal.role} </TableCell> <TableCell> {principal.disabledAt ? ( <Badge variant="secondary">{t("Disabled")}</Badge> ) : ( <Badge variant="success-light">{t("Active")}</Badge> )} </TableCell> <TableCell className="text-xs text-muted-foreground"> {format.dateTime(new Date(principal.createdAt), { dateStyle: "medium", })} </TableCell> {manage ? ( <TableCell className="text-right"> {principal.disabledAt ? null : ( <DisableServicePrincipalButton id={principal.id} name={principal.name} /> )} </TableCell> ) : null} </TableRow> ))} </TableBody> </Table> )} </CardContent> </Card> ); } export function ServicePrincipalsTableSkeleton() { return <Skeleton className="h-40 rounded-xl" aria-busy="true" />; }