{"id":21660024,"url":"https://github.com/oxidecomputer/console","last_synced_at":"2025-07-22T23:38:38.211Z","repository":{"id":188977828,"uuid":"249571104","full_name":"oxidecomputer/console","owner":"oxidecomputer","description":"Oxide Web Console","archived":false,"fork":false,"pushed_at":"2025-07-14T21:29:10.000Z","size":24074,"stargazers_count":171,"open_issues_count":224,"forks_count":14,"subscribers_count":18,"default_branch":"main","last_synced_at":"2025-07-14T23:47:10.942Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://console-preview.oxide.computer","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mpl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/oxidecomputer.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2020-03-23T23:59:58.000Z","updated_at":"2025-07-11T21:27:44.000Z","dependencies_parsed_at":"2023-11-26T01:26:02.306Z","dependency_job_id":"0b2f18f6-c542-411b-9ee0-00c69b80802e","html_url":"https://github.com/oxidecomputer/console","commit_stats":null,"previous_names":["oxidecomputer/console"],"tags_count":9,"template":false,"template_full_name":null,"purl":"pkg:github/oxidecomputer/console","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oxidecomputer%2Fconsole","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oxidecomputer%2Fconsole/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oxidecomputer%2Fconsole/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oxidecomputer%2Fconsole/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/oxidecomputer","download_url":"https://codeload.github.com/oxidecomputer/console/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oxidecomputer%2Fconsole/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":266591232,"owners_count":23953082,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-22T02:00:09.085Z","response_time":66,"last_error":null,"robots_txt_status":null,"robots_txt_updated_at":null,"robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-25T09:32:03.960Z","updated_at":"2025-07-22T23:38:38.185Z","avatar_url":"https://github.com/oxidecomputer.png","language":"TypeScript","readme":"# Oxide Web Console\n\nWeb client to the [Oxide API](https://github.com/oxidecomputer/omicron).\n\n![screenshot of instances list page](docs/readme-screenshot.png)\n\n## [Live demo](https://console-preview.oxide.computer)\n\nAt 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.\n\n## Goals and principles\n\n- The console is not an application, it is a _client_ to the application (the Oxide API) — minimize client-side state\n- Be a transparent view onto the API — teach API concepts and avoid making the user learn anything console-specific\n- Simple, predictable, and broadly functional everywhere is better than deeply polished in a few places\n- When we don't have product clarity, build the simplest thing and move on\n- There is enough technical risk elsewhere down the stack, so be conservative:\n  - Focus on building things we can be sure we'll need\n  - Rely on good libraries that we can use the way they want to be used\n  - Building on bad abstractions is costly — duplication is fine while you figure out the right abstraction\n- Linkability — use routes to capture app state\n- Rely on code generated from API spec for correctness\n\n## Architecture\n\n![console client-server architecture diagram](docs/architecture-browser-only.svg)\n\nIn 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:\n\n- a directory of static assets and some endpoints that serve them\n- a few other endpoints to handle auth actions like login/logout\n- a table of sessions (console-specific in practice, but not intrinsically so)\n\nThe 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) (internal document for now) 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.\n\n## Tech\n\n- [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/))\n- [Vite](https://vitejs.dev/) for dev server and browser bundling\n- [Tailwind](https://tailwindcss.com/) for styling\n- [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)\n- Testing\n  - [Mock Service Worker](https://mswjs.io/) for mock API server\n  - [Vitest](https://vitest.dev/) for unit tests\n  - [Playwright](https://playwright.dev/) for E2E browser tests\n\n## Directory structure\n\nThe 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`.\n\n## Development\n\n### Node.js version\n\nUse Node.js v18+.\n\n### Install dependencies\n\n```sh\nnpm install\nnpx playwright install # only needed to run e2e tests\n```\n\n### Run Vite dev server + [MSW](https://mswjs.io/) mock API\n\nThis is the way we do most console development. Just run:\n\n```\nnpm run dev\n```\n\nand 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.\n\n#### Specifying non-default user\n\nPick a user from the list of users in\n[`mock-api/user.ts`](/mock-api/user.ts). The one without fleet\nviewer permissions is `Hans Jonas`. Open the browser console and run:\n\n```js\ndocument.cookie = 'msw-user=Hans Jonas;domain=localhost;path=/'\n```\n\nYou 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.)\n\n### Run Vite dev server against local Nexus API\n\nYou 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).\n\nOne 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:\n\n```sh\n../console/tools/start_api.sh\n```\n\n### Run local dev server against the dogfood rack\n\n1. Get on the VPN\n1. Run `npm run start:dogfood`\n1. 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\n1. Go to https://oxide.sys.rack2.eng.oxide.computer in another tab and log in\n1. Open the dev tools Storage tab and copy the `session` cookie value, which should look like `d9b1a96e151092eb0ea08b1a0d8c4788441f1894`\n1. Go back to your localhost tab, open the developer console, and run\n\n```js\ndocument.cookie = 'session=d9b1a96e151092eb0ea08b1a0d8c4788441f1894;domain=localhost;path=/'\n```\n\nGo to https://localhost:4000 again and you should be logged in.\n\n### E2E tests with [Playwright](https://playwright.dev/)\n\nPlaywright 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`.\n\nTo 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.\n\n### Summary of useful commands\n\n| Command                  | Description                                                                        |\n| ------------------------ | ---------------------------------------------------------------------------------- |\n| `npm run dev`            | Run Vite dev server with mock API                                                  |\n| `npm test`               | Vitest unit tests                                                                  |\n| `npm run e2ec`           | Run Playwright E2E tests in Chrome only                                            |\n| `npm run lint`           | ESLint                                                                             |\n| `npx tsc`                | Check types                                                                        |\n| `npm run ci`             | Lint, tests (unit and e2e), and types                                              |\n| `npm run fmt`            | Format everything. Rarely necessary thanks to editor integration                   |\n| `npm run gen-api`        | Generate API client (see [`docs/update-pinned-api.md`](docs/update-pinned-api.md)) |\n| `npm run start:mock-api` | Serve mock API on port 12220                                                       |\n\n## Relevant RFDs\n\n- [RFD 4 User-Facing API](https://rfd.shared.oxide.computer/rfd/0004)\n- [RFD 169 Console Authentication and Session Management](https://rfd.shared.oxide.computer/rfd/0169)\n- [RFD 223 Web Console Architecture](https://rfd.shared.oxide.computer/rfd/0223)\n","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foxidecomputer%2Fconsole","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Foxidecomputer%2Fconsole","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foxidecomputer%2Fconsole/lists"}