feat(ui): Setup GCP — pairing requests + channels admin (Pieza 2/3)#1
Merged
Conversation
Adds a "Setup GCP" item to the Console sidebar that lets HQ operators
manage the SitioUno fleet against the new fleet-registry-api endpoints
(see sitiouno/gcloud-office#1):
- View / approve / reject pending pairing requests from new branch
nodes that join over Tailscale.
- Create, configure, set-secret, test, enable/disable, and delete
notification channels (Telegram first), with secret values written
to GCP Secret Manager via the registry API and never persisted in
the browser.
The page talks to the registry API over REST (admin bearer), separate
from the existing gateway WebSocket adapter which has no REST proxy.
Base URL + token are resolved from VITE_REGISTRY_API_URL/TOKEN or from
runtime injection via window.__OPENCLAW_CONFIG__.registryApiUrl/Token,
mirroring how the gateway URL/token are resolved in src/App.tsx.
Channels page now also surfaces a small banner pointing to Setup GCP
for Telegram alerts, and the telegram channel-schema gains the required
chatId field so any future channel-config UI built on the existing
schema picks it up automatically.
i18n keys added under setupGcp.* and consoleNav.setupGcp in both en
and zh; channels.fields.chatId / placeholders.chatId added; channels.gcpNotice.* added.
Tests:
- registry-api-client URL/header/body resolution + error mapping
- SetupGcpPage smoke render + not-configured hint
build / typecheck / vitest (483 tests) all green.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
3 tasks
sitiouno
added a commit
that referenced
this pull request
Apr 26, 2026
…bearer) (#2) Adjust the registry-API client introduced in PR #1 to talk to a NEW local HQ sidecar on the Tailscale tailnet (`http://openclaw-hq:8781`) instead of a public Cloud Run endpoint, and remove the bearer-token plumbing. Why - Shipping a long-lived admin token in the browser bundle (even via runtime injection) is a meaningful security regression: any XSS, devtools peek, or accidental log capture leaks an admin credential. This was flagged by Jean as exposed surface. - The architecture rework moves all admin traffic inside the Tailscale VPN. The HQ sidecar listens only on the tailnet and validates the source identity server-side via `tailscale whois`. The VPN is the trust boundary, not a token. Changes - `src/lib/registry-api-client.ts`: - Default base URL `http://openclaw-hq:8781` (override via `VITE_REGISTRY_API_URL` or `window.__OPENCLAW_CONFIG__.registryApiUrl`). - Drop all token handling: no `Authorization` header, no `VITE_REGISTRY_API_TOKEN`, no `__OPENCLAW_CONFIG__.registryApiToken`, no token field on the resolved config. - Keep all typed methods (pairing.list/get/approve/reject/smokeTest, channels.list/get/create/update/delete/setSecret/test). - Add `notify.broadcast({ scope, text })` -> POST `/v1/notify`. - Tests: - `registry-api-client.test.ts`: drop bearer-header assertions; add explicit assertions that no `Authorization` header is sent; add coverage for the new Tailscale default and for `notify.broadcast`. - `SetupGcpPage.test.tsx`: rewrite the "not configured" expectation — with the new default the base URL is always populated, so we instead assert the default `openclaw-hq:8781` URL is rendered. Stub `fetch` so the auto-loaded pairing/channels lists don't hit the network. - `src/vite-env.d.ts`: drop `VITE_REGISTRY_API_TOKEN`, document the new default for `VITE_REGISTRY_API_URL`. - `.env.example`: drop the token line, point the URL example at the Tailscale HQ sidecar, explain the trust model in the comment. - i18n (`en` + `zh` `setupGcp.notConfigured.body`): rephrase to mention only the URL knob and the VPN trust boundary. The `ChannelsPage` GCP banner is unchanged — it already links to `/setup-gcp` with copy that does not reference the bearer or the external URL. Validation - `npm run typecheck`: clean. - `npm test`: 483/483 passing across 57 files. - `npm run build`: succeeds. Co-authored-by: Jean Garcia (via Codex Local) <jean@flexipos.us> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a Setup GCP item to the Console sidebar of the openclaw-office fork. From this page, HQ operators can:
This is Pieza 2/3 in the SitioUno alignment plan. The backend (Pieza 1) is sitiouno/gcloud-office#1 — that PR adds
/v1/pairing/*and/v1/channels/*to thefleet-registry-apiCloud Run service. This PR is the operator-facing UI that drives those endpoints.sitiouno/gcloud-officesitiouno/gcloud-officeArchitecture decisions
gatewayadapter has no REST proxy and is scoped to a single OpenClaw node, while pairing/channels are HQ-Cloud admin operations against a separate Cloud Run service. The new client lives insrc/lib/registry-api-client.tsand is invoked directly from a Zustand store; it does not go throughgetAdapter().VITE_REGISTRY_API_URL/VITE_REGISTRY_API_TOKENat build time, orwindow.__OPENCLAW_CONFIG__.registryApiUrl/Tokeninjected at runtime — same patternsrc/App.tsxuses forgatewayUrl/gatewayToken. No URL hard-coded.localStorage. The admin token is a server-side credential; it lives only in the env / runtime config object.secret_valuetoPOST /v1/channels/{id}/secret, then forgets it — the field is local component state, not Zustand state, and is cleared after submit. A read-only notice on the page reminds the operator.telegramentry insrc/lib/channel-schemas.tswas extended with the requiredchatIdtext field, so any future Channels-side flow that re-uses that schema picks it up automatically.Files added
src/lib/registry-api-client.ts— typed REST client (pairing.*,channels.*), env/runtime config resolution,RegistryApiError+RegistryApiNotConfiguredError.src/lib/__tests__/registry-api-client.test.ts— covers config resolution, bearer header, JSON body, error mapping, and not-configured behavior.src/store/console-stores/setupgcp-store.ts— Zustand store with separate loading/error/in-flight state for pairing and channels.src/components/pages/SetupGcpPage.tsx— page shell with two tabs (Pairing Requests/Notification Channels) and the Secret Manager notice.src/components/pages/SetupGcpPage.test.tsx— render smoke + not-configured hint test.src/components/console/setupgcp/PairingRequestsView.tsx— status-filter chips, expandable rows, Approve/Reject (with reason) actions.src/components/console/setupgcp/ChannelsAdminView.tsx— channel list, Create dialog (with chat_id + bot token for telegram), set-secret form, test button with inline result, enable/disable, delete.src/components/console/setupgcp/SecretManagerNotice.tsx— read-only "secrets stay in GCP" panel.Files modified
src/components/layout/ConsoleLayout.tsx— newCloudsidebar item between Cron and Settings.src/App.tsx—/setup-gcproute +PAGE_MAPentry.src/gateway/types.ts—PageIdgains"setupGcp".src/components/pages/ChannelsPage.tsx— banner pointing to Setup GCP for Telegram alerts.src/lib/channel-schemas.ts— telegram now requireschatIdalongsidebotToken.src/i18n/locales/{en,zh}/layout.json—consoleNav.setupGcp,pageTitles.setupGcp.src/i18n/locales/{en,zh}/console.json— fullsetupGcp.*tree,channels.fields.chatId,channels.placeholders.chatId,channels.gcpNotice.*.src/vite-env.d.ts— declaresVITE_REGISTRY_API_URL+VITE_REGISTRY_API_TOKEN..env.example— example block for the new env vars (no real values).Validation
npm run typecheck— clean.npm test— 483/483 passing (57 test files), including the 7 new tests.npm run build— production build succeeds.oxlint src/— no new warnings introduced (3 pre-existing warnings unchanged).lucide-react,react-i18next,react-router-dom,zustand.Test plan
VITE_REGISTRY_API_URLandVITE_REGISTRY_API_TOKENpointing at the staging fleet-registry-api → Setup GCP loads pending requests, approve flow returns 200.secret_refbecomesnotification-channel-telegram-{id}, Test button delivers a message.window.__OPENCLAW_CONFIG__ = { registryApiUrl, registryApiToken }at runtime viabin/openclaw-office.jstemplate → behaves identically to the env-var path.Screenshots
(to be added once the staging fleet-registry-api PR #1 is deployed and a real Telegram channel can be exercised)
Out of scope
notification_channels(Pieza 4, gcloud-office repo).POST /smoke-testexists in the API but requires the operator to paste the delegation token; deferred to a follow-up).🤖 Generated with Claude Code