mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 10:21:52 +00:00
re-arrange (#3123)
This commit is contained in:
185
web/CLAUDE.md
Normal file
185
web/CLAUDE.md
Normal file
@@ -0,0 +1,185 @@
|
||||
# Web — Angular Frontend
|
||||
|
||||
Angular 20 + TypeScript workspace using [Taiga UI](https://taiga-ui.dev/) component library.
|
||||
|
||||
## Projects
|
||||
|
||||
- `projects/ui/` — Main admin interface
|
||||
- `projects/setup-wizard/` — Initial setup
|
||||
- `projects/start-tunnel/` — VPN management UI
|
||||
- `projects/shared/` — Common library (API clients, components, i18n)
|
||||
- `projects/marketplace/` — Service discovery
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
npm ci
|
||||
npm run start:ui # Dev server with mocks
|
||||
npm run build:ui # Production build
|
||||
npm run check # Type check all projects
|
||||
```
|
||||
|
||||
## Golden Rules
|
||||
|
||||
1. **Taiga-first.** Use Taiga components, directives, and APIs whenever possible. Avoid hand-rolled HTML/CSS unless absolutely necessary. If Taiga has a component for it, use it.
|
||||
|
||||
2. **Pattern-match.** Nearly anything we build has a similar example elsewhere in this codebase. Search for existing patterns before writing new code. Copy the conventions used in neighboring components.
|
||||
|
||||
3. **When unsure about Taiga, ask or look it up.** Use `WebFetch` against `https://taiga-ui.dev/llms-full.txt` to search for component usage, or ask the user. Taiga docs are authoritative. See [Taiga UI Docs](#taiga-ui-docs) below.
|
||||
|
||||
## Taiga UI Docs
|
||||
|
||||
Taiga provides an LLM-friendly reference at `https://taiga-ui.dev/llms-full.txt` (~2200 lines covering all components with code examples). Use `WebFetch` to search it when you need to look up a component, directive, or API:
|
||||
|
||||
```
|
||||
WebFetch url=https://taiga-ui.dev/llms-full.txt prompt="How to use TuiTextfield with a select dropdown"
|
||||
```
|
||||
|
||||
When implementing something with Taiga, **also check existing code in this project** for local patterns and conventions — Taiga usage here may have project-specific wrappers or style choices.
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
### API Layer (JSON-RPC)
|
||||
|
||||
All backend communication uses JSON-RPC, not REST.
|
||||
|
||||
- **`HttpService`** (`shared/src/services/http.service.ts`) — Low-level HTTP wrapper. Sends JSON-RPC POST requests via `rpcRequest()`.
|
||||
- **`ApiService`** (`ui/src/app/services/api/embassy-api.service.ts`) — Abstract class defining 100+ RPC methods. Two implementations:
|
||||
- `LiveApiService` — Production, calls the real backend
|
||||
- `MockApiService` — Development with mocks
|
||||
- **`api.types.ts`** (`ui/src/app/services/api/api.types.ts`) — Namespace `RR` with all request/response type pairs.
|
||||
|
||||
**Calling an RPC endpoint from a component:**
|
||||
```typescript
|
||||
private readonly api = inject(ApiService)
|
||||
|
||||
async doSomething() {
|
||||
await this.api.someMethod({ param: value })
|
||||
}
|
||||
```
|
||||
|
||||
The live API handles `x-patch-sequence` headers — after a mutating call, it waits for the PatchDB WebSocket to catch up before resolving. This ensures the UI always reflects the result of the call.
|
||||
|
||||
### PatchDB (Reactive State)
|
||||
|
||||
The backend pushes state diffs to the frontend via WebSocket. This is the primary way components get data.
|
||||
|
||||
- **`PatchDbSource`** (`ui/src/app/services/patch-db/patch-db-source.ts`) — Establishes a WebSocket subscription when authenticated. Buffers updates every 250ms.
|
||||
- **`DataModel`** (`ui/src/app/services/patch-db/data-model.ts`) — TypeScript type for the full database shape (`ui`, `serverInfo`, `packageData`).
|
||||
- **`PatchDB<DataModel>`** — Injected service. Use `watch$()` to observe specific paths.
|
||||
|
||||
**Watching data in a component:**
|
||||
```typescript
|
||||
private readonly patch = inject<PatchDB<DataModel>>(PatchDB)
|
||||
|
||||
// Watch a specific path — returns Observable, convert to Signal with toSignal()
|
||||
readonly name = toSignal(this.patch.watch$('ui', 'name'))
|
||||
readonly status = toSignal(this.patch.watch$('serverInfo', 'statusInfo'))
|
||||
readonly packages = toSignal(this.patch.watch$('packageData'))
|
||||
```
|
||||
|
||||
**In templates:** `{{ name() }}` — signals are called as functions.
|
||||
|
||||
### WebSockets
|
||||
|
||||
Three WebSocket use cases, all opened via `api.openWebsocket$<T>(guid)`:
|
||||
|
||||
1. **PatchDB** — Continuous state patches (managed by `PatchDbSource`)
|
||||
2. **Logs** — Streamed via `followServerLogs` / `followPackageLogs`, buffered every 1s
|
||||
3. **Metrics** — Real-time server metrics via `followServerMetrics`
|
||||
|
||||
### Navigation & Routing
|
||||
|
||||
- **Main app** (`ui/src/app/routing.module.ts`) — NgModule-based with guards (`AuthGuard`, `UnauthGuard`, `stateNot()`), lazy loading via `loadChildren`, `PreloadAllModules`.
|
||||
- **Portal routes** (`ui/src/app/routes/portal/portal.routes.ts`) — Modern array-based routes with `loadChildren` and `loadComponent`.
|
||||
- **Setup wizard** (`setup-wizard/src/app/app.routes.ts`) — Standalone `loadComponent()` per step.
|
||||
- Route config uses `bindToComponentInputs: true` — route params bind directly to component `@Input()`.
|
||||
|
||||
### Forms
|
||||
|
||||
Two patterns:
|
||||
|
||||
1. **Dynamic (spec-driven)** — `FormService` (`ui/src/app/services/form.service.ts`) generates `FormGroup` from IST (Input Specification Type) schemas. Supports text, textarea, number, color, datetime, object, list, union, toggle, select, multiselect, file. Used for service configuration forms.
|
||||
|
||||
2. **Manual** — Standard Angular `FormGroup`/`FormControl` with validators. Used for login, setup wizard, system settings.
|
||||
|
||||
Form controls live in `ui/src/app/routes/portal/components/form/controls/` — each extends a base `Control<Spec, Value>` class and uses Taiga input components.
|
||||
|
||||
**Dialog-based forms** use `PolymorpheusComponent` + `TuiDialogContext` for modal rendering.
|
||||
|
||||
### i18n
|
||||
|
||||
- **`i18nPipe`** (`shared/src/i18n/i18n.pipe.ts`) — Translates English keys to the active language.
|
||||
- **Dictionaries** live in `shared/src/i18n/dictionaries/` (en, es, de, fr, pl).
|
||||
- Usage in templates: `{{ 'Some English Text' | i18n }}`
|
||||
|
||||
### Services & State
|
||||
|
||||
Services often extend `Observable` and expose reactive streams via DI:
|
||||
|
||||
- **`ConnectionService`** — Combines network status + WebSocket readiness
|
||||
- **`StateService`** — Polls server availability, manages app state (`running`, `initializing`, etc.)
|
||||
- **`AuthService`** — Tracks `isVerified$`, triggers PatchDB start/stop
|
||||
- **`PatchMonitorService`** — Starts/stops PatchDB based on auth state
|
||||
- **`PatchDataService`** — Watches entire DB, updates localStorage bootstrap
|
||||
|
||||
## Component Conventions
|
||||
|
||||
- **Standalone components** preferred (no NgModule). Use `imports` array in `@Component`.
|
||||
- **`export default class`** for route components (enables direct `loadComponent` import).
|
||||
- **`inject()`** function for DI (not constructor injection).
|
||||
- **`signal()`** and `computed()`** for local reactive state.
|
||||
- **`toSignal()`** to convert Observables (e.g., PatchDB watches) to signals.
|
||||
- **`ChangeDetectionStrategy.OnPush`** on almost all components.
|
||||
- **`takeUntilDestroyed(inject(DestroyRef))`** for subscription cleanup.
|
||||
|
||||
## Common Taiga Patterns
|
||||
|
||||
### Textfield + Select (dropdown)
|
||||
```html
|
||||
<tui-textfield tuiChevron>
|
||||
<label tuiLabel>Label</label>
|
||||
<input tuiSelect />
|
||||
<tui-data-list *tuiTextfieldDropdown>
|
||||
<button tuiOption [value]="item" *ngFor="let item of items">{{ item }}</button>
|
||||
</tui-data-list>
|
||||
</tui-textfield>
|
||||
```
|
||||
Provider to remove the X clear button:
|
||||
```typescript
|
||||
providers: [tuiTextfieldOptionsProvider({ cleaner: signal(false) })]
|
||||
```
|
||||
|
||||
### Buttons
|
||||
```html
|
||||
<button tuiButton appearance="primary">Submit</button>
|
||||
<button tuiButton appearance="secondary">Cancel</button>
|
||||
<button tuiIconButton appearance="icon" iconStart="@tui.trash"></button>
|
||||
```
|
||||
|
||||
### Dialogs
|
||||
```typescript
|
||||
// Confirmation
|
||||
this.dialog.openConfirm({ label: 'Warning', data: { content: '...', yes: 'Confirm', no: 'Cancel' } })
|
||||
|
||||
// Custom component in dialog
|
||||
this.dialog.openComponent(new PolymorpheusComponent(MyComponent, injector), { label: 'Title' })
|
||||
```
|
||||
|
||||
### Toggle
|
||||
```html
|
||||
<input tuiSwitch type="checkbox" size="m" [showIcons]="false" [(ngModel)]="value" />
|
||||
```
|
||||
|
||||
### Errors & Tooltips
|
||||
```html
|
||||
<tui-error [error]="[] | tuiFieldError | async" />
|
||||
<tui-icon [tuiTooltip]="'Hint text'" />
|
||||
```
|
||||
|
||||
### Layout
|
||||
```html
|
||||
<tui-elastic-container><!-- dynamic height --></tui-elastic-container>
|
||||
<tui-scrollbar><!-- scrollable content --></tui-scrollbar>
|
||||
<tui-loader [textContent]="'Loading...' | i18n" />
|
||||
```
|
||||
115
web/CONTRIBUTING.md
Normal file
115
web/CONTRIBUTING.md
Normal file
@@ -0,0 +1,115 @@
|
||||
# Contributing to StartOS Web
|
||||
|
||||
For general environment setup (Node.js, cloning, etc.), see the root [CONTRIBUTING.md](../CONTRIBUTING.md).
|
||||
|
||||
## Web Setup
|
||||
|
||||
```sh
|
||||
cd web
|
||||
npm ci
|
||||
npm run build:deps
|
||||
```
|
||||
|
||||
#### Configure `config.json`
|
||||
|
||||
```sh
|
||||
cp config-sample.json config.json
|
||||
```
|
||||
|
||||
- By default, "useMocks" is set to `true`.
|
||||
- Use "maskAs" to mock the host from which the web UI is served. Valid values are `tor`, `local`, `localhost`, `ipv4`, `ipv6`, and `clearnet`.
|
||||
- Use "maskAsHttps" to mock the protocol over which the web UI is served. `true` means https; `false` means http.
|
||||
|
||||
## Development Server
|
||||
|
||||
You can develop using mocks (recommended to start) or against a live server. Code changes will live reload the browser.
|
||||
|
||||
### Using mocks
|
||||
|
||||
```sh
|
||||
npm run start:setup
|
||||
npm run start:ui
|
||||
```
|
||||
|
||||
### Proxying to a live server
|
||||
|
||||
1. In `config.json`, set "useMocks" to `false`
|
||||
|
||||
2. Copy and configure the proxy config:
|
||||
|
||||
```sh
|
||||
cp proxy.conf-sample.json proxy.conf.json
|
||||
```
|
||||
|
||||
3. Replace every instance of `<CHANGEME>` with the hostname of your remote server
|
||||
|
||||
4. Start the proxy dev server:
|
||||
|
||||
```sh
|
||||
npm run start:ui:proxy
|
||||
```
|
||||
|
||||
## Translations
|
||||
|
||||
### Currently supported languages
|
||||
|
||||
- English
|
||||
- Spanish
|
||||
- Polish
|
||||
- German
|
||||
- French
|
||||
<!-- - Korean
|
||||
- Russian
|
||||
- Japanese
|
||||
- Hebrew
|
||||
- Arabic
|
||||
- Mandarin
|
||||
- Hindi
|
||||
- Portuguese
|
||||
- Italian
|
||||
- Thai -->
|
||||
|
||||
### Adding a new translation
|
||||
|
||||
When prompting AI to translate the English dictionary, it is recommended to only give it 50-100 entries at a time. Beyond that it struggles. Remember to sanity check the results and ensure keys/values align in the resulting dictionary.
|
||||
|
||||
#### Sample AI prompt
|
||||
|
||||
Translate the English dictionary below into `<language>`. Format the result as a javascript object with the numeric values of the English dictionary as keys in the translated dictionary. These translations are for the web UI of StartOS, a graphical server operating system optimized for self-hosting. Comments may be included in the English dictionary to provide additional context.
|
||||
|
||||
#### Adding to StartOS
|
||||
|
||||
- In the `shared` project:
|
||||
1. Create a new file (`language.ts`) in `src/i18n/dictionaries`
|
||||
2. Update the `I18N_PROVIDERS` array in `src/i18n/i18n.providers.ts` (2 places)
|
||||
3. Update the `languages` array in `/src/i18n/i18n.service.ts`
|
||||
4. Add the name of the new language (lowercase) to the English dictionary in `src/i18n/dictionaries/en.ts`. Add the translations of the new language's name (lowercase) to ALL non-English dictionaries in `src/i18n/dictionaries/` (e.g., `es.ts`, `pl.ts`, etc.).
|
||||
|
||||
If you have any doubt about the above steps, check the [French example PR](https://github.com/Start9Labs/start-os/pull/2945/files) for reference.
|
||||
|
||||
- Here in this CONTRIBUTING.md:
|
||||
1. Add the language to the list of supported languages above
|
||||
|
||||
### Updating the English dictionary
|
||||
|
||||
#### Sample AI prompt
|
||||
|
||||
Translate the English dictionary below into the languages beneath the dictionary. Format the result as a javascript object with translated language as keys, mapping to a javascript object with the numeric values of the English dictionary as keys and the translations as values. These translations are for the web UI of StartOS, a graphical server operating system optimized for self-hosting. Comments may be included in the English dictionary to provide additional context.
|
||||
|
||||
English dictionary:
|
||||
|
||||
```
|
||||
'Hello': 420,
|
||||
'Goodby': 421
|
||||
```
|
||||
|
||||
Languages:
|
||||
|
||||
- Spanish
|
||||
- Polish
|
||||
- German
|
||||
- French
|
||||
|
||||
#### Adding to StartOS
|
||||
|
||||
In the `shared` project, copy/paste the translations into their corresponding dictionaries in `/src/i18n/dictionaries`.
|
||||
157
web/README.md
157
web/README.md
@@ -1,155 +1,20 @@
|
||||
# StartOS Web
|
||||
|
||||
StartOS web UIs are written in [Angular/Typescript](https://angular.io/docs) and leverage the [Ionic Framework](https://ionicframework.com/) component library.
|
||||
[Angular](https://angular.dev/) + TypeScript workspace using the [Taiga UI](https://taiga-ui.dev/) component library.
|
||||
|
||||
StartOS conditionally serves one of three Web UIs, depending on the state of the system and user choice.
|
||||
## Applications
|
||||
|
||||
- **setup-wizard** - UI for setting up StartOS, served on start.local.
|
||||
- **ui** - primary UI for administering StartOS, served on various hosts unique to the instance.
|
||||
StartOS serves one of these UIs depending on the state of the system:
|
||||
|
||||
Additionally, there are two libraries for shared code:
|
||||
- **ui** — Primary admin interface for managing StartOS, served on hosts unique to the instance.
|
||||
- **setup-wizard** — Initial setup UI, served on `start.local`.
|
||||
- **start-tunnel** — VPN/tunnel management UI.
|
||||
|
||||
- **marketplace** - library code shared between the StartOS UI and Start9's [brochure marketplace](https://github.com/Start9Labs/brochure-marketplace).
|
||||
- **shared** - library code shared between the various web UIs and marketplace lib.
|
||||
## Libraries
|
||||
|
||||
## Environment Setup
|
||||
- **shared** — Common code shared between all web UIs (API clients, components, i18n).
|
||||
- **marketplace** — Library code for service discovery, shared between the StartOS UI and the marketplace.
|
||||
|
||||
#### Install NodeJS and NPM
|
||||
## Contributing
|
||||
|
||||
- [Install nodejs](https://nodejs.org/en/)
|
||||
- [Install npm](https://www.npmjs.com/get-npm)
|
||||
|
||||
#### Check that your versions match the ones below
|
||||
|
||||
```sh
|
||||
node --version
|
||||
v22.15.0
|
||||
|
||||
npm --version
|
||||
v11.3.0
|
||||
```
|
||||
|
||||
#### Install and enable the Prettier extension for your text editor
|
||||
|
||||
#### Clone StartOS and load submodules
|
||||
|
||||
```sh
|
||||
git clone https://github.com/Start9Labs/start-os.git
|
||||
cd start-os
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
|
||||
#### Move to web directory and install dependencies
|
||||
|
||||
```sh
|
||||
cd web
|
||||
npm ci
|
||||
npm run build:deps
|
||||
```
|
||||
|
||||
> Note if you are on **Windows** you need to install `make` for these scripts to work. Easiest way to do so is to install [Chocolatey](https://chocolatey.org/install) and then run `choco install make`.
|
||||
|
||||
#### Copy `config-sample.json` to a new file `config.json`.
|
||||
|
||||
```sh
|
||||
cp config-sample.json config.json
|
||||
```
|
||||
|
||||
- By default, "useMocks" is set to `true`.
|
||||
- Use "maskAs" to mock the host from which the web UI is served. Valid values are `tor`, `local`, `localhost`, `ipv4`, `ipv6`, and `clearnet`.
|
||||
- Use "maskAsHttps" to mock the protocol over which the web UI is served. `true` means https; `false` means http.
|
||||
|
||||
## Running the development server
|
||||
|
||||
You can develop using mocks (recommended to start) or against a live server. Either way, any code changes will live reload the development server and refresh the browser page.
|
||||
|
||||
### Using mocks
|
||||
|
||||
#### Start the standard development server
|
||||
|
||||
```sh
|
||||
npm run start:setup
|
||||
npm run start:ui
|
||||
```
|
||||
|
||||
### Proxying to a live server
|
||||
|
||||
#### In `config.json`, set "useMocks" to `false`
|
||||
|
||||
#### Copy `proxy.conf-sample.json` to a new file `proxy.conf.json`
|
||||
|
||||
```sh
|
||||
cp proxy.conf-sample.json proxy.conf.json
|
||||
```
|
||||
|
||||
#### Replace every instance of "\<CHANGEME>\" with the hostname of your remote server
|
||||
|
||||
#### Start the proxy development server
|
||||
|
||||
```sh
|
||||
npm run start:ui:proxy
|
||||
```
|
||||
|
||||
## Translations
|
||||
|
||||
### Currently supported languages
|
||||
|
||||
- Spanish
|
||||
- Polish
|
||||
- German
|
||||
- French
|
||||
<!-- - Korean
|
||||
- Russian
|
||||
- Japanese
|
||||
- Hebrew
|
||||
- Arabic
|
||||
- Mandarin
|
||||
- Hindi
|
||||
- Portuguese
|
||||
- Italian
|
||||
- Thai -->
|
||||
|
||||
### Adding a new translation
|
||||
|
||||
When prompting AI to translate the English dictionary, it is recommended to only give it 50-100 entries at a time. Beyond that it struggles. Remember to sanity check the results and ensure keys/values align in the resulting dictionary.
|
||||
|
||||
#### Sample AI prompt
|
||||
|
||||
Translate the English dictionary below into `<language>`. Format the result as a javascript object with the numeric values of the English dictionary as keys in the translated dictionary. These translations are for the web UI of StartOS, a graphical server operating system optimized for self-hosting. Comments may be included in the English dictionary to provide additional context.
|
||||
|
||||
#### Adding to StartOS
|
||||
|
||||
- In the `shared` project:
|
||||
1. Create a new file (`language.ts`) in `src/i18n/dictionaries`
|
||||
2. Update the `I18N_PROVIDERS` array in `src/i18n/i18n.providers.ts` (2 places)
|
||||
3. Update the `languages` array in `/src/i18n/i18n.service.ts`
|
||||
4. Add the name of the new language (lowercase) to the English dictionary in `src/i18n/dictionaries/en.ts`. Add the translations of the new language’s name (lowercase) to ALL non-English dictionaries in `src/i18n/dictionaries/` (e.g., `es.ts`, `pl.ts`, etc.).
|
||||
|
||||
If you have any doubt about the above steps, check the [French example PR](https://github.com/Start9Labs/start-os/pull/2945/files) for reference.
|
||||
|
||||
- Here in this README:
|
||||
1. Add the language to the list of supported languages below
|
||||
|
||||
### Updating the English dictionary
|
||||
|
||||
#### Sample AI prompt
|
||||
|
||||
Translate the English dictionary below into the languages beneath the dictionary. Format the result as a javascript object with translated language as keys, mapping to a javascript object with the numeric values of the English dictionary as keys and the translations as values. These translations are for the web UI of StartOS, a graphical server operating system optimized for self-hosting. Comments may be included in the English dictionary to provide additional context.
|
||||
|
||||
English dictionary:
|
||||
|
||||
```
|
||||
'Hello': 420,
|
||||
'Goodby': 421
|
||||
```
|
||||
|
||||
Languages:
|
||||
|
||||
- Spanish
|
||||
- Polish
|
||||
- German
|
||||
- French
|
||||
|
||||
#### Adding to StartOS
|
||||
|
||||
In the `shared` project, copy/past the translations into their corresponding dictionaries in `/src/i18n/dictionaries`.
|
||||
See [CONTRIBUTING.md](CONTRIBUTING.md) for environment setup, development server instructions, and translation guides.
|
||||
|
||||
Reference in New Issue
Block a user