diff --git a/apps/app/src/directory-settings.tsx b/apps/app/src/directory-settings.tsx index 0b3aae6d..5e908d18 100644 --- a/apps/app/src/directory-settings.tsx +++ b/apps/app/src/directory-settings.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { type ReactNode, useState } from "react"; import { Button, Input } from "@ace/ui"; import type { Diagnostics } from "@ace/host/protocol"; @@ -9,6 +9,13 @@ import { Confirm, Group, Page, Problem, Row, Status, Value } from "./settings-la const SETUP = "https://github.com/githubnext/ace/blob/main/docs/setup.md#team-directory"; +/** What each service the directory can name offers, since an unnamed one leaves no trace elsewhere. */ +const services = [ + { key: "hosted", label: "Hosted channels", offers: "Move to cloud in a channel's menu" }, + { key: "app", label: "Web app", offers: "Ace in a browser on other devices" }, + { key: "compute", label: "Cloud compute", offers: "Run in cloud for new channels" }, +] as const; + const secrets = { keychain: "Saved in Keychain", environment: "From the environment", @@ -55,6 +62,9 @@ export function TeamSettings({ active }: { active: boolean }) { )} + {checked?.directory.url && checked.directory.synced && ( + + )} {!checks.open ? Waiting for Ace Helper… : !checked && !checks.error && Checking…} @@ -63,6 +73,39 @@ export function TeamSettings({ active }: { active: boolean }) { ); } +function Setup({ children }: { children: ReactNode }) { + return ( + + {children} + + ); +} + +/** Which services the joined directory names; whoever deployed it sets the rest up. */ +function TeamServices({ services: named }: { services: Diagnostics["directory"]["services"] }) { + return ( + + {services.map(({ key, label, offers }) => ( + + {named[key] ? URL.parse(named[key])?.host || named[key] : "Not set up"} + + ))} + {services.some(({ key }) => !named[key]) && ( +
+ + Whoever deployed the directory can add the rest. Set up team services + +
+ )} +
+ ); +} + /** A time today, or a date and time when older, so a stale sync doesn't read as recent. */ function when(time: number) { const date = new Date(time); @@ -127,15 +170,7 @@ function TeamDirectory( label="Directory" description={directory.url || ( <> - Not joined.{" "} - - Set up a directory - + Not joined. Set up a directory )} footer={ diff --git a/apps/host/src/diagnostics.ts b/apps/host/src/diagnostics.ts index c29087e0..90f35209 100644 --- a/apps/host/src/diagnostics.ts +++ b/apps/host/src/diagnostics.ts @@ -49,10 +49,11 @@ export async function diagnostics(sync?: boolean): Promise { Promise.all([git(), shell()]), tailnet.diagnostics(), ]); + const { hosted, app, compute } = directory.read(); return { tools, tailscale: config.loopback ? { ...tailscale, loopback: true } : tailscale, - directory: { ...directory.status(), secret: await secret }, + directory: { ...directory.status(), secret: await secret, services: { hosted, app, compute } }, shell: { ...environment }, }; } diff --git a/apps/host/src/protocol.ts b/apps/host/src/protocol.ts index c19dab6b..022df6b1 100644 --- a/apps/host/src/protocol.ts +++ b/apps/host/src/protocol.ts @@ -57,6 +57,8 @@ export type Diagnostics = { error?: string; failed?: number; secret: CredentialStatus; + /** The team services the directory named in its last answer; an unnamed one is not set up. */ + services: { hosted?: string; app?: string; compute?: string }; }; /** Whether the helper loaded the login shell's environment; `pending` while it retries. */ shell: { state: "adopted" | "pending" | "failed"; error?: string }; diff --git a/docs/setup.md b/docs/setup.md index c6ed967f..e81e64c3 100644 --- a/docs/setup.md +++ b/docs/setup.md @@ -69,7 +69,7 @@ for s in channel directory; do (cd services/$s && bunx wrangler deploy && printf ``` Then name the other services in the directory, from `services/directory`, so every host that joins -picks them up: +picks them up. `Settings → Team → Team services` shows which ones a host's directory names: - `HOSTED`: the channel service (`https://ace-channel..workers.dev`), offered in a channel's `Move to` menu.