# Oxide Web Console Web client to the [Oxide API](https://github.com/oxidecomputer/omicron). ![screenshot of instances list page](docs/readme-screenshot.png) ## [Live demo](https://console-preview.oxide.computer) At https://console-preview.oxide.computer, the console is deployed as a static site with a mock API running in a [Service Worker](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API). You can create mock resources and they will persist across client-side navigations, but they exist only in the browser: nobody else can see them and the mock "DB" is reset on pageload. Request and response bodies in the mock API match the Oxide API's [OpenAPI spec](https://github.com/oxidecomputer/omicron/blob/main/openapi/nexus.json), but behavior is only mocked in as much detail as is required for development and testing of the console and is not fully representative of the real API. ## Goals and principles - The console is not an application, it is a _client_ to the application (the Oxide API) — minimize client-side state - Be a transparent view onto the API — teach API concepts and avoid making the user learn anything console-specific - Simple, predictable, and broadly functional everywhere is better than deeply polished in a few places - When we don't have product clarity, build the simplest thing and move on - There is enough technical risk elsewhere down the stack, so be conservative: - Focus on building things we can be sure we'll need - Rely on good libraries that we can use the way they want to be used - Building on bad abstractions is costly — duplication is fine while you figure out the right abstraction - Linkability — use routes to capture app state - Rely on code generated from API spec for correctness ## Architecture ![console client-server architecture diagram](docs/architecture-browser-only.svg) In order to avoid the complexity of server-side rendering (and running JS on the rack), 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 API server's point of view, the web console simply is: - a directory of static assets and some endpoints that serve them - a few other endpoints to handle auth actions like login/logout - a table of sessions (console-specific in practice, but not intrinsically so) The web console has no special privileges as an API consumer. Logging in sets a cookie, and we make cookie-authed API requests after that. See [RFD 223 Web Console Architecture](https://rfd.shared.oxide.computer/rfd/0223) for a more detailed discussion. The endpoints live in [`nexus/src/external_api/console_api.rs`](https://github.com/oxidecomputer/omicron/blob/c3048a1b43b046c284432eba34d0bc1933de4d56/nexus/src/external_api/console_api.rs) in Omicron. ## Tech - [TypeScript](https://www.typescriptlang.org/) + [React](https://reactjs.org/) (+ [React Router](https://reactrouter.com/), [TanStack Query](https://tanstack.com/query/latest/), [TanStack Table](https://tanstack.com/table/v8/)) - [Vite](https://vitejs.dev/) for dev server and browser bundling - [Tailwind](https://tailwindcss.com/) for styling - [oxide.ts](https://github.com/oxidecomputer/oxide.ts) generates an API client from [Nexus's OpenAPI spec](https://github.com/oxidecomputer/omicron/blob/main/openapi/nexus.json) - Testing - [Mock Service Worker](https://mswjs.io/) for mock API server - [Vitest](https://vitest.dev/) for unit tests, with [Browser Mode](https://vitest.dev/guide/browser/) for component tests - [Playwright](https://playwright.dev/) for E2E browser tests ## Directory structure The app is in [`app`](app). You can see the route structure in [`app/routes.tsx`](app/routes.tsx). Also in [`app`](app) we have a [`ui`](app/ui) dir where the low-level components live and an [`api`](app/api) dir where we keep the generated API client and a React Query wrapper for it. The latter is aliased in [`tsconfig.json`](tsconfig.json) for easy import from the main app as `@oxide/api`. ## Development ### Node.js version Use Node.js v24+ and npm v12+. Node bundles an older npm, so upgrade it with `npm install --global npm@12`. ### Install dependencies ```sh npm install npx playwright install # only needed to run e2e tests ``` ### Run Vite dev server + [MSW](https://mswjs.io/) mock API This is the way we do most console development. Just run: ``` npm run dev ``` 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 Worker to run a mock API right the browser. This mock API is also used in tests. #### Specifying non-default user Pick a user from the list of users in [`mock-api/user.ts`](/mock-api/user.ts). The one without fleet viewer permissions is `Hans Jonas`. Open the browser console and run: ```js document.cookie = 'msw-user=Hans Jonas;domain=localhost;path=/' ``` You are now user Hans Jonas. To go back to the default, delete the cookie. (We will get the mock API to clear the cookie for you on logout soon.) ### Run Vite dev server against local Nexus API 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:nexus` and navigate to http://localhost:4000/login/test-suite-silo/local in the browser. It will not work unless Nexus is running at `localhost:12220`, which is the default for `omicron-dev` (see [Running Omicron (Simulated)](https://github.com/oxidecomputer/omicron/blob/main/docs/how-to-run-simulated.adoc) 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`](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: ```sh ../console/tools/start_api.sh ``` ### Run local dev server against the dogfood rack 1. Get on the VPN 1. Run `npm run start:dogfood` 1. Go to https://localhost:4000 (note the https). The page won't work yet, and you'll get redirected to `/login`, which will look like a 404 1. Go to https://oxide.sys.rack2.eng.oxide.computer in another tab and log in 1. Open the dev tools Storage tab and copy the `session` cookie value, which should look like `d9b1a96e151092eb0ea08b1a0d8c4788441f1894` 1. Go back to your localhost tab, open the developer console, and run ```js document.cookie = 'session=d9b1a96e151092eb0ea08b1a0d8c4788441f1894;domain=localhost;path=/' ``` Go to https://localhost:4000 again and you should be logged in. ### E2E tests with [Playwright](https://playwright.dev/) Playwright tests live in [`test/e2e`](test/e2e/). `npm run e2e` runs the tests in Chrome, Firefox, and Safari, but this is rarely necessary in local dev. `npm run e2ec` is a shortcut for `playwright test --project=chrome`, which runs the tests in Chrome only (the fastest one, useful for local dev). Playwright has an excellent [UI mode](https://playwright.dev/docs/test-ui-mode) for running and debugging tests that you can get to by running `npm run e2e -- --ui`. To debug end-to-end failures on CI, check out 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](https://playwright.dev/docs/trace-viewer-intro#viewing-the-trace) of the failure. ### Summary of useful commands | Command | Description | | ------------------------ | ---------------------------------------------------------------------------------- | | `npm run dev` | Run Vite dev server with mock API | | `npm test` | Vitest unit tests | | `npm run e2ec` | Run Playwright E2E tests in Chrome only | | `npm run lint` | ESLint | | `npm run tsc` | Check types | | `npm run ci` | Lint, tests (unit and e2e), 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`](docs/update-pinned-api.md)) | | `npm run start:mock-api` | Serve mock API on port 12220 | ## Relevant RFDs - [RFD 4 User-Facing API](https://rfd.shared.oxide.computer/rfd/0004) - [RFD 169 Console Authentication and Session Management](https://rfd.shared.oxide.computer/rfd/0169) - [RFD 223 Web Console Architecture](https://rfd.shared.oxide.computer/rfd/0223)