Web client to the Control Plane API.
In order to avoid the complexity of server-side rendering (and running JS on the rack in general), the web console is a fully client-side React app. We use Vite (which uses Rollup internally for production builds) to build a set of assets (index.html
, JS bundles, CSS, fonts, images) and we serve those assets as static files from a special set of console endpoints in Nexus. From the control plane's point of view, the web console simply is:
- a tarball of static assets + endpoints that serve them
- a few other endpoints to handle auth actions like login/logout
- a table of sessions (not necessarily console-specific)
The web console has no special privileges as an API consumer. We log in (which sets a cookie) and make cookie-authed API requests after that. See RFD 223 Web Console Architecture for a more detailed discussion. The endpoints live in nexus/src/external_api/console_api.rs
in Omicron.
- TypeScript + React (+ React Router, React Query, React Table)
- Vite for dev server and browser bundling
- Tailwind for styling
- oxide.ts generates an API client from Nexus's OpenAPI spec
- Testing
- Mock Service Workers for mock API server
- Vitest for unit tests
- Playwright for E2E browser tests
- Ladle, a lightweight Storybook clone based on Vite
The app is in app
. You can see the route structure in app/routes.tsx
. In libs
we have a ui
dir where the low-level components live (and the Storybook definition) and an api
dir where we keep the generated API client and a React Query wrapper for it. These directories are aliased in tsconfig.json
for easy import from the main app as @oxide/ui
and @oxide/api
, respectively.
The fastest way to see the console in action is to check out the repo, run npm install && npm run start:msw
, and go to http://localhost:4000 in the browser. This runs the console with a mock API server that runs in a Service Worker.
npm install
Run Vite dev server + MSW mock API
This is the way we do nearly all console development. Just run:
npm run start:msw
and navigate to http://localhost:4000 in the browser. The running app will automatically update when you write a source file. This mode uses Mock Service Workers to run a mock API right the browser. This mock API is also used in tests.
You can also run the console dev server locally with the mock server off, instead passing requests through to localhost:12220
. Run npm run start
(note no :msw
) and navigate to http://localhost:4000/ in the browser. It will mostly not work unless Nexus is running at localhost:12220
, which is the default for omicron-dev
(see Running Omicron (Simulated) for how to set that up).
One way to run everything is to use the tools/start_api.sh
script, which uses tmux to run multiple processes in different panes and automatically populates some fake data (see tools/populate_omicron_data.sh
to see exactly what). From the omicron directory, run tools/start_api.sh
. Since we're assuming console
and omicron
are next to each other, that looks like this:
../console/tools/start_api.sh
Configuring tmux
Because running the API requires running two programs plus the populate data script, we use tmux to split the terminal into panes so we can see the log output of all three. tmux has its own complicated set of keyboard shortcuts. A good way to avoid having to deal with that if you want to poke around in the server logs is to create~/.tmux.conf
that looks like this:
set -g mouse on
This will let you click to focus a pane and scrolling output with the mouse will automatically work. If you do want to use the shortcuts, here's a tmux.conf
to make it a little more vim-like:
# change leader key from ctrl-b to ctrl-a
unbind C-b
set-option -g prefix C-a
bind-key C-a send-prefix
# ctrl-a v makes a vertical split, ctrl-a h make a horizontal split
bind v split-window -h
bind s split-window -v
unbind '"'
unbind %
# ctrl-a h/j/k/l move between panes
bind h select-pane -L
bind j select-pane -D
bind k select-pane -U
bind l select-pane -R
set -g mouse on
Run Ladle
npm run storybook
This will start a preview environment for the UI components in the project. The window should open automatically, but if not go to http://localhost:61000
.
E2E tests with Playwright
Playwright tests match the filename pattern .e2e.ts
. The basic command to run all tests is npm run e2e
. You may have to run npx playwright install
after npm install
to get the browser binaries.
There are two types of tests in our project. Validation tests which rely on mocked responses from MSW and smoke tests which assume a clean environment. Smoke tests are design to be ran against a rack meaning they create any required resources for the test and clean up after themselves.
Tests are ran across chrome
, firefox
, and safari
when running npm run e2e
. Test runs can be isolated to a single browser by setting a BROWSER
environment variable like BROWSER=chrome npm run e2e
. Tests can be further isolated down to either smoke or validation suites by providing a --project
argument. For example, npm run e2e -- --project=validate-chrome
or npm run e2e -- --project=smoke-firefox
.
Some debugging tricks (see the docs here for more details):
- Add
await page.pause()
to a test and runBROWSER=chrome npm run e2e <test file> -- --headed
to run a test in a single headed browser with the excellent Inspector open and pause at that line. This is perfect for making sure the screen looks like you expect at that moment and testing selectors to use in the next step.
To debug end-to-end failures on CI checkout the branch with the failure and run ./tools/debug-ci-e2e-fail.sh
. It'll download the latest failures from CI and allow you to open a playwright trace of the failure.
Command | Description |
---|---|
npm test run |
Vitest tests |
npm test |
Vitest tests in watch mode |
npm run e2ec |
Only run end-to-end tests in chrome |
npm run lint |
ESLint |
npx tsc |
Check types |
npm run ci |
Lint, tests, and types |
npm run fmt |
Format everything. Rarely necessary thanks to editor integration |
npm run gen-api |
Generate API client (see docs/update-pinned-api.md ) |
npm run start:mock-api |
Serve mock API on port 12220 |