ui

package
v1.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 31, 2026 License: MIT Imports: 2 Imported by: 0

README

gwag admin UI

React + MUI + TanStack Router admin console for an instance of gwag. Talks to the gateway over GraphQL only — every view (services, peers, schema, future pages) is a graphql-codegen-typed query against /graphql.

This is a dogfood of the project's stance: the gateway exposes its own admin operations as GraphQL (via self-registration of the control plane proto + planned huma route ingestion), and the UI consumes them the same way any external client would.

Build flow

1. start the gateway        cd ../examples/multi && ./run.sh
2. fetch + codegen          pnpm run gen
3. dev server               pnpm run dev   → http://localhost:5173
4. production build         pnpm run build → dist/

pnpm run gen is pnpm run schema && pnpm run codegen:

  • schemacurl ${GATEWAY_URL:-http://localhost:18080}/schema/graphql > schema.graphql
  • codegen — graphql-codegen reads schema.graphql + src/**/*.graphql and emits typed functions into src/api/gateway.ts.

After codegen, every page imports sdk.<OperationName> from @/api/client and gets typed args + responses for free. Add a new operation: drop a query Foo { ... } into src/queries.graphql (or inline in a .tsx via a tagged template — codegen reads both), re-run pnpm run gen, use sdk.Foo() in any component.

Layout

package.json                pnpm dependency manifest
codegen.ts                  graphql-codegen config
vite.config.ts              Vite + TanStack-Router-Vite plugin
schema.graphql              SDL cache (overwritten by `pnpm run schema`)
src/
  main.tsx                  Provider wiring (MUI theme, TanStack Query, Router)
  routeTree.gen.ts          Generated by @tanstack/router-plugin
  api/
    client.ts               GraphQLClient + sdk; lazy Authorization header
    auth.ts                 sessionStorage-backed admin token store
    events.ts               graphql-ws client (subscriptions over /api/graphql)
    gateway.ts              Generated by codegen (placeholder otherwise)
  providers/
    EventsProvider.tsx      graphql-ws subscriptions + ring buffer; useSubscribe() / useEvents()
  components/
    Layout.tsx              AppBar (settings + notifications) + Drawer shell
    SettingsDrawer.tsx      Admin token entry; useAdminToken() hook
    EventsTray.tsx          Slide-out drawer rendering recent events
  routes/
    __root.tsx              Wraps every page in <Layout>
    index.tsx               Dashboard (env, peers, services counters)
    services.tsx            Services table (ListServices)
    peers.tsx               Peers table + Forget mutation
    schema.tsx              SDL viewer (raw text from /schema/graphql)
  queries.graphql           Operations consumed by codegen

Hosting

All gateway endpoints live under /api/* so the SPA owns the root. In dev, Vite proxies /api to the gateway (default http://localhost:18080; override with GATEWAY_URL env). In production, pnpm run build populates dist/, and the //go:embed all:dist directive in ../ui/embed.go bakes it into the gateway binary — gateway.UIHandler(ui.FS()) mounts at / with SPA-style fallback to index.html.

Single-binary boot:

cd ui && pnpm install && pnpm run build
cd ..
go run ./examples/multi/cmd/gateway --nats-data /tmp/gw1
# UI at http://localhost:8080/, GraphQL at /api/graphql

Auth

The gateway gates write operations on a bearer token (logged at boot as admin token = …). To unlock the UI's Forget button and any future admin_* mutation:

  1. Click the gear icon in the AppBar.
  2. Paste the token into the Settings drawer.
  3. Save — graphql-request reads it from sessionStorage per request and sends Authorization: Bearer <token> to /graphql.

The badge dot on the gear is the "no token set" indicator. Storage is sessionStorage only — closing the tab discards it; refresh keeps it. There's intentionally no "remember me" option; the gateway boot log is the source of truth, and persistent UI storage of a long- lived bearer is the kind of thing that ends up in a screenshot.

If the deployment fronts the gateway with another auth layer (session cookie, OAuth proxy), that's same-origin transparent to the UI; the bearer is a separate concern for the gateway's own admin surface.

Events provider

The Layout is wrapped in <EventsProvider> so any descendant page can opt into a graphql-ws subscription:

import { useSubscribe } from '@/providers/EventsProvider';

useSubscribe<{ greeter_greetings: { greeting: string; forName: string } }>({
  id: 'greeter-alice',
  query: `subscription ($name:String!,$hmac:String!,$timestamp:Int!) {
    greeter_greetings(name:$name, hmac:$hmac, timestamp:$timestamp) {
      greeting forName
    }
  }`,
  variables: { name: 'alice', hmac: 'x', timestamp: 0 },
  onData: (p) => console.log(p),
});

Every event also lands in a global ring buffer (last 50). The bell icon in the AppBar shows the unread count and opens an EventsTray drawer that renders the buffer as a reverse-chronological feed. graphql-ws is lazy: the WebSocket opens on the first subscribe and closes after the last unsubscribe.

In production, subscription args (hmac, timestamp) come from admin_signSubscriptionToken. Dev gateways started with --insecure-subscribe accept any value.

Useful pnpm scripts

pnpm run dev      # vite dev server with HMR
pnpm run build    # tsc -b && vite build → dist/
pnpm run preview  # vite preview against dist/
pnpm run schema   # refresh schema.graphql from the running gateway
pnpm run codegen  # graphql-codegen against schema.graphql
pnpm run gen      # schema + codegen

Documentation

Overview

Package ui embeds the compiled SPA bundle (`dist/`) as an embed.FS so the gateway binary can serve the admin UI without any external static directory. Pair with gateway.UIHandler to mount it at the SPA root.

Build flow:

cd ui && pnpm install && pnpm run build   # populates dist/
go build ./...                              # embeds dist/* into the binary

dist/ is gitignored entirely — only generated content lives there. The tracked placeholder is `ui/fallback/index.html`; bin/build seeds (or recovers) dist/index.html from that fallback when pnpm run build fails or hasn't run yet, so //go:embed all:dist always finds at least one file. Run bin/build before `go build` on a fresh clone.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func FS

func FS() fs.FS

FS returns an fs.FS rooted at the dist directory (the build output), suitable to pass to gateway.UIHandler. Returns a non-nil FS even when only the placeholder is present.

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL