update binding for API types, add ARCHITECTURE

This commit is contained in:
Matt Hill
2026-02-16 00:05:24 -07:00
parent 3518eccc87
commit bb68c3b91c
179 changed files with 1685 additions and 1590 deletions

89
web/ARCHITECTURE.md Normal file
View File

@@ -0,0 +1,89 @@
# Web Architecture
Angular 20 + TypeScript workspace using [Taiga UI](https://taiga-ui.dev/) component library.
## 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

View File

@@ -37,98 +37,16 @@ WebFetch url=https://taiga-ui.dev/llms-full.txt prompt="How to use TuiTextfield
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
## Architecture
### 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
See [ARCHITECTURE.md](ARCHITECTURE.md) for the web architecture: API layer, PatchDB state, WebSockets, routing, forms, i18n, and services.
## 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.
- **`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.
@@ -136,6 +54,7 @@ Services often extend `Observable` and expose reactive streams via DI:
## Common Taiga Patterns
### Textfield + Select (dropdown)
```html
<tui-textfield tuiChevron>
<label tuiLabel>Label</label>
@@ -145,12 +64,15 @@ Services often extend `Observable` and expose reactive streams via DI:
</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>
@@ -158,6 +80,7 @@ providers: [tuiTextfieldOptionsProvider({ cleaner: signal(false) })]
```
### Dialogs
```typescript
// Confirmation
this.dialog.openConfirm({ label: 'Warning', data: { content: '...', yes: 'Confirm', no: 'Cancel' } })
@@ -167,17 +90,20 @@ this.dialog.openComponent(new PolymorpheusComponent(MyComponent, injector), { la
```
### 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>

21
web/package-lock.json generated
View File

@@ -867,6 +867,7 @@
"integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.27.1",
@@ -8041,6 +8042,7 @@
"integrity": "sha512-SL0JY3DaxylDuo/MecFeiC+7pedM0zia33zl0vcjgwcq1q1FWWF1To9EIauPbl8GbMCU0R2e0uJ8bZunhYKD2g==",
"devOptional": true,
"license": "MIT",
"peer": true,
"dependencies": {
"cli-truncate": "^4.0.0",
"colorette": "^2.0.20",
@@ -11910,6 +11912,7 @@
"integrity": "sha512-uzcxnSDVjAopEUjljkWh8EIrg6tlzrjFUfMcR1EVsRDGwf/ccef0qQPRyOrROwhrTDaApueq+ja+KLPlzR/zdg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.5.0",
@@ -12217,24 +12220,6 @@
"dev": true,
"license": "ISC"
},
"node_modules/yaml": {
"version": "2.8.2",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz",
"integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==",
"dev": true,
"license": "ISC",
"optional": true,
"peer": true,
"bin": {
"yaml": "bin.mjs"
},
"engines": {
"node": ">= 14.6"
},
"funding": {
"url": "https://github.com/sponsors/eemeli"
}
},
"node_modules/yargs": {
"version": "18.0.0",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz",

View File

@@ -1,7 +1,6 @@
import * as jose from 'node-jose'
import {
DiskInfo,
FollowLogsRes,
FullKeyboard,
SetLanguageParams,
StartOSDiskInfo,
@@ -49,7 +48,7 @@ export abstract class ApiService {
abstract shutdown(): Promise<void> // setup.shutdown
// Logs & Progress
abstract initFollowLogs(): Promise<FollowLogsRes> // setup.logs.follow
abstract initFollowLogs(): Promise<T.LogFollowResponse> // setup.logs.follow
abstract openWebsocket$<T>(guid: string): Observable<T>
// Restart (for error recovery)

View File

@@ -2,7 +2,6 @@ import { Inject, Injectable, DOCUMENT } from '@angular/core'
import {
DiskInfo,
encodeBase64,
FollowLogsRes,
FullKeyboard,
HttpService,
isRpcError,
@@ -124,7 +123,7 @@ export class LiveApiService extends ApiService {
}
async initFollowLogs() {
return this.rpcRequest<FollowLogsRes>({
return this.rpcRequest<T.LogFollowResponse>({
method: 'setup.logs.follow',
params: {},
})

View File

@@ -2,7 +2,6 @@ import { Injectable } from '@angular/core'
import {
DiskInfo,
encodeBase64,
FollowLogsRes,
FullKeyboard,
pauseFor,
SetLanguageParams,
@@ -166,7 +165,7 @@ export class MockApiService extends ApiService {
}
}
async initFollowLogs(): Promise<FollowLogsRes> {
async initFollowLogs(): Promise<T.LogFollowResponse> {
await pauseFor(500)
return {
startCursor: 'fakestartcursor',

View File

@@ -12,13 +12,13 @@ import {
switchMap,
timer,
} from 'rxjs'
import { FollowLogsReq, FollowLogsRes, Log } from '../types/api'
import { T } from '@start9labs/start-sdk'
import { Constructor } from '../types/constructor'
import { convertAnsi } from '../util/convert-ansi'
interface Api {
initFollowLogs: (params: FollowLogsReq) => Promise<FollowLogsRes>
openWebsocket$: (guid: string) => Observable<Log>
initFollowLogs: (params: {}) => Promise<T.LogFollowResponse>
openWebsocket$: (guid: string) => Observable<T.LogEntry>
}
export function provideSetupLogsService(

View File

@@ -1,27 +1,3 @@
export type FollowLogsReq = {}
export type FollowLogsRes = {
startCursor: string
guid: string
}
export type FetchLogsReq = {
before: boolean
cursor?: string
limit?: number
}
export type FetchLogsRes = {
entries: Log[]
startCursor?: string
endCursor?: string
}
export interface Log {
timestamp: string
message: string
bootId: string
}
export type DiskListResponse = DiskInfo[]
export interface DiskInfo {

View File

@@ -1,4 +1,4 @@
import { Log } from '../types/api'
import { T } from '@start9labs/start-sdk'
import { toLocalIsoString } from './to-local-iso-string'
import Convert from 'ansi-to-html'
@@ -8,7 +8,7 @@ const CONVERT = new Convert({
escapeXML: true,
})
export function convertAnsi(entries: readonly Log[]): string {
export function convertAnsi(entries: readonly T.LogEntry[]): string {
return entries
.map(
({ timestamp, message }) =>

View File

@@ -12,7 +12,7 @@ import { TuiCell } from '@taiga-ui/layout'
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
import { PatchDB } from 'patch-db-client'
import { map } from 'rxjs'
import { BackupReport } from 'src/app/services/api/api.types'
import { T } from '@start9labs/start-sdk'
import { getManifest } from '../utils/get-package-data'
import { DataModel } from '../services/patch-db/data-model'
@@ -60,7 +60,7 @@ export class BackupsReportModal {
readonly data =
injectContext<
TuiDialogContext<void, { content: BackupReport; createdAt: string }>
TuiDialogContext<void, { content: T.BackupReport; createdAt: string }>
>().data
readonly pkgTitles = toSignal(

View File

@@ -69,7 +69,7 @@ export default class LogsPage implements OnInit {
private readonly api = inject(ApiService)
private readonly errorService = inject(ErrorService)
startCursor?: string
startCursor?: string | null
loading = false
logs: string[] = []
scrollTop = 0
@@ -98,7 +98,7 @@ export default class LogsPage implements OnInit {
try {
const response = await this.api.diagnosticGetLogs({
cursor: this.startCursor,
cursor: this.startCursor ?? undefined,
before: !!this.startCursor,
limit: 200,
})

View File

@@ -216,17 +216,19 @@ export class InterfaceAddressesComponent {
private async savePrivateDomain(fqdn: string): Promise<boolean> {
const iface = this.value()
const gatewayId = this.gatewayGroup().gatewayId
const loader = this.loader.open('Saving').subscribe()
try {
if (this.packageId()) {
await this.api.pkgAddPrivateDomain({
fqdn,
gateway: gatewayId,
package: this.packageId(),
host: iface?.addressInfo.hostId || '',
})
} else {
await this.api.osUiAddPrivateDomain({ fqdn })
await this.api.osUiAddPrivateDomain({ fqdn, gateway: gatewayId })
}
return true
} catch (e: any) {

View File

@@ -3,10 +3,9 @@ import {
convertAnsi,
DownloadHTMLService,
ErrorService,
FetchLogsReq,
FetchLogsRes,
LoadingService,
} from '@start9labs/shared'
import { T } from '@start9labs/start-sdk'
import { LogsComponent } from './logs.component'
@Directive({
@@ -19,7 +18,7 @@ export class LogsDownloadDirective {
private readonly downloadHtml = inject(DownloadHTMLService)
@Input({ required: true })
logsDownload!: (params: FetchLogsReq) => Promise<FetchLogsRes>
logsDownload!: (params: T.LogsParams) => Promise<T.LogResponse>
@HostListener('click')
async download() {

View File

@@ -18,7 +18,7 @@ export class LogsFetchDirective {
switchMap(() =>
from(
this.component.fetchLogs({
cursor: this.component.startCursor,
cursor: this.component.startCursor ?? undefined,
before: true,
limit: 400,
}),

View File

@@ -5,11 +5,12 @@ import {
WaIntersectionObserver,
} from '@ng-web-apis/intersection-observer'
import { WaMutationObserver } from '@ng-web-apis/mutation-observer'
import { FetchLogsReq, FetchLogsRes, i18nPipe } from '@start9labs/shared'
import { i18nPipe } from '@start9labs/shared'
import { T } from '@start9labs/start-sdk'
import { TuiButton, TuiLoader, TuiScrollbar } from '@taiga-ui/core'
import { NgDompurifyPipe } from '@taiga-ui/dompurify'
import { BehaviorSubject } from 'rxjs'
import { RR } from 'src/app/services/api/api.types'
import { FollowServerLogsReq } from 'src/app/services/api/api.types'
import { LogsDownloadDirective } from './logs-download.directive'
import { LogsFetchDirective } from './logs-fetch.directive'
import { LogsPipe } from './logs.pipe'
@@ -41,17 +42,17 @@ export class LogsComponent {
private readonly scrollbar?: ElementRef<HTMLElement>
@Input({ required: true }) followLogs!: (
params: RR.FollowServerLogsReq,
) => Promise<RR.FollowServerLogsRes>
params: FollowServerLogsReq,
) => Promise<T.LogFollowResponse>
@Input({ required: true }) fetchLogs!: (
params: FetchLogsReq,
) => Promise<FetchLogsRes>
params: T.LogsParams,
) => Promise<T.LogResponse>
@Input({ required: true }) context!: string
scrollTop = 0
startCursor?: string
startCursor?: string | null
scroll = true
loading = false
previous: readonly string[] = []

View File

@@ -1,10 +1,6 @@
import { inject, Pipe, PipeTransform } from '@angular/core'
import {
convertAnsi,
i18nPipe,
Log,
toLocalIsoString,
} from '@start9labs/shared'
import { convertAnsi, i18nPipe, toLocalIsoString } from '@start9labs/shared'
import { T } from '@start9labs/start-sdk'
import {
bufferTime,
catchError,
@@ -25,7 +21,7 @@ import {
take,
tap,
} from 'rxjs'
import { RR } from 'src/app/services/api/api.types'
import { FollowServerLogsReq } from 'src/app/services/api/api.types'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { ConnectionService } from 'src/app/services/connection.service'
import { LogsComponent } from './logs.component'
@@ -40,9 +36,7 @@ export class LogsPipe implements PipeTransform {
private readonly i18n = inject(i18nPipe)
transform(
followLogs: (
params: RR.FollowServerLogsReq,
) => Promise<RR.FollowServerLogsRes>,
followLogs: (params: FollowServerLogsReq) => Promise<T.LogFollowResponse>,
): Observable<readonly string[]> {
return merge(
this.logs.status$.pipe(
@@ -53,7 +47,7 @@ export class LogsPipe implements PipeTransform {
defer(() => followLogs(this.options)).pipe(
tap(r => this.logs.setCursor(r.startCursor)),
switchMap(r =>
this.api.openWebsocket$<Log>(r.guid, {
this.api.openWebsocket$<T.LogEntry>(r.guid, {
openObserver: {
next: () => this.logs.status$.next('connected'),
},

View File

@@ -15,7 +15,7 @@ import {
} from '@taiga-ui/core'
import { TuiConfirmData, TUI_CONFIRM, TuiSkeleton } from '@taiga-ui/kit'
import { filter, map, Subject, switchMap } from 'rxjs'
import { BackupTarget } from 'src/app/services/api/api.types'
import { T } from '@start9labs/start-sdk'
import { GetBackupIconPipe } from '../pipes/get-backup-icon.pipe'
@Component({
@@ -140,7 +140,7 @@ export class BackupsTargetsComponent {
readonly delete$ = new Subject<string>()
@Input()
backupsTargets: Record<string, BackupTarget> | null = null
backupsTargets: Record<string, T.BackupTarget> | null = null
@Output()
readonly update = new EventEmitter<string>()

View File

@@ -11,7 +11,8 @@ import {
import { TuiBadge, TuiSwitch } from '@taiga-ui/kit'
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
import { from, map } from 'rxjs'
import { BackupJob, BackupTarget } from 'src/app/services/api/api.types'
import { T } from '@start9labs/start-sdk'
import { BackupJob } from 'src/app/services/api/api.types'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { ToHumanCronPipe } from '../pipes/to-human-cron.pipe'
import { BackupJobBuilder } from '../utils/job-builder'
@@ -149,7 +150,7 @@ export class BackupsEditModal {
selectTarget() {
this.dialogs
.open<BackupTarget & { id: string }>(TARGET, TARGET_CREATE)
.open<T.BackupTarget & { id: string }>(TARGET, TARGET_CREATE)
.subscribe(({ id }) => {
this.job.targetId = id
})

View File

@@ -8,7 +8,7 @@ import { TuiBlock, TuiCheckbox } from '@taiga-ui/kit'
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
import { PatchDB } from 'patch-db-client'
import { take } from 'rxjs'
import { PackageBackupInfo } from 'src/app/services/api/api.types'
import { T } from '@start9labs/start-sdk'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { DataModel } from 'src/app/services/patch-db/data-model'
import { ToOptionsPipe } from '../pipes/to-options.pipe'
@@ -98,7 +98,7 @@ export class BackupsRecoverModal {
}
}
get backups(): Record<string, PackageBackupInfo> {
get backups(): Record<string, T.PackageBackupInfo> {
return this.context.data.backupInfo.packageBackups
}
@@ -110,13 +110,12 @@ export class BackupsRecoverModal {
const ids = options.filter(({ checked }) => !!checked).map(({ id }) => id)
const loader = this.loader.open('Initializing').subscribe()
const { targetId, serverId, password } = this.context.data
const { targetId, password } = this.context.data
try {
await this.api.restorePackages({
ids,
targetId,
serverId,
password,
})

View File

@@ -6,7 +6,7 @@ import {
signal,
} from '@angular/core'
import { ErrorService, Exver } from '@start9labs/shared'
import { Version } from '@start9labs/start-sdk'
import { T, Version } from '@start9labs/start-sdk'
import {
TuiButton,
TuiDialogContext,
@@ -19,7 +19,6 @@ import {
import { TuiCell } from '@taiga-ui/layout'
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
import { PatchDB } from 'patch-db-client'
import { BackupTarget } from 'src/app/services/api/api.types'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { DataModel } from 'src/app/services/patch-db/data-model'
import { getServerInfo } from 'src/app/utils/get-server-info'
@@ -81,7 +80,7 @@ export class BackupsTargetModal {
readonly context =
injectContext<
TuiDialogContext<BackupTarget & { id: string }, { type: BackupType }>
TuiDialogContext<T.BackupTarget & { id: string }, { type: BackupType }>
>()
readonly loading = signal(true)
@@ -91,7 +90,7 @@ export class BackupsTargetModal {
: 'Loading Backup Sources'
serverId = ''
targets: Record<string, BackupTarget> = {}
targets: Record<string, T.BackupTarget> = {}
async ngOnInit() {
try {
@@ -104,14 +103,14 @@ export class BackupsTargetModal {
}
}
isDisabled(target: BackupTarget): boolean {
isDisabled(target: T.BackupTarget): boolean {
return (
!target.mountable ||
(this.context.data.type === 'restore' && !this.hasBackup(target))
)
}
hasBackup(target: BackupTarget): boolean {
hasBackup(target: T.BackupTarget): boolean {
return (
target.startOs?.[this.serverId] &&
Version.parse(target.startOs[this.serverId].version).compare(
@@ -127,7 +126,7 @@ export class BackupsTargetModal {
.subscribe()
}
select(target: BackupTarget, id: string) {
select(target: T.BackupTarget, id: string) {
this.context.completeWith({ ...target, id })
}
}

View File

@@ -3,8 +3,8 @@ import { ErrorService, LoadingService } from '@start9labs/shared'
import { TuiButton, TuiLink, TuiNotification } from '@taiga-ui/core'
import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
import { FormComponent } from 'src/app/routes/portal/components/form.component'
import { T } from '@start9labs/start-sdk'
import {
BackupTarget,
BackupTargetType,
RR,
UnknownDisk,
@@ -188,7 +188,7 @@ export class BackupsTargetsModal implements OnInit {
}
private async add(
type: BackupTargetType,
type: T.BackupTargetType,
value:
| RR.AddCifsBackupTargetReq
| RR.AddCloudBackupTargetReq
@@ -204,7 +204,7 @@ export class BackupsTargetsModal implements OnInit {
}
private async update(
type: BackupTargetType,
type: T.BackupTargetType,
value:
| RR.UpdateCifsBackupTargetReq
| RR.UpdateCloudBackupTargetReq
@@ -220,13 +220,13 @@ export class BackupsTargetsModal implements OnInit {
}
private setTargets(
saved: Record<string, BackupTarget> = this.targets()?.saved || {},
saved: Record<string, T.BackupTarget> = this.targets()?.saved || {},
unknownDisks: UnknownDisk[] = this.targets()?.unknownDisks || [],
) {
this.targets.set({ unknownDisks, saved })
}
private async getSpec(target: BackupTarget) {
private async getSpec(target: T.BackupTarget) {
switch (target.type) {
case 'cifs':
return await configBuilderToSpec(cifsSpec)

View File

@@ -1,5 +1,5 @@
import { Pipe, PipeTransform } from '@angular/core'
import { BackupTarget } from 'src/app/services/api/api.types'
import { T } from '@start9labs/start-sdk'
import { DisplayInfo } from '../types/display-info'
import { GetBackupIconPipe } from './get-backup-icon.pipe'
@@ -9,7 +9,7 @@ import { GetBackupIconPipe } from './get-backup-icon.pipe'
export class GetDisplayInfoPipe implements PipeTransform {
readonly icon = new GetBackupIconPipe()
transform(target: BackupTarget): DisplayInfo {
transform(target: T.BackupTarget): DisplayInfo {
const result = {
name: target.name,
path: `Path: ${target.path}`,

View File

@@ -1,11 +1,11 @@
import { Pipe, PipeTransform } from '@angular/core'
import { BackupReport } from 'src/app/services/api/api.types'
import { T } from '@start9labs/start-sdk'
@Pipe({
name: 'hasError',
})
export class HasErrorPipe implements PipeTransform {
transform(report: BackupReport): boolean {
transform(report: T.BackupReport): boolean {
return (
!!report.server.error ||
!!Object.values(report.packages).find(({ error }) => error)

View File

@@ -1,10 +1,9 @@
import { inject, Pipe, PipeTransform } from '@angular/core'
import { map, Observable } from 'rxjs'
import { PackageBackupInfo } from 'src/app/services/api/api.types'
import { T, Version } from '@start9labs/start-sdk'
import { ConfigService } from 'src/app/services/config.service'
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
import { RecoverOption } from '../types/recover-option'
import { Version } from '@start9labs/start-sdk'
@Pipe({
name: 'toOptions',
@@ -14,7 +13,7 @@ export class ToOptionsPipe implements PipeTransform {
transform(
packageData$: Observable<Record<string, PackageDataEntry>>,
packageBackups: Record<string, PackageBackupInfo> = {},
packageBackups: Record<string, T.PackageBackupInfo> = {},
): Observable<RecoverOption[]> {
return packageData$.pipe(
map(packageData =>

View File

@@ -2,7 +2,7 @@ import { inject, Injectable } from '@angular/core'
import { LoadingService } from '@start9labs/shared'
import { TuiDialogOptions, TuiDialogService } from '@taiga-ui/core'
import { from, switchMap } from 'rxjs'
import { BackupTarget } from 'src/app/services/api/api.types'
import { T } from '@start9labs/start-sdk'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { TARGET, TARGET_CREATE } from '../modals/target.component'
import { BACKUP, BACKUP_OPTIONS } from '../modals/backup.component'
@@ -17,7 +17,7 @@ export class BackupsCreateService {
readonly handle = () => {
this.dialogs
.open<BackupTarget & { id: string }>(TARGET, TARGET_CREATE)
.open<T.BackupTarget & { id: string }>(TARGET, TARGET_CREATE)
.pipe(
switchMap(({ id }) =>
this.dialogs

View File

@@ -22,7 +22,7 @@ import {
PROMPT,
PromptOptions,
} from 'src/app/routes/portal/modals/prompt.component'
import { BackupTarget } from 'src/app/services/api/api.types'
import { T } from '@start9labs/start-sdk'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { RECOVER } from '../modals/recover.component'
import { SERVERS } from '../modals/servers.component'
@@ -41,7 +41,7 @@ export class BackupsRestoreService {
readonly handle = () => {
this.dialogs
.open<BackupTarget & { id: string }>(TARGET, TARGET_RESTORE)
.open<T.BackupTarget & { id: string }>(TARGET, TARGET_RESTORE)
.pipe(
switchMap(target =>
this.dialogs

View File

@@ -1,8 +1,8 @@
import { BackupInfo } from 'src/app/services/api/api.types'
import { T } from '@start9labs/start-sdk'
export interface RecoverData {
targetId: string
serverId: string
backupInfo: BackupInfo
backupInfo: T.BackupInfo
password: string
}

View File

@@ -1,6 +1,6 @@
import { PackageBackupInfo } from 'src/app/services/api/api.types'
import { T } from '@start9labs/start-sdk'
export interface RecoverOption extends PackageBackupInfo {
export interface RecoverOption extends T.PackageBackupInfo {
id: string
checked: boolean
installed: boolean

View File

@@ -1,4 +1,5 @@
import { BackupJob, BackupTarget, RR } from 'src/app/services/api/api.types'
import { T } from '@start9labs/start-sdk'
import { BackupJob, RR } from 'src/app/services/api/api.types'
export class BackupJobBuilder {
name: string

View File

@@ -1,7 +1,8 @@
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
import { i18nPipe } from '@start9labs/shared'
import { T } from '@start9labs/start-sdk'
import { LogsComponent } from 'src/app/routes/portal/components/logs/logs.component'
import { RR } from 'src/app/services/api/api.types'
import { FollowServerLogsReq } from 'src/app/services/api/api.types'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { LogsHeaderComponent } from '../components/header.component'
@@ -24,9 +25,9 @@ import { LogsHeaderComponent } from '../components/header.component'
export default class SystemKernelComponent {
private readonly api = inject(ApiService)
protected readonly follow = (params: RR.FollowServerLogsReq) =>
protected readonly follow = (params: FollowServerLogsReq) =>
this.api.followKernelLogs(params)
protected readonly fetch = (params: RR.GetServerLogsReq) =>
protected readonly fetch = (params: T.LogsParams) =>
this.api.getKernelLogs(params)
}

View File

@@ -1,8 +1,9 @@
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
import { i18nPipe } from '@start9labs/shared'
import { T } from '@start9labs/start-sdk'
import { LogsComponent } from 'src/app/routes/portal/components/logs/logs.component'
import { LogsHeaderComponent } from 'src/app/routes/portal/routes/logs/components/header.component'
import { RR } from 'src/app/services/api/api.types'
import { FollowServerLogsReq } from 'src/app/services/api/api.types'
import { ApiService } from 'src/app/services/api/embassy-api.service'
@Component({
@@ -24,9 +25,9 @@ import { ApiService } from 'src/app/services/api/embassy-api.service'
export default class SystemOSComponent {
private readonly api = inject(ApiService)
protected readonly follow = (params: RR.FollowServerLogsReq) =>
protected readonly follow = (params: FollowServerLogsReq) =>
this.api.followServerLogs(params)
protected readonly fetch = (params: RR.GetServerLogsReq) =>
protected readonly fetch = (params: T.LogsParams) =>
this.api.getServerLogs(params)
}

View File

@@ -4,7 +4,7 @@ import {
computed,
input,
} from '@angular/core'
import { ServerMetrics } from 'src/app/services/api/api.types'
import { T } from '@start9labs/start-sdk'
import { DataComponent } from './data.component'
import { i18nKey } from '@start9labs/shared'
@@ -86,7 +86,7 @@ const LABELS: Record<string, i18nKey> = {
imports: [DataComponent],
})
export class CpuComponent {
readonly value = input<ServerMetrics['cpu']>()
readonly value = input<T.Metrics['cpu']>()
readonly transform = computed(
(value = this.value()?.percentageUsed?.value || '0') =>

View File

@@ -6,7 +6,7 @@ import {
} from '@angular/core'
import { TuiTitle } from '@taiga-ui/core'
import { TuiCell } from '@taiga-ui/layout'
import { ServerMetrics } from 'src/app/services/api/api.types'
import { T } from '@start9labs/start-sdk'
import { ValuePipe } from './value.pipe'
import { i18nKey, i18nPipe } from '@start9labs/shared'
@@ -43,8 +43,8 @@ import { i18nKey, i18nPipe } from '@start9labs/shared'
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [TuiCell, TuiTitle, ValuePipe, i18nPipe],
})
export class DataComponent<T extends ServerMetrics[keyof ServerMetrics]> {
readonly labels = input.required<Record<keyof T, i18nKey>>()
readonly value = input<T>()
readonly keys = computed(() => Object.keys(this.labels()) as Array<keyof T>)
export class DataComponent<M extends T.Metrics[keyof T.Metrics]> {
readonly labels = input.required<Record<keyof M, i18nKey>>()
readonly value = input<M>()
readonly keys = computed(() => Object.keys(this.labels()) as Array<keyof M>)
}

View File

@@ -5,7 +5,7 @@ import {
input,
} from '@angular/core'
import { TuiProgress } from '@taiga-ui/kit'
import { ServerMetrics } from 'src/app/services/api/api.types'
import { T } from '@start9labs/start-sdk'
import { DataComponent } from './data.component'
import { ValuePipe } from './value.pipe'
import { i18nKey } from '@start9labs/shared'
@@ -40,7 +40,7 @@ const LABELS: Record<string, i18nKey> = {
imports: [DataComponent, TuiProgress, ValuePipe],
})
export class MemoryComponent {
readonly value = input<ServerMetrics['memory']>()
readonly value = input<T.Metrics['memory']>()
readonly used = computed(
(value = this.value()?.percentageUsed.value || '0') =>

View File

@@ -13,14 +13,14 @@ import {
take,
tap,
} from 'rxjs'
import { ServerMetrics } from 'src/app/services/api/api.types'
import { T } from '@start9labs/start-sdk'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { ConnectionService } from 'src/app/services/connection.service'
@Injectable({
providedIn: 'root',
})
export class MetricsService extends Observable<ServerMetrics> {
export class MetricsService extends Observable<T.Metrics> {
private readonly connection = inject(ConnectionService)
private readonly api = inject(ApiService)
@@ -28,7 +28,7 @@ export class MetricsService extends Observable<ServerMetrics> {
this.api.followServerMetrics({}),
).pipe(
switchMap(({ guid, metrics }) =>
this.api.openWebsocket$<ServerMetrics>(guid).pipe(startWith(metrics)),
this.api.openWebsocket$<T.Metrics>(guid).pipe(startWith(metrics)),
),
catchError(() =>
this.connection.pipe(filter(Boolean), take(1), ignoreElements()),

View File

@@ -5,7 +5,7 @@ import {
input,
} from '@angular/core'
import { TuiProgress } from '@taiga-ui/kit'
import { ServerMetrics } from 'src/app/services/api/api.types'
import { T } from '@start9labs/start-sdk'
import { DataComponent } from './data.component'
import { i18nKey } from '@start9labs/shared'
@@ -41,7 +41,7 @@ const LABELS: Record<string, i18nKey> = {
imports: [TuiProgress, DataComponent],
})
export class StorageComponent {
readonly value = input<ServerMetrics['disk']>()
readonly value = input<T.Metrics['disk']>()
readonly used = computed(
(

View File

@@ -17,11 +17,8 @@ import {
import { TuiButton } from '@taiga-ui/core'
import { filter } from 'rxjs'
import { distinctUntilChanged, skip } from 'rxjs/operators'
import {
RR,
ServerNotification,
ServerNotifications,
} from 'src/app/services/api/api.types'
import { T } from '@start9labs/start-sdk'
import { ServerNotification } from 'src/app/services/api/api.types'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { BadgeService } from 'src/app/services/badge.service'
import { NotificationService } from 'src/app/services/notification.service'
@@ -66,7 +63,7 @@ export default class NotificationsComponent implements OnInit {
readonly service = inject(NotificationService)
readonly api = inject(ApiService)
readonly errorService = inject(ErrorService)
readonly notifications = signal<ServerNotifications | null>(null)
readonly notifications = signal<T.NotificationWithId[] | null>(null)
protected readonly table = viewChild<
NotificationsTableComponent<ServerNotification<number>>
@@ -92,7 +89,7 @@ export default class NotificationsComponent implements OnInit {
})
}
async getMore(params: RR.GetNotificationsReq) {
async getMore(params: T.ListNotificationParams) {
try {
this.notifications.set(null)
this.notifications.set(await this.api.getNotifications(params))
@@ -101,7 +98,7 @@ export default class NotificationsComponent implements OnInit {
}
}
async remove(all: ServerNotifications) {
async remove(all: T.NotificationWithId[]) {
const ids =
this.table()
?.selected()
@@ -119,7 +116,7 @@ export default class NotificationsComponent implements OnInit {
}
private init() {
this.getMore({}).then(() => {
this.getMore({ before: null, limit: null }).then(() => {
const latest = this.notifications()?.at(0)
if (latest) {
this.service.markSeenAll(latest.id)

View File

@@ -144,7 +144,7 @@ export class ServiceTaskComponent {
.subscribe(async () => {
const loader = this.loader.open().subscribe()
try {
await this.api.clearTask({ packageId, replayId })
await this.api.clearTask({ packageId, replayId, force: false })
} catch (e: any) {
this.errorService.handleError(e)
} finally {

View File

@@ -1,6 +1,6 @@
import { RR } from 'src/app/services/api/api.types'
import { ActionRes } from 'src/app/services/api/api.types'
type ActionResponse = NonNullable<RR.ActionRes>
type ActionResponse = NonNullable<ActionRes>
type ActionResult = NonNullable<ActionResponse['result']>
export type ActionResponseWithResult = ActionResponse & { result: ActionResult }
export type SingleResult = ActionResult & { type: 'single' }

View File

@@ -1,7 +1,8 @@
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
import { getPkgId } from '@start9labs/shared'
import { T } from '@start9labs/start-sdk'
import { LogsComponent } from 'src/app/routes/portal/components/logs/logs.component'
import { RR } from 'src/app/services/api/api.types'
import { FollowServerLogsReq } from 'src/app/services/api/api.types'
import { ApiService } from 'src/app/services/api/embassy-api.service'
@Component({
@@ -24,9 +25,9 @@ export default class ServiceLogsRoute {
readonly id = getPkgId()
readonly follow = async (params: RR.FollowServerLogsReq) =>
readonly follow = async (params: FollowServerLogsReq) =>
this.api.followPackageLogs({ id: this.id, ...params })
readonly fetch = async (params: RR.GetServerLogsReq) =>
readonly fetch = async (params: T.LogsParams) =>
this.api.getPackageLogs({ id: this.id, ...params })
}

View File

@@ -1,8 +1,7 @@
import { inject, Injectable, signal } from '@angular/core'
import { ErrorService, getErrorMessage } from '@start9labs/shared'
import { Version } from '@start9labs/start-sdk'
import { T, Version } from '@start9labs/start-sdk'
import {
BackupTarget,
CifsBackupTarget,
DiskBackupTarget,
} from 'src/app/services/api/api.types'
@@ -61,13 +60,13 @@ export class BackupService {
}
}
hasAnyBackup({ startOs }: BackupTarget): boolean {
hasAnyBackup({ startOs }: T.BackupTarget): boolean {
return Object.values(startOs).some(
s => Version.parse(s.version).compare(Version.parse('0.3.6')) !== 'less',
)
}
hasThisBackup({ startOs }: BackupTarget, id: string): boolean {
hasThisBackup({ startOs }: T.BackupTarget, id: string): boolean {
const item = startOs[id]
return (

View File

@@ -1,9 +1,8 @@
import { T } from '@start9labs/start-sdk'
import { TuiDialogContext } from '@taiga-ui/core'
import {
BackupInfo,
CifsBackupTarget,
DiskBackupTarget,
PackageBackupInfo,
} from 'src/app/services/api/api.types'
import { MappedBackupTarget } from './backup.service'
@@ -12,7 +11,7 @@ export type BackupContext = TuiDialogContext<
MappedBackupTarget<CifsBackupTarget | DiskBackupTarget>
>
export interface RecoverOption extends PackageBackupInfo {
export interface RecoverOption extends T.PackageBackupInfo {
id: string
checked: boolean
installed: boolean
@@ -22,6 +21,6 @@ export interface RecoverOption extends PackageBackupInfo {
export interface RecoverData {
targetId: string
serverId: string
backupInfo: BackupInfo
backupInfo: T.BackupInfo
password: string
}

View File

@@ -11,14 +11,14 @@ import {
i18nPipe,
LoadingService,
} from '@start9labs/shared'
import { ISB } from '@start9labs/start-sdk'
import { ISB, T } from '@start9labs/start-sdk'
import { TuiButton, TuiIcon } from '@taiga-ui/core'
import { TuiTooltip } from '@taiga-ui/kit'
import { filter } from 'rxjs'
import { FormComponent } from 'src/app/routes/portal/components/form.component'
import { PlaceholderComponent } from 'src/app/routes/portal/components/placeholder.component'
import { TableComponent } from 'src/app/routes/portal/components/table.component'
import { CifsBackupTarget, RR } from 'src/app/services/api/api.types'
import { CifsBackupTarget } from 'src/app/services/api/api.types'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { FormDialogService } from 'src/app/services/form-dialog.service'
import { configBuilderToSpec } from 'src/app/utils/configBuilderToSpec'
@@ -211,7 +211,7 @@ export class BackupNetworkComponent {
buttons: [
{
text: this.i18n.transform('Connect'),
handler: (value: RR.AddBackupTargetReq) => this.addTarget(value),
handler: (value: T.CifsAddParams) => this.addTarget(value),
},
],
},
@@ -226,7 +226,7 @@ export class BackupNetworkComponent {
buttons: [
{
text: this.i18n.transform('Connect'),
handler: async (value: RR.AddBackupTargetReq) => {
handler: async (value: T.CifsAddParams) => {
const loader = this.loader
.open('Testing connectivity to shared folder')
.subscribe()
@@ -272,7 +272,7 @@ export class BackupNetworkComponent {
})
}
private async addTarget(v: RR.AddBackupTargetReq): Promise<boolean> {
private async addTarget(v: T.CifsAddParams): Promise<boolean> {
const loader = this.loader
.open('Testing connectivity to shared folder')
.subscribe()

View File

@@ -148,8 +148,8 @@ export class BackupsRecoverComponent {
async restore(options: RecoverOption[]): Promise<void> {
const ids = options.filter(({ checked }) => !!checked).map(({ id }) => id)
const { targetId, serverId, password } = this.context.data
const params = { ids, targetId, serverId, password }
const { targetId, password } = this.context.data
const params = { ids, targetId, password }
const loader = this.loader.open('Initializing').subscribe()
try {

View File

@@ -181,7 +181,11 @@ export default class SystemEmailComponent {
`${this.i18n.transform('A test email has been sent to')} ${this.testAddress}. <i>${this.i18n.transform('Check your spam folder and mark as not spam.')}</i>` as i18nKey
try {
await this.api.testSmtp({ to: this.testAddress, ...value })
await this.api.testSmtp({
...value,
password: value.password || '',
to: this.testAddress,
})
this.dialog
.openAlert(success, { label: 'Success', size: 's' })
.subscribe()

View File

@@ -134,6 +134,7 @@ export default class GatewaysComponent {
input.config.selection === 'paste'
? input.config.value.file
: await (input.config.value.file as any as File).text(),
type: null, // @TODO Aiden why is attr here?
setAsDefaultOutbound: input.setAsDefaultOutbound,
})
return true

View File

@@ -7,9 +7,9 @@ import {
} from '@angular/core'
import { RouterLink } from '@angular/router'
import { ErrorService, i18nPipe, LoadingService } from '@start9labs/shared'
import { T } from '@start9labs/start-sdk'
import { TuiButton } from '@taiga-ui/core'
import { from, map, merge, Observable, Subject } from 'rxjs'
import { Session } from 'src/app/services/api/api.types'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { TitleDirective } from 'src/app/services/title.service'
import { SessionsTableComponent } from './table.component'
@@ -72,7 +72,7 @@ export default class SystemSessionsComponent {
readonly current$ = this.sessions$.pipe(
map(s => {
const current = s.sessions[s.current]
const current = s.current ? s.sessions[s.current] : undefined
return current ? [current] : []
}),
@@ -115,6 +115,6 @@ export default class SystemSessionsComponent {
}
}
interface SessionWithId extends Session {
interface SessionWithId extends T.Session {
id: string
}

View File

@@ -11,7 +11,7 @@ import { FormsModule } from '@angular/forms'
import { TuiIcon } from '@taiga-ui/core'
import { TuiCheckbox, TuiFade, TuiSkeleton } from '@taiga-ui/kit'
import { TableComponent } from 'src/app/routes/portal/components/table.component'
import { Session } from 'src/app/services/api/api.types'
import { T } from '@start9labs/start-sdk'
import { PlatformInfoPipe } from './platform-info.pipe'
import { i18nPipe } from '@start9labs/shared'
@@ -165,11 +165,11 @@ import { i18nPipe } from '@start9labs/shared'
i18nPipe,
],
})
export class SessionsTableComponent<T extends Session> implements OnChanges {
readonly sessions = input<readonly T[] | null>(null)
export class SessionsTableComponent<S extends T.Session> implements OnChanges {
readonly sessions = input<readonly S[] | null>(null)
readonly single = input(false)
readonly selected = signal<readonly T[]>([])
readonly selected = signal<readonly S[]>([])
readonly all = computed(
() =>
!!this.selected()?.length &&
@@ -180,7 +180,7 @@ export class SessionsTableComponent<T extends Session> implements OnChanges {
this.selected.set([])
}
onToggle(session: T) {
onToggle(session: S) {
if (this.selected().includes(session)) {
this.selected.update(selected => selected.filter(s => s !== session))
} else {

View File

@@ -13,11 +13,10 @@ import {
i18nPipe,
LoadingService,
} from '@start9labs/shared'
import { ISB } from '@start9labs/start-sdk'
import { ISB, T } from '@start9labs/start-sdk'
import { TuiButton, TuiHint } from '@taiga-ui/core'
import { filter, from, merge, Subject } from 'rxjs'
import { FormComponent } from 'src/app/routes/portal/components/form.component'
import { SSHKey } from 'src/app/services/api/api.types'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { FormDialogService } from 'src/app/services/form-dialog.service'
import { TitleDirective } from 'src/app/services/title.service'
@@ -101,13 +100,13 @@ export default class SystemSSHComponent {
private readonly i18n = inject(i18nPipe)
private readonly dialogs = inject(DialogService)
private readonly local$ = new Subject<readonly SSHKey[]>()
private readonly local$ = new Subject<readonly T.SshKeyResponse[]>()
readonly keys$ = merge(from(this.api.getSshKeys({})), this.local$)
protected tableKeys = viewChild<SSHTableComponent<SSHKey>>('table')
protected tableKeys = viewChild<SSHTableComponent<T.SshKeyResponse>>('table')
async add(all: readonly SSHKey[]) {
async add(all: readonly T.SshKeyResponse[]) {
const spec = ISB.InputSpec.of({
key: ISB.Value.text({
name: this.i18n.transform('Public Key'),
@@ -150,7 +149,7 @@ export default class SystemSSHComponent {
})
}
remove(all: readonly SSHKey[]) {
remove(all: readonly T.SshKeyResponse[]) {
this.dialogs
.openConfirm({ label: 'Are you sure?', size: 's' })
.pipe(filter(Boolean))

View File

@@ -11,7 +11,7 @@ import { FormsModule } from '@angular/forms'
import { i18nPipe } from '@start9labs/shared'
import { TuiCheckbox, TuiFade, TuiSkeleton } from '@taiga-ui/kit'
import { TableComponent } from 'src/app/routes/portal/components/table.component'
import { SSHKey } from 'src/app/services/api/api.types'
import { T } from '@start9labs/start-sdk'
@Component({
selector: '[keys]',
@@ -151,10 +151,12 @@ import { SSHKey } from 'src/app/services/api/api.types'
i18nPipe,
],
})
export class SSHTableComponent<T extends SSHKey> implements OnChanges {
readonly keys = input<readonly T[] | null>(null)
export class SSHTableComponent<
K extends T.SshKeyResponse,
> implements OnChanges {
readonly keys = input<readonly K[] | null>(null)
readonly selected = signal<readonly T[]>([])
readonly selected = signal<readonly K[]>([])
readonly all = computed(
() =>
!!this.selected()?.length &&
@@ -165,7 +167,7 @@ export class SSHTableComponent<T extends SSHKey> implements OnChanges {
this.selected.set([])
}
onToggle(key: T) {
onToggle(key: K) {
if (this.selected().includes(key)) {
this.selected.update(selected => selected.filter(s => s !== key))
} else {

View File

@@ -1,12 +1,11 @@
import { AvailableWifi } from 'src/app/services/api/api.types'
import { RR } from 'src/app/services/api/api.types'
import { T } from '@start9labs/start-sdk'
export interface WiFiForm {
ssid: string
password: string
}
export interface Wifi extends AvailableWifi {
export interface Wifi extends T.WifiListOut {
readonly connected?: boolean
}
@@ -15,7 +14,7 @@ export interface WifiData {
available: readonly Wifi[]
}
export function parseWifi(res: RR.GetWifiRes): WifiData {
export function parseWifi(res: T.WifiListInfo): WifiData {
return {
available: res.availableWifi,
known: Object.entries(res.ssids).map(([ssid, strength]) => ({

View File

@@ -148,7 +148,7 @@ export default class SystemWifiComponent {
.subscribe()
try {
await this.api.enableWifi({ enable })
await this.api.enableWifi({ enabled: enable })
} catch (e: any) {
this.errorService.handleError(e)
} finally {
@@ -187,9 +187,8 @@ export default class SystemWifiComponent {
await this.api.addWifi({
ssid,
password,
priority: 0,
connect: true,
})
await this.api.connectWifi({ ssid })
} else {
await this.api.connectWifi({ ssid })
}
@@ -263,8 +262,6 @@ export default class SystemWifiComponent {
await this.api.addWifi({
ssid,
password,
priority: 0,
connect: false,
})
wifi.known = wifi.known.concat({
ssid,

View File

@@ -64,9 +64,9 @@ export class ActionService {
try {
const res = await this.api.runAction({
packageId,
eventId,
actionId,
input: input ?? null,
eventId,
})
if (!res) return

View File

@@ -2,9 +2,8 @@ import {
InstalledState,
PackageDataEntry,
} from 'src/app/services/patch-db/data-model'
import { RR, ServerMetrics, ServerNotifications } from './api.types'
import { ActionRes } from './api.types'
import { BTC_ICON, LND_ICON, PROXY_ICON, REGISTRY_ICON } from './api-icons'
import { Log } from '@start9labs/shared'
import { configBuilderToSpec } from 'src/app/utils/configBuilderToSpec'
import { T, ISB, IST } from '@start9labs/start-sdk'
import { GetPackagesRes } from '@start9labs/marketplace'
@@ -30,7 +29,7 @@ export namespace Mock {
shuttingDown: false,
}
export const RegistryOSUpdate: RR.CheckOsUpdateRes = {
export const RegistryOSUpdate: T.OsVersionInfoMap = {
'0.4.1': {
headline: 'v0.4.1',
releaseNotes: 'Testing some release notes',
@@ -899,7 +898,7 @@ export namespace Mock {
},
}
export const Notifications: ServerNotifications = [
export const Notifications: T.NotificationWithId[] = [
{
id: 1,
packageId: null,
@@ -974,7 +973,7 @@ export namespace Mock {
},
]
export function getMetrics(): ServerMetrics {
export function getMetrics(): T.Metrics {
return {
general: {
temperature: {
@@ -1055,7 +1054,7 @@ export namespace Mock {
}
}
export const ServerLogs: Log[] = [
export const ServerLogs: T.LogEntry[] = [
{
timestamp: '2022-07-28T03:52:54.808769Z',
message: '****** START *****',
@@ -1079,7 +1078,7 @@ export namespace Mock {
},
]
export const Sessions: RR.GetSessionsRes = {
export const Sessions: T.SessionList = {
current: 'b7b1a9cef4284f00af9e9dda6e676177',
sessions: {
'9513226517c54ddd8107d6d7b9d8aed7': {
@@ -1101,7 +1100,7 @@ export namespace Mock {
},
}
export const SshKeys: RR.GetSSHKeysRes = [
export const SshKeys: T.SshKeyResponse[] = [
{
createdAt: new Date().toISOString(),
alg: 'ed25519',
@@ -1116,14 +1115,14 @@ export namespace Mock {
},
]
export const SshKey: RR.AddSSHKeyRes = {
export const SshKey: T.SshKeyResponse = {
createdAt: new Date().toISOString(),
alg: 'ed25519',
hostname: 'Lucy Key',
fingerprint: '44:44:7e:78:61:b4:bf:g2:de:24:15:96:4e:d4:15:53',
}
export const Wifi: RR.GetWifiRes = {
export const Wifi: T.WifiListInfo = {
ethernet: true,
ssids: {
Goosers: 50,
@@ -1150,7 +1149,7 @@ export namespace Mock {
],
}
export const BackupTargets: RR.GetBackupTargetsRes = {
export const BackupTargets: { [id: string]: T.BackupTarget } = {
hsbdjhasbasda: {
type: 'cifs',
hostname: 'smb://192.169.10.0',
@@ -1195,6 +1194,7 @@ export namespace Mock {
used: 100000000000,
model: null,
vendor: 'SSK',
guid: null,
startOs: {
'1234-5678-9876-5432': {
hostname: 'adjective-noun',
@@ -1338,7 +1338,7 @@ export namespace Mock {
// },
// ]
export const BackupInfo: RR.GetBackupInfoRes = {
export const BackupInfo: T.BackupInfo = {
version: '0.3.6',
timestamp: new Date().toISOString(),
packageBackups: {
@@ -1357,7 +1357,7 @@ export namespace Mock {
},
}
export const ActionResMessage: RR.ActionRes = {
export const ActionResMessage: ActionRes = {
version: '1',
title: 'New Password',
message:
@@ -1365,7 +1365,7 @@ export namespace Mock {
result: null,
}
export const ActionResSingle: RR.ActionRes = {
export const ActionResSingle: ActionRes = {
version: '1',
title: 'New Password',
message:
@@ -1379,7 +1379,7 @@ export namespace Mock {
},
}
export const ActionResGroup: RR.ActionRes = {
export const ActionResGroup: ActionRes = {
version: '1',
title: 'Properties',
message:
@@ -2180,6 +2180,7 @@ export namespace Mock {
},
publicDomains: {},
privateDomains: {},
portForwards: [],
},
bcdefgh: {
bindings: {
@@ -2203,6 +2204,7 @@ export namespace Mock {
},
publicDomains: {},
privateDomains: {},
portForwards: [],
},
cdefghi: {
bindings: {
@@ -2226,6 +2228,7 @@ export namespace Mock {
},
publicDomains: {},
privateDomains: {},
portForwards: [],
},
},
storeExposedDependents: [],

View File

@@ -1,521 +1,99 @@
import { Dump } from 'patch-db-client'
import { DataModel } from 'src/app/services/patch-db/data-model'
import {
FetchLogsReq,
FetchLogsRes,
FullKeyboard,
SetLanguageParams,
StartOSDiskInfo,
} from '@start9labs/shared'
import { IST, T } from '@start9labs/start-sdk'
import { WebSocketSubjectConfig } from 'rxjs/webSocket'
import {
GetPackageReq,
GetPackageRes,
GetPackagesReq,
GetPackagesRes,
} from '@start9labs/marketplace'
import { GetPackageReq, GetPackagesReq } from '@start9labs/marketplace'
export namespace RR {
// websocket
// websocket
export type WebsocketConfig<T> = Omit<WebSocketSubjectConfig<T>, 'url'>
export type WebsocketConfig<U> = Omit<WebSocketSubjectConfig<U>, 'url'>
// state
// state
export type EchoReq = { message: string } // server.echo
export type EchoRes = string
export type ServerState = 'initializing' | 'error' | 'running'
export type ServerState = 'initializing' | 'error' | 'running'
// diagnostic
// DB
export type SubscribePatchReq = {}
export type SubscribePatchRes = {
dump: Dump<DataModel>
guid: string
}
export type SetDBValueReq<T> = { pointer: string; value: T } // db.put.ui
export type SetDBValueRes = null
// auth
export type LoginReq = {
password: string
ephemeral?: boolean
} // auth.login - unauthed
export type loginRes = null
export type LogoutReq = {} // auth.logout
export type LogoutRes = null
export type ResetPasswordReq = {
oldPassword: string
newPassword: string
} // auth.reset-password
export type ResetPasswordRes = null
// diagnostic
export type DiagnosticErrorRes = {
code: number
message: string
data: { details: string }
}
// init
export type InitFollowProgressRes = {
progress: T.FullProgress
guid: string
}
// server
export type GetSystemTimeReq = {} // server.time
export type GetSystemTimeRes = {
now: string
uptime: number // seconds
}
export type GetServerLogsReq = FetchLogsReq // server.logs & server.kernel-logs
export type GetServerLogsRes = FetchLogsRes
export type FollowServerLogsReq = {
limit?: number // (optional) default is 50. Ignored if cursor provided
boot?: number | string | null // (optional) number is offset (0: current, -1 prev, +1 first), string is a specific boot id, null is all. Default is undefined
cursor?: string // the last known log. Websocket will return all logs since this log
} // server.logs.follow & server.kernel-logs.follow
export type FollowServerLogsRes = {
startCursor: string
guid: string
}
export type FollowServerMetricsReq = {} // server.metrics.follow
export type FollowServerMetricsRes = {
guid: string
metrics: ServerMetrics
}
export type UpdateServerReq = { registry: string; targetVersion: string } // server.update
export type UpdateServerRes = 'updating' | 'no-updates'
export type RestartServerReq = {} // server.restart
export type RestartServerRes = null
export type ShutdownServerReq = {} // server.shutdown
export type ShutdownServerRes = null
export type DiskRepairReq = {} // server.disk.repair
export type DiskRepairRes = null
export type SetDnsReq = {
servers: string[] | null
} // net.dns.set-static
export type SetDnsRes = null
export type QueryDnsReq = {
fqdn: string
} // net.dns.query
export type QueryDnsRes = string | null
export type TestPortForwardReq = {
gateway: string
port: number
} // net.port-forward.test
export type TestPortForwardRes = boolean
export type SetKeyboardReq = FullKeyboard // server.set-keyboard
export type SetKeyboardRes = null
export type SetLanguageReq = SetLanguageParams // server.set-language
export type SetLanguageRes = null
// smtp
export type SetSMTPReq = T.SmtpValue // server.set-smtp
export type SetSMTPRes = null
export type ClearSMTPReq = {} // server.clear-smtp
export type ClearSMTPRes = null
export type TestSMTPReq = SetSMTPReq & { to: string } // server.test-smtp
export type TestSMTPRes = null
// sessions
export type GetSessionsReq = {} // sessions.list
export type GetSessionsRes = {
current: string
sessions: { [hash: string]: Session }
}
export type KillSessionsReq = { ids: string[] } // sessions.kill
export type KillSessionsRes = null
// notification
export type GetNotificationsReq = {
before?: number
limit?: number
} // notification.list
export type GetNotificationsRes = ServerNotification<number>[]
export type DeleteNotificationsReq = { ids: number[] } // notification.remove
export type DeleteNotificationsRes = null
export type MarkSeenNotificationReq = DeleteNotificationsReq // notification.mark-seen
export type MarkSeenNotificationRes = null
export type MarkSeenAllNotificationsReq = { before: number } // notification.mark-seen-before
export type MarkSeenAllNotificationsRes = null
export type MarkUnseenNotificationReq = DeleteNotificationsReq // notification.mark-unseen
export type MarkUnseenNotificationRes = null
// wifi
export type GetWifiReq = {}
export type GetWifiRes = {
ssids: {
[ssid: string]: number
}
connected: string | null
country: string | null
ethernet: boolean
availableWifi: AvailableWifi[]
}
export type AddWifiReq = {
// wifi.add
ssid: string
password: string
priority: number
connect: boolean
}
export type AddWifiRes = null
export type EnabledWifiReq = { enable: boolean } // wifi.set-enabled
export type EnabledWifiRes = null
export type SetWifiCountryReq = { country: string } // wifi.country.set
export type SetWifiCountryRes = null
export type ConnectWifiReq = { ssid: string } // wifi.connect
export type ConnectWifiRes = null
export type DeleteWifiReq = { ssid: string } // wifi.remove
export type DeleteWifiRes = null
// ssh
export type GetSSHKeysReq = {} // ssh.list
export type GetSSHKeysRes = SSHKey[]
export type AddSSHKeyReq = { key: string } // ssh.add
export type AddSSHKeyRes = SSHKey
export type DeleteSSHKeyReq = { fingerprint: string } // ssh.remove
export type DeleteSSHKeyRes = null
// backup
export type GetBackupTargetsReq = {} // backup.target.list
export type GetBackupTargetsRes = { [id: string]: BackupTarget }
export type AddBackupTargetReq = {
// backup.target.cifs.add
hostname: string
path: string
username: string
password: string | null
}
export type AddBackupTargetRes = { [id: string]: CifsBackupTarget }
export type UpdateBackupTargetReq = AddBackupTargetReq & { id: string } // backup.target.cifs.update
export type UpdateBackupTargetRes = AddBackupTargetRes
export type RemoveBackupTargetReq = { id: string } // backup.target.cifs.remove
export type RemoveBackupTargetRes = null
export type GetBackupInfoReq = {
// backup.target.info
targetId: string
serverId: string
password: string
}
export type GetBackupInfoRes = BackupInfo
export type CreateBackupReq = {
// backup.create
targetId: string
packageIds: string[]
oldPassword: string | null
password: string
}
export type CreateBackupRes = null
// network
export type GatewayType = 'inbound-outbound' | 'outbound-only'
export type AddTunnelReq = {
name: string
config: string // file contents
setAsDefaultOutbound?: boolean
} // net.tunnel.add
export type AddTunnelRes = {
id: string
}
export type UpdateTunnelReq = {
id: string
name: string
} // net.gateway.set-name
export type UpdateTunnelRes = null
export type RemoveTunnelReq = { id: string } // net.tunnel.remove
export type RemoveTunnelRes = null
// Set default outbound gateway
export type SetDefaultOutboundReq = { gateway: string | null } // net.gateway.set-default-outbound
export type SetDefaultOutboundRes = null
// Set service outbound gateway
export type SetServiceOutboundReq = {
packageId: string
gateway: string | null
} // package.set-outbound-gateway
export type SetServiceOutboundRes = null
export type InitAcmeReq = {
provider: string
contact: string[]
}
export type InitAcmeRes = null
export type RemoveAcmeReq = {
provider: string
}
export type RemoveAcmeRes = null
export type ServerBindingSetAddressEnabledReq = {
// server.host.binding.set-address-enabled
internalPort: 80
address: string // JSON-serialized HostnameInfo
enabled: boolean | null // null = reset to default
}
export type ServerBindingSetAddressEnabledRes = null
export type OsUiAddPublicDomainReq = {
// server.host.address.domain.public.add
fqdn: string // FQDN
gateway: T.GatewayId
acme: string | null // URL. null means local Root CA
}
export type OsUiAddPublicDomainRes = QueryDnsRes
export type OsUiRemovePublicDomainReq = {
// server.host.address.domain.public.remove
fqdn: string // FQDN
}
export type OsUiRemovePublicDomainRes = null
export type OsUiAddPrivateDomainReq = {
// server.host.address.domain.private.add
fqdn: string // FQDN
}
export type OsUiAddPrivateDomainRes = null
export type OsUiRemovePrivateDomainReq = {
// server.host.address.domain.private.remove
fqdn: string // FQDN
}
export type OsUiRemovePrivateDomainRes = null
export type PkgBindingSetAddressEnabledReq = Omit<
ServerBindingSetAddressEnabledReq,
'internalPort'
> & {
// package.host.binding.set-address-enabled
internalPort: number
package: T.PackageId // string
host: T.HostId // string
}
export type PkgBindingSetAddressEnabledRes = null
export type PkgAddPublicDomainReq = OsUiAddPublicDomainReq & {
// package.host.address.domain.public.add
package: T.PackageId // string
host: T.HostId // string
}
export type PkgAddPublicDomainRes = OsUiAddPublicDomainRes
export type PkgRemovePublicDomainReq = OsUiRemovePublicDomainReq & {
// package.host.address.domain.public.remove
package: T.PackageId // string
host: T.HostId // string
}
export type PkgRemovePublicDomainRes = OsUiRemovePublicDomainRes
export type PkgAddPrivateDomainReq = OsUiAddPrivateDomainReq & {
// package.host.address.domain.private.add
package: T.PackageId // string
host: T.HostId // string
}
export type PkgAddPrivateDomainRes = OsUiAddPrivateDomainRes
export type PkgRemovePrivateDomainReq = PkgAddPrivateDomainReq
export type PkgRemovePrivateDomainRes = OsUiRemovePrivateDomainRes
export type GetPackageLogsReq = FetchLogsReq & { id: string } // package.logs
export type GetPackageLogsRes = FetchLogsRes
export type FollowPackageLogsReq = FollowServerLogsReq & { id: string } // package.logs.follow
export type FollowPackageLogsRes = FollowServerLogsRes
export type InstallPackageReq = T.InstallParams
export type InstallPackageRes = null
export type CancelInstallPackageReq = { id: string }
export type CancelInstallPackageRes = null
export type GetActionInputReq = { packageId: string; actionId: string } // package.action.get-input
export type GetActionInputRes = {
eventId: string
spec: IST.InputSpec
value: object | null
}
export type ActionReq = {
packageId: string
eventId: string | null
actionId: string
input: object | null
} // package.action.run
export type ActionRes = (T.ActionResult & { version: '1' }) | null
export type ClearTaskReq = {
packageId: string
replayId: string
} // package.action.clear-task
export type ClearTaskRes = null
export type RestorePackagesReq = {
// package.backup.restore
ids: string[]
targetId: string
serverId: string
password: string
}
export type RestorePackagesRes = null
export type StartPackageReq = { id: string } // package.start
export type StartPackageRes = null
export type RestartPackageReq = { id: string } // package.restart
export type RestartPackageRes = null
export type StopPackageReq = { id: string } // package.stop
export type StopPackageRes = null
export type RebuildPackageReq = { id: string } // package.rebuild
export type RebuildPackageRes = null
export type UninstallPackageReq = {
id: string
force: boolean
soft: boolean
} // package.uninstall
export type UninstallPackageRes = null
export type SideloadPackageReq = {
manifest: T.Manifest
icon: string // base64
}
export type SideloadPackageRes = {
upload: string
progress: string // guid
}
// registry
/** these are returned in ASCENDING order. the newest available version will be the LAST in the object */
export type CheckOsUpdateReq = { registry: string; serverId: string }
export type CheckOsUpdateRes = { [version: string]: T.OsVersionInfo }
export type GetRegistryInfoReq = { registry: string }
export type GetRegistryInfoRes = T.RegistryInfo
export type GetRegistryPackageReq = GetPackageReq & { registry: string }
export type GetRegistryPackageRes = GetPackageRes
export type GetRegistryPackagesReq = GetPackagesReq & { registry: string }
export type GetRegistryPackagesRes = GetPackagesRes
export type DiagnosticErrorRes = {
code: number
message: string
data: { details: string }
}
interface MetricData {
value: string
unit: string
// logs
export type FollowServerLogsReq = Omit<T.LogsParams, 'before'>
// bindings
export type ServerBindingSetAddressEnabledReq = {
// server.host.binding.set-address-enabled
internalPort: 80
address: string // JSON-serialized HostnameInfo
enabled: boolean | null // null = reset to default
}
export type ServerMetrics = {
general: {
temperature: MetricData | null
}
memory: {
total: MetricData
percentageUsed: MetricData
used: MetricData
available: MetricData
zramTotal: MetricData
zramUsed: MetricData
zramAvailable: MetricData
}
cpu: {
percentageUsed: MetricData
idle: MetricData
userSpace: MetricData
kernelSpace: MetricData
wait: MetricData
}
disk: {
capacity: MetricData
percentageUsed: MetricData
used: MetricData
available: MetricData
}
export type PkgBindingSetAddressEnabledReq = Omit<
ServerBindingSetAddressEnabledReq,
'internalPort'
> & {
// package.host.binding.set-address-enabled
internalPort: number
package: T.PackageId // string
host: T.HostId // string
}
export type Session = {
loggedIn: string
lastActive: string
userAgent: string
// package domains
export type PkgAddPublicDomainReq = T.AddPublicDomainParams & {
// package.host.address.domain.public.add
package: T.PackageId // string
host: T.HostId // string
}
export type BackupTarget = DiskBackupTarget | CifsBackupTarget
export interface DiskBackupTarget {
type: 'disk'
vendor: string | null
model: string | null
logicalname: string | null
label: string | null
capacity: number
used: number | null
startOs: Record<string, StartOSDiskInfo>
export type PkgRemovePublicDomainReq = T.RemoveDomainParams & {
// package.host.address.domain.public.remove
package: T.PackageId // string
host: T.HostId // string
}
export interface CifsBackupTarget {
type: 'cifs'
hostname: string
path: string
username: string
mountable: boolean
startOs: Record<string, StartOSDiskInfo>
export type PkgAddPrivateDomainReq = T.AddPrivateDomainParams & {
// package.host.address.domain.private.add
package: T.PackageId // string
host: T.HostId // string
}
export type PkgRemovePrivateDomainReq = T.RemoveDomainParams & {
// package.host.address.domain.private.remove
package: T.PackageId // string
host: T.HostId // string
}
// package logs
export type GetPackageLogsReq = T.LogsParams & { id: string } // package.logs
export type FollowPackageLogsReq = FollowServerLogsReq & { id: string } // package.logs.follow
// actions
export type GetActionInputRes = {
eventId: string
spec: IST.InputSpec
value: object | null
}
export type ActionRes = (T.ActionResult & { version: '1' }) | null
// registry
export type GetRegistryPackageReq = GetPackageReq & { registry: string }
export type GetRegistryPackagesReq = GetPackagesReq & { registry: string }
// backup
export type DiskBackupTarget = Extract<T.BackupTarget, { type: 'disk' }>
export type CifsBackupTarget = T.CifsBackupTarget & { type: 'cifs' }
export type RecoverySource = DiskRecoverySource | CifsRecoverySource
export interface DiskRecoverySource {
@@ -531,74 +109,28 @@ export interface CifsRecoverySource {
password: string
}
export type BackupInfo = {
version: string
timestamp: string
packageBackups: {
[id: string]: PackageBackupInfo
}
}
// notifications
export type PackageBackupInfo = {
title: string
version: string
osVersion: string
timestamp: string
}
export type ServerSpecs = {
[key: string]: string | number
}
export type SSHKey = {
createdAt: string
alg: string
hostname: string
fingerprint: string
}
export type ServerNotifications = ServerNotification<number>[]
export type ServerNotification<T extends number> = {
export type ServerNotification<N extends number> = {
id: number
packageId: string | null
createdAt: string
code: T
level: NotificationLevel
code: N
level: T.NotificationLevel
title: string
message: string
data: NotificationData<T>
data: NotificationData<N>
seen: boolean
}
export type NotificationLevel = 'success' | 'info' | 'warning' | 'error'
export type NotificationData<T> = T extends 0
export type NotificationData<N> = N extends 0
? null
: T extends 1
? BackupReport
: T extends 2
: N extends 1
? T.BackupReport
: N extends 2
? string
: any
export type BackupReport = {
server: {
attempted: boolean
error: string | null
}
packages: {
[id: string]: {
error: string | null
}
}
}
export type AvailableWifi = {
ssid: string
strength: number
security: string[]
}
declare global {
type Stringified<T> = string & {
[P in keyof T]: T[P]
@@ -614,10 +146,6 @@ declare global {
}
}
export type Encrypted = {
encrypted: string
}
// @TODO 041
// export namespace RR041 {

View File

@@ -1,5 +1,28 @@
import { RR } from './api.types'
import { FullKeyboard, SetLanguageParams } from '@start9labs/shared'
import { T } from '@start9labs/start-sdk'
import { GetPackageRes, GetPackagesRes } from '@start9labs/marketplace'
import { Dump } from 'patch-db-client'
import { WebSocketSubject } from 'rxjs/webSocket'
import { DataModel } from '../patch-db/data-model'
import {
ActionRes,
CifsBackupTarget,
DiagnosticErrorRes,
FollowPackageLogsReq,
FollowServerLogsReq,
GetActionInputRes,
GetPackageLogsReq,
GetRegistryPackageReq,
GetRegistryPackagesReq,
PkgAddPrivateDomainReq,
PkgAddPublicDomainReq,
PkgBindingSetAddressEnabledReq,
PkgRemovePrivateDomainReq,
PkgRemovePublicDomainReq,
ServerBindingSetAddressEnabledReq,
ServerState,
WebsocketConfig,
} from './api.types'
export abstract class ApiService {
// http
@@ -17,226 +40,206 @@ export abstract class ApiService {
abstract openWebsocket$<T>(
guid: string,
config?: RR.WebsocketConfig<T>,
config?: WebsocketConfig<T>,
): WebSocketSubject<T>
// state
abstract echo(params: RR.EchoReq, url: string): Promise<RR.EchoRes>
abstract echo(params: T.EchoParams, url: string): Promise<string>
abstract getState(): Promise<RR.ServerState>
abstract getState(): Promise<ServerState>
// db
abstract subscribeToPatchDB(
params: RR.SubscribePatchReq,
): Promise<RR.SubscribePatchRes>
abstract subscribeToPatchDB(params: {}): Promise<{
dump: Dump<DataModel>
guid: string
}>
abstract setDbValue<T>(
pathArr: Array<string | number>,
value: T,
): Promise<RR.SetDBValueRes>
): Promise<null>
// auth
abstract login(params: RR.LoginReq): Promise<RR.loginRes>
abstract login(params: T.LoginParams): Promise<null>
abstract logout(params: RR.LogoutReq): Promise<RR.LogoutRes>
abstract logout(params: {}): Promise<null>
abstract getSessions(params: RR.GetSessionsReq): Promise<RR.GetSessionsRes>
abstract getSessions(params: {}): Promise<T.SessionList>
abstract killSessions(params: RR.KillSessionsReq): Promise<RR.KillSessionsRes>
abstract killSessions(params: T.KillParams): Promise<null>
abstract resetPassword(
params: RR.ResetPasswordReq,
): Promise<RR.ResetPasswordRes>
abstract resetPassword(params: T.ResetPasswordParams): Promise<null>
// diagnostic
abstract diagnosticGetError(): Promise<RR.DiagnosticErrorRes>
abstract diagnosticGetError(): Promise<DiagnosticErrorRes>
abstract diagnosticRestart(): Promise<void>
abstract diagnosticForgetDrive(): Promise<void>
abstract diagnosticRepairDisk(): Promise<void>
abstract diagnosticGetLogs(
params: RR.GetServerLogsReq,
): Promise<RR.GetServerLogsRes>
abstract diagnosticGetLogs(params: T.LogsParams): Promise<T.LogResponse>
// init
abstract initFollowProgress(): Promise<RR.InitFollowProgressRes>
abstract initFollowProgress(): Promise<T.SetupProgress>
abstract initFollowLogs(
params: RR.FollowServerLogsReq,
): Promise<RR.FollowServerLogsRes>
params: FollowServerLogsReq,
): Promise<T.LogFollowResponse>
// server
abstract getSystemTime(
params: RR.GetSystemTimeReq,
): Promise<RR.GetSystemTimeRes>
abstract getSystemTime(params: {}): Promise<T.TimeInfo>
abstract getServerLogs(
params: RR.GetServerLogsReq,
): Promise<RR.GetServerLogsRes>
abstract getServerLogs(params: T.LogsParams): Promise<T.LogResponse>
abstract getKernelLogs(
params: RR.GetServerLogsReq,
): Promise<RR.GetServerLogsRes>
abstract getKernelLogs(params: T.LogsParams): Promise<T.LogResponse>
abstract followServerLogs(
params: RR.FollowServerLogsReq,
): Promise<RR.FollowServerLogsRes>
params: FollowServerLogsReq,
): Promise<T.LogFollowResponse>
abstract followKernelLogs(
params: RR.FollowServerLogsReq,
): Promise<RR.FollowServerLogsRes>
params: FollowServerLogsReq,
): Promise<T.LogFollowResponse>
abstract followServerMetrics(
params: RR.FollowServerMetricsReq,
): Promise<RR.FollowServerMetricsRes>
abstract followServerMetrics(params: {}): Promise<T.MetricsFollowResponse>
abstract updateServer(params: RR.UpdateServerReq): Promise<RR.UpdateServerRes>
abstract updateServer(params: {
registry: string
targetVersion: string
}): Promise<'updating' | 'no-updates'>
abstract restartServer(
params: RR.RestartServerReq,
): Promise<RR.RestartServerRes>
abstract restartServer(params: {}): Promise<null>
abstract shutdownServer(
params: RR.ShutdownServerReq,
): Promise<RR.ShutdownServerRes>
abstract shutdownServer(params: {}): Promise<null>
abstract repairDisk(params: RR.DiskRepairReq): Promise<RR.DiskRepairRes>
abstract repairDisk(params: {}): Promise<null>
abstract toggleKiosk(enable: boolean): Promise<null>
abstract setKeyboard(params: RR.SetKeyboardReq): Promise<RR.SetKeyboardRes>
abstract setKeyboard(params: FullKeyboard): Promise<null>
abstract setLanguage(params: RR.SetLanguageReq): Promise<RR.SetLanguageRes>
abstract setLanguage(params: SetLanguageParams): Promise<null>
abstract setDns(params: RR.SetDnsReq): Promise<RR.SetDnsRes>
abstract setDns(params: T.SetStaticDnsParams): Promise<null>
abstract queryDns(params: RR.QueryDnsReq): Promise<RR.QueryDnsRes>
abstract queryDns(params: T.QueryDnsParams): Promise<string | null>
abstract testPortForward(
params: RR.TestPortForwardReq,
): Promise<RR.TestPortForwardRes>
abstract testPortForward(params: {
gateway: string
port: number
}): Promise<boolean>
// smtp
abstract setSmtp(params: RR.SetSMTPReq): Promise<RR.SetSMTPRes>
abstract setSmtp(params: T.SmtpValue): Promise<null>
abstract clearSmtp(params: RR.ClearSMTPReq): Promise<RR.ClearSMTPRes>
abstract clearSmtp(params: {}): Promise<null>
abstract testSmtp(params: RR.TestSMTPReq): Promise<RR.TestSMTPRes>
abstract testSmtp(params: T.TestSmtpParams): Promise<null>
// marketplace URLs
abstract checkOSUpdate(
params: RR.CheckOsUpdateReq,
): Promise<RR.CheckOsUpdateRes>
abstract checkOSUpdate(params: {
registry: string
serverId: string
}): Promise<T.OsVersionInfoMap>
abstract getRegistryInfo(
params: RR.GetRegistryInfoReq,
): Promise<RR.GetRegistryInfoRes>
abstract getRegistryInfo(params: {
registry: string
}): Promise<T.RegistryInfo>
abstract getRegistryPackage(
params: RR.GetRegistryPackageReq,
): Promise<RR.GetRegistryPackageRes>
params: GetRegistryPackageReq,
): Promise<GetPackageRes>
abstract getRegistryPackages(
params: RR.GetRegistryPackagesReq,
): Promise<RR.GetRegistryPackagesRes>
params: GetRegistryPackagesReq,
): Promise<GetPackagesRes>
// notification
abstract getNotifications(
params: RR.GetNotificationsReq,
): Promise<RR.GetNotificationsRes>
params: T.ListNotificationParams,
): Promise<T.NotificationWithId[]>
abstract markSeenNotifications(
params: RR.MarkSeenNotificationReq,
): Promise<RR.MarkSeenNotificationRes>
params: T.ModifyNotificationParams,
): Promise<null>
abstract markSeenAllNotifications(
params: RR.MarkSeenAllNotificationsReq,
): Promise<RR.MarkSeenAllNotificationsRes>
params: T.ModifyNotificationBeforeParams,
): Promise<null>
abstract markUnseenNotifications(
params: RR.DeleteNotificationsReq,
): Promise<RR.DeleteNotificationsRes>
params: T.ModifyNotificationParams,
): Promise<null>
abstract deleteNotifications(
params: RR.DeleteNotificationsReq,
): Promise<RR.DeleteNotificationsRes>
params: T.ModifyNotificationParams,
): Promise<null>
// ** proxies **
abstract addTunnel(params: RR.AddTunnelReq): Promise<RR.AddTunnelRes>
abstract addTunnel(params: T.AddTunnelParams): Promise<{ id: string }>
abstract updateTunnel(params: RR.UpdateTunnelReq): Promise<RR.UpdateTunnelRes>
abstract updateTunnel(params: T.RenameGatewayParams): Promise<null>
abstract removeTunnel(params: RR.RemoveTunnelReq): Promise<RR.RemoveTunnelRes>
abstract removeTunnel(params: T.RemoveTunnelParams): Promise<null>
abstract setDefaultOutbound(
params: RR.SetDefaultOutboundReq,
): Promise<RR.SetDefaultOutboundRes>
abstract setDefaultOutbound(params: { gateway: string | null }): Promise<null>
abstract setServiceOutbound(
params: RR.SetServiceOutboundReq,
): Promise<RR.SetServiceOutboundRes>
abstract setServiceOutbound(params: {
packageId: string
gateway: string | null
}): Promise<null>
// ** domains **
// wifi
abstract enableWifi(params: RR.EnabledWifiReq): Promise<RR.EnabledWifiRes>
abstract enableWifi(params: T.SetWifiEnabledParams): Promise<null>
abstract setWifiCountry(
params: RR.SetWifiCountryReq,
): Promise<RR.SetWifiCountryRes>
abstract setWifiCountry(params: T.SetCountryParams): Promise<null>
abstract getWifi(
params: RR.GetWifiReq,
timeout: number,
): Promise<RR.GetWifiRes>
abstract getWifi(params: {}, timeout: number): Promise<T.WifiListInfo>
abstract addWifi(params: RR.AddWifiReq): Promise<RR.AddWifiRes>
abstract addWifi(params: T.WifiAddParams): Promise<null>
abstract connectWifi(params: RR.ConnectWifiReq): Promise<RR.ConnectWifiRes>
abstract connectWifi(params: T.WifiSsidParams): Promise<null>
abstract deleteWifi(params: RR.DeleteWifiReq): Promise<RR.DeleteWifiRes>
abstract deleteWifi(params: T.WifiSsidParams): Promise<null>
// ssh
abstract getSshKeys(params: RR.GetSSHKeysReq): Promise<RR.GetSSHKeysRes>
abstract getSshKeys(params: {}): Promise<T.SshKeyResponse[]>
abstract addSshKey(params: RR.AddSSHKeyReq): Promise<RR.AddSSHKeyRes>
abstract addSshKey(params: T.SshAddParams): Promise<T.SshKeyResponse>
abstract deleteSshKey(params: RR.DeleteSSHKeyReq): Promise<RR.DeleteSSHKeyRes>
abstract deleteSshKey(params: T.SshDeleteParams): Promise<null>
// backup
abstract getBackupTargets(
params: RR.GetBackupTargetsReq,
): Promise<RR.GetBackupTargetsRes>
abstract getBackupTargets(params: {}): Promise<{
[id: string]: T.BackupTarget
}>
abstract addBackupTarget(
params: RR.AddBackupTargetReq,
): Promise<RR.AddBackupTargetRes>
params: T.CifsAddParams,
): Promise<{ [id: string]: CifsBackupTarget }>
abstract updateBackupTarget(
params: RR.UpdateBackupTargetReq,
): Promise<RR.UpdateBackupTargetRes>
params: T.CifsUpdateParams,
): Promise<{ [id: string]: CifsBackupTarget }>
abstract removeBackupTarget(
params: RR.RemoveBackupTargetReq,
): Promise<RR.RemoveBackupTargetRes>
abstract removeBackupTarget(params: T.CifsRemoveParams): Promise<null>
abstract getBackupInfo(
params: RR.GetBackupInfoReq,
): Promise<RR.GetBackupInfoRes>
abstract getBackupInfo(params: T.InfoParams): Promise<T.BackupInfo>
abstract createBackup(params: RR.CreateBackupReq): Promise<RR.CreateBackupRes>
abstract createBackup(params: T.BackupParams): Promise<null>
// @TODO 041
@@ -288,51 +291,37 @@ export abstract class ApiService {
// package
abstract getPackageLogs(
params: RR.GetPackageLogsReq,
): Promise<RR.GetPackageLogsRes>
abstract getPackageLogs(params: GetPackageLogsReq): Promise<T.LogResponse>
abstract followPackageLogs(
params: RR.FollowPackageLogsReq,
): Promise<RR.FollowPackageLogsRes>
params: FollowPackageLogsReq,
): Promise<T.LogFollowResponse>
abstract installPackage(
params: RR.InstallPackageReq,
): Promise<RR.InstallPackageRes>
abstract installPackage(params: T.InstallParams): Promise<null>
abstract cancelInstallPackage(
params: RR.CancelInstallPackageReq,
): Promise<RR.CancelInstallPackageRes>
abstract cancelInstallPackage(params: T.CancelInstallParams): Promise<null>
abstract getActionInput(
params: RR.GetActionInputReq,
): Promise<RR.GetActionInputRes>
params: T.GetActionInputParams,
): Promise<GetActionInputRes>
abstract runAction(params: RR.ActionReq): Promise<RR.ActionRes>
abstract runAction(params: T.RunActionParams): Promise<ActionRes>
abstract clearTask(params: RR.ClearTaskReq): Promise<RR.ClearTaskRes>
abstract clearTask(params: T.ClearTaskParams): Promise<null>
abstract restorePackages(
params: RR.RestorePackagesReq,
): Promise<RR.RestorePackagesRes>
abstract restorePackages(params: T.RestorePackageParams): Promise<null>
abstract startPackage(params: RR.StartPackageReq): Promise<RR.StartPackageRes>
abstract startPackage(params: T.ControlParams): Promise<null>
abstract restartPackage(
params: RR.RestartPackageReq,
): Promise<RR.RestartPackageRes>
abstract restartPackage(params: T.ControlParams): Promise<null>
abstract stopPackage(params: RR.StopPackageReq): Promise<RR.StopPackageRes>
abstract stopPackage(params: T.ControlParams): Promise<null>
abstract rebuildPackage(
params: RR.RebuildPackageReq,
): Promise<RR.RebuildPackageRes>
abstract rebuildPackage(params: T.RebuildParams): Promise<null>
abstract uninstallPackage(
params: RR.UninstallPackageReq,
): Promise<RR.UninstallPackageRes>
abstract uninstallPackage(params: T.UninstallParams): Promise<null>
abstract sideloadPackage(): Promise<RR.SideloadPackageRes>
abstract sideloadPackage(): Promise<T.SideloadResponse>
// @TODO 041
@@ -342,47 +331,39 @@ export abstract class ApiService {
// params: RR.SetServiceOutboundTunnelReq,
// ): Promise<RR.SetServiceOutboundTunnelRes>
abstract initAcme(params: RR.InitAcmeReq): Promise<RR.InitAcmeRes>
abstract initAcme(params: T.InitAcmeParams): Promise<null>
abstract removeAcme(params: RR.RemoveAcmeReq): Promise<RR.RemoveAcmeRes>
abstract removeAcme(params: T.RemoveAcmeParams): Promise<null>
abstract serverBindingSetAddressEnabled(
params: RR.ServerBindingSetAddressEnabledReq,
): Promise<RR.ServerBindingSetAddressEnabledRes>
params: ServerBindingSetAddressEnabledReq,
): Promise<null>
abstract osUiAddPublicDomain(
params: RR.OsUiAddPublicDomainReq,
): Promise<RR.OsUiAddPublicDomainRes>
params: T.AddPublicDomainParams,
): Promise<string | null>
abstract osUiRemovePublicDomain(
params: RR.OsUiRemovePublicDomainReq,
): Promise<RR.OsUiRemovePublicDomainRes>
abstract osUiRemovePublicDomain(params: T.RemoveDomainParams): Promise<null>
abstract osUiAddPrivateDomain(
params: RR.OsUiAddPrivateDomainReq,
): Promise<RR.OsUiAddPrivateDomainRes>
abstract osUiAddPrivateDomain(params: T.AddPrivateDomainParams): Promise<null>
abstract osUiRemovePrivateDomain(
params: RR.OsUiRemovePrivateDomainReq,
): Promise<RR.OsUiRemovePrivateDomainRes>
abstract osUiRemovePrivateDomain(params: T.RemoveDomainParams): Promise<null>
abstract pkgBindingSetAddressEnabled(
params: RR.PkgBindingSetAddressEnabledReq,
): Promise<RR.PkgBindingSetAddressEnabledRes>
params: PkgBindingSetAddressEnabledReq,
): Promise<null>
abstract pkgAddPublicDomain(
params: RR.PkgAddPublicDomainReq,
): Promise<RR.PkgAddPublicDomainRes>
params: PkgAddPublicDomainReq,
): Promise<string | null>
abstract pkgRemovePublicDomain(
params: RR.PkgRemovePublicDomainReq,
): Promise<RR.PkgRemovePublicDomainRes>
params: PkgRemovePublicDomainReq,
): Promise<null>
abstract pkgAddPrivateDomain(
params: RR.PkgAddPrivateDomainReq,
): Promise<RR.PkgAddPrivateDomainRes>
abstract pkgAddPrivateDomain(params: PkgAddPrivateDomainReq): Promise<null>
abstract pkgRemovePrivateDomain(
params: RR.PkgRemovePrivateDomainReq,
): Promise<RR.PkgRemovePrivateDomainRes>
params: PkgRemovePrivateDomainReq,
): Promise<null>
}

View File

@@ -1,19 +1,41 @@
import { DOCUMENT, Inject, Injectable } from '@angular/core'
import { blake3 } from '@noble/hashes/blake3'
import {
FullKeyboard,
HttpOptions,
HttpService,
isRpcError,
RpcError,
RPCOptions,
SetLanguageParams,
} from '@start9labs/shared'
import { T } from '@start9labs/start-sdk'
import { GetPackageRes, GetPackagesRes } from '@start9labs/marketplace'
import { Dump, pathFromArray } from 'patch-db-client'
import { filter, firstValueFrom, Observable } from 'rxjs'
import { webSocket, WebSocketSubject } from 'rxjs/webSocket'
import { PATCH_CACHE } from 'src/app/services/patch-db/patch-db-source'
import { AuthService } from '../auth.service'
import { DataModel } from '../patch-db/data-model'
import { RR } from './api.types'
import {
ActionRes,
CifsBackupTarget,
DiagnosticErrorRes,
FollowPackageLogsReq,
FollowServerLogsReq,
GetActionInputRes,
GetPackageLogsReq,
GetRegistryPackageReq,
GetRegistryPackagesReq,
PkgAddPrivateDomainReq,
PkgAddPublicDomainReq,
PkgBindingSetAddressEnabledReq,
PkgRemovePrivateDomainReq,
PkgRemovePublicDomainReq,
ServerBindingSetAddressEnabledReq,
ServerState,
WebsocketConfig,
} from './api.types'
import { ApiService } from './embassy-api.service'
@Injectable()
@@ -65,7 +87,7 @@ export class LiveApiService extends ApiService {
openWebsocket$<T>(
guid: string,
config: RR.WebsocketConfig<T> = {},
config: WebsocketConfig<T> = {},
): WebSocketSubject<T> {
const { location } = this.document.defaultView!
const protocol = location.protocol === 'http:' ? 'ws' : 'wss'
@@ -79,59 +101,58 @@ export class LiveApiService extends ApiService {
// state
async echo(params: RR.EchoReq, url: string): Promise<RR.EchoRes> {
async echo(params: T.EchoParams, url: string): Promise<string> {
return this.rpcRequest({ method: 'echo', params }, url)
}
async getState(): Promise<RR.ServerState> {
async getState(): Promise<ServerState> {
return this.rpcRequest({ method: 'state', params: {}, timeout: 10000 })
}
// db
async subscribeToPatchDB(
params: RR.SubscribePatchReq,
): Promise<RR.SubscribePatchRes> {
async subscribeToPatchDB(params: {}): Promise<{
dump: Dump<DataModel>
guid: string
}> {
return this.rpcRequest({ method: 'db.subscribe', params })
}
async setDbValue<T>(
pathArr: Array<string | number>,
value: T,
): Promise<RR.SetDBValueRes> {
): Promise<null> {
const pointer = pathFromArray(pathArr)
const params: RR.SetDBValueReq<T> = { pointer, value }
const params = { pointer, value }
return this.rpcRequest({ method: 'db.put.ui', params })
}
// auth
async login(params: RR.LoginReq): Promise<RR.loginRes> {
async login(params: T.LoginParams): Promise<null> {
return this.rpcRequest({ method: 'auth.login', params })
}
async logout(params: RR.LogoutReq): Promise<RR.LogoutRes> {
async logout(params: {}): Promise<null> {
return this.rpcRequest({ method: 'auth.logout', params })
}
async getSessions(params: RR.GetSessionsReq): Promise<RR.GetSessionsRes> {
async getSessions(params: {}): Promise<T.SessionList> {
return this.rpcRequest({ method: 'auth.session.list', params })
}
async killSessions(params: RR.KillSessionsReq): Promise<RR.KillSessionsRes> {
async killSessions(params: T.KillParams): Promise<null> {
return this.rpcRequest({ method: 'auth.session.kill', params })
}
async resetPassword(
params: RR.ResetPasswordReq,
): Promise<RR.ResetPasswordRes> {
async resetPassword(params: T.ResetPasswordParams): Promise<null> {
return this.rpcRequest({ method: 'auth.reset-password', params })
}
// diagnostic
async diagnosticGetError(): Promise<RR.DiagnosticErrorRes> {
return this.rpcRequest<RR.DiagnosticErrorRes>({
async diagnosticGetError(): Promise<DiagnosticErrorRes> {
return this.rpcRequest<DiagnosticErrorRes>({
method: 'diagnostic.error',
params: {},
})
@@ -158,10 +179,8 @@ export class LiveApiService extends ApiService {
})
}
async diagnosticGetLogs(
params: RR.GetServerLogsReq,
): Promise<RR.GetServerLogsRes> {
return this.rpcRequest<RR.GetServerLogsRes>({
async diagnosticGetLogs(params: T.LogsParams): Promise<T.LogResponse> {
return this.rpcRequest<T.LogResponse>({
method: 'diagnostic.logs',
params,
})
@@ -169,71 +188,62 @@ export class LiveApiService extends ApiService {
// init
async initFollowProgress(): Promise<RR.InitFollowProgressRes> {
async initFollowProgress(): Promise<T.SetupProgress> {
return this.rpcRequest({ method: 'init.subscribe', params: {} })
}
async initFollowLogs(
params: RR.FollowServerLogsReq,
): Promise<RR.FollowServerLogsRes> {
params: FollowServerLogsReq,
): Promise<T.LogFollowResponse> {
return this.rpcRequest({ method: 'init.logs.follow', params })
}
// server
async getSystemTime(
params: RR.GetSystemTimeReq,
): Promise<RR.GetSystemTimeRes> {
async getSystemTime(params: {}): Promise<T.TimeInfo> {
return this.rpcRequest({ method: 'server.time', params })
}
async getServerLogs(
params: RR.GetServerLogsReq,
): Promise<RR.GetServerLogsRes> {
async getServerLogs(params: T.LogsParams): Promise<T.LogResponse> {
return this.rpcRequest({ method: 'server.logs', params })
}
async getKernelLogs(
params: RR.GetServerLogsReq,
): Promise<RR.GetServerLogsRes> {
async getKernelLogs(params: T.LogsParams): Promise<T.LogResponse> {
return this.rpcRequest({ method: 'server.kernel-logs', params })
}
async followServerLogs(
params: RR.FollowServerLogsReq,
): Promise<RR.FollowServerLogsRes> {
params: FollowServerLogsReq,
): Promise<T.LogFollowResponse> {
return this.rpcRequest({ method: 'server.logs.follow', params })
}
async followKernelLogs(
params: RR.FollowServerLogsReq,
): Promise<RR.FollowServerLogsRes> {
params: FollowServerLogsReq,
): Promise<T.LogFollowResponse> {
return this.rpcRequest({ method: 'server.kernel-logs.follow', params })
}
async followServerMetrics(
params: RR.FollowServerMetricsReq,
): Promise<RR.FollowServerMetricsRes> {
async followServerMetrics(params: {}): Promise<T.MetricsFollowResponse> {
return this.rpcRequest({ method: 'server.metrics.follow', params })
}
async updateServer(params: RR.UpdateServerReq): Promise<RR.UpdateServerRes> {
async updateServer(params: {
registry: string
targetVersion: string
}): Promise<'updating' | 'no-updates'> {
return this.rpcRequest({ method: 'server.update', params })
}
async restartServer(
params: RR.RestartServerReq,
): Promise<RR.RestartServerRes> {
async restartServer(params: {}): Promise<null> {
return this.rpcRequest({ method: 'server.restart', params })
}
async shutdownServer(
params: RR.ShutdownServerReq,
): Promise<RR.ShutdownServerRes> {
async shutdownServer(params: {}): Promise<null> {
return this.rpcRequest({ method: 'server.shutdown', params })
}
async repairDisk(params: RR.RestartServerReq): Promise<RR.RestartServerRes> {
async repairDisk(params: {}): Promise<null> {
return this.rpcRequest({ method: 'disk.repair', params })
}
@@ -244,51 +254,51 @@ export class LiveApiService extends ApiService {
})
}
async setKeyboard(params: RR.SetKeyboardReq): Promise<RR.SetKeyboardRes> {
async setKeyboard(params: FullKeyboard): Promise<null> {
return this.rpcRequest({ method: 'server.set-keyboard', params })
}
async setLanguage(params: RR.SetLanguageReq): Promise<RR.SetLanguageRes> {
async setLanguage(params: SetLanguageParams): Promise<null> {
return this.rpcRequest({ method: 'server.set-language', params })
}
async setDns(params: RR.SetDnsReq): Promise<RR.SetDnsRes> {
async setDns(params: T.SetStaticDnsParams): Promise<null> {
return this.rpcRequest({
method: 'net.dns.set-static',
params,
})
}
async queryDns(params: RR.QueryDnsReq): Promise<RR.QueryDnsRes> {
async queryDns(params: T.QueryDnsParams): Promise<string | null> {
return this.rpcRequest({
method: 'net.dns.query',
params,
})
}
async testPortForward(
params: RR.TestPortForwardReq,
): Promise<RR.TestPortForwardRes> {
async testPortForward(params: {
gateway: string
port: number
}): Promise<boolean> {
return this.rpcRequest({
method: 'net.port-forward.test',
method: 'net.gateway.check-port',
params,
})
}
// marketplace URLs
async checkOSUpdate(
params: RR.CheckOsUpdateReq,
): Promise<RR.CheckOsUpdateRes> {
async checkOSUpdate(params: {
registry: string
serverId: string
}): Promise<T.OsVersionInfoMap> {
return this.rpcRequest({
method: 'registry.os.version.get',
params,
})
}
async getRegistryInfo(
params: RR.GetRegistryInfoReq,
): Promise<RR.GetRegistryInfoRes> {
async getRegistryInfo(params: { registry: string }): Promise<T.RegistryInfo> {
return this.rpcRequest({
method: 'registry.info',
params,
@@ -296,8 +306,8 @@ export class LiveApiService extends ApiService {
}
async getRegistryPackage(
params: RR.GetRegistryPackageReq,
): Promise<RR.GetRegistryPackageRes> {
params: GetRegistryPackageReq,
): Promise<GetPackageRes> {
return this.rpcRequest({
method: 'registry.package.get',
params,
@@ -305,8 +315,8 @@ export class LiveApiService extends ApiService {
}
async getRegistryPackages(
params: RR.GetRegistryPackagesReq,
): Promise<RR.GetRegistryPackagesRes> {
params: GetRegistryPackagesReq,
): Promise<GetPackagesRes> {
return this.rpcRequest({
method: 'registry.package.get',
params,
@@ -316,26 +326,24 @@ export class LiveApiService extends ApiService {
// notification
async getNotifications(
params: RR.GetNotificationsReq,
): Promise<RR.GetNotificationsRes> {
params: T.ListNotificationParams,
): Promise<T.NotificationWithId[]> {
return this.rpcRequest({ method: 'notification.list', params })
}
async deleteNotifications(
params: RR.DeleteNotificationsReq,
): Promise<RR.DeleteNotificationsRes> {
async deleteNotifications(params: T.ModifyNotificationParams): Promise<null> {
return this.rpcRequest({ method: 'notification.remove', params })
}
async markSeenNotifications(
params: RR.MarkSeenNotificationReq,
): Promise<RR.MarkSeenNotificationRes> {
params: T.ModifyNotificationParams,
): Promise<null> {
return this.rpcRequest({ method: 'notification.mark-seen', params })
}
async markSeenAllNotifications(
params: RR.MarkSeenAllNotificationsReq,
): Promise<RR.MarkSeenAllNotificationsRes> {
params: T.ModifyNotificationBeforeParams,
): Promise<null> {
return this.rpcRequest({
method: 'notification.mark-seen-before',
params,
@@ -343,133 +351,123 @@ export class LiveApiService extends ApiService {
}
async markUnseenNotifications(
params: RR.MarkUnseenNotificationReq,
): Promise<RR.MarkUnseenNotificationRes> {
params: T.ModifyNotificationParams,
): Promise<null> {
return this.rpcRequest({ method: 'notification.mark-unseen', params })
}
// proxies
async addTunnel(params: RR.AddTunnelReq): Promise<RR.AddTunnelRes> {
async addTunnel(params: T.AddTunnelParams): Promise<{ id: string }> {
return this.rpcRequest({ method: 'net.tunnel.add', params })
}
async updateTunnel(params: RR.UpdateTunnelReq): Promise<RR.UpdateTunnelRes> {
async updateTunnel(params: T.RenameGatewayParams): Promise<null> {
return this.rpcRequest({ method: 'net.gateway.set-name', params })
}
async removeTunnel(params: RR.RemoveTunnelReq): Promise<RR.RemoveTunnelRes> {
async removeTunnel(params: T.RemoveTunnelParams): Promise<null> {
return this.rpcRequest({ method: 'net.tunnel.remove', params })
}
async setDefaultOutbound(
params: RR.SetDefaultOutboundReq,
): Promise<RR.SetDefaultOutboundRes> {
async setDefaultOutbound(params: { gateway: string | null }): Promise<null> {
return this.rpcRequest({
method: 'net.gateway.set-default-outbound',
params,
})
}
async setServiceOutbound(
params: RR.SetServiceOutboundReq,
): Promise<RR.SetServiceOutboundRes> {
async setServiceOutbound(params: {
packageId: string
gateway: string | null
}): Promise<null> {
return this.rpcRequest({ method: 'package.set-outbound-gateway', params })
}
// wifi
async enableWifi(params: RR.EnabledWifiReq): Promise<RR.EnabledWifiRes> {
async enableWifi(params: T.SetWifiEnabledParams): Promise<null> {
return this.rpcRequest({ method: 'wifi.enable', params })
}
async getWifi(
params: RR.GetWifiReq,
timeout?: number,
): Promise<RR.GetWifiRes> {
async getWifi(params: {}, timeout?: number): Promise<T.WifiListInfo> {
return this.rpcRequest({ method: 'wifi.get', params, timeout })
}
async setWifiCountry(
params: RR.SetWifiCountryReq,
): Promise<RR.SetWifiCountryRes> {
async setWifiCountry(params: T.SetCountryParams): Promise<null> {
return this.rpcRequest({ method: 'wifi.country.set', params })
}
async addWifi(params: RR.AddWifiReq): Promise<RR.AddWifiRes> {
async addWifi(params: T.WifiAddParams): Promise<null> {
return this.rpcRequest({ method: 'wifi.add', params })
}
async connectWifi(params: RR.ConnectWifiReq): Promise<RR.ConnectWifiRes> {
async connectWifi(params: T.WifiSsidParams): Promise<null> {
return this.rpcRequest({ method: 'wifi.connect', params })
}
async deleteWifi(params: RR.DeleteWifiReq): Promise<RR.DeleteWifiRes> {
async deleteWifi(params: T.WifiSsidParams): Promise<null> {
return this.rpcRequest({ method: 'wifi.remove', params })
}
// smtp
async setSmtp(params: RR.SetSMTPReq): Promise<RR.SetSMTPRes> {
async setSmtp(params: T.SmtpValue): Promise<null> {
return this.rpcRequest({ method: 'server.set-smtp', params })
}
async clearSmtp(params: RR.ClearSMTPReq): Promise<RR.ClearSMTPRes> {
async clearSmtp(params: {}): Promise<null> {
return this.rpcRequest({ method: 'server.clear-smtp', params })
}
async testSmtp(params: RR.TestSMTPReq): Promise<RR.TestSMTPRes> {
async testSmtp(params: T.TestSmtpParams): Promise<null> {
return this.rpcRequest({ method: 'server.test-smtp', params })
}
// ssh
async getSshKeys(params: RR.GetSSHKeysReq): Promise<RR.GetSSHKeysRes> {
async getSshKeys(params: {}): Promise<T.SshKeyResponse[]> {
return this.rpcRequest({ method: 'ssh.list', params })
}
async addSshKey(params: RR.AddSSHKeyReq): Promise<RR.AddSSHKeyRes> {
async addSshKey(params: T.SshAddParams): Promise<T.SshKeyResponse> {
return this.rpcRequest({ method: 'ssh.add', params })
}
async deleteSshKey(params: RR.DeleteSSHKeyReq): Promise<RR.DeleteSSHKeyRes> {
async deleteSshKey(params: T.SshDeleteParams): Promise<null> {
return this.rpcRequest({ method: 'ssh.remove', params })
}
// backup
async getBackupTargets(
params: RR.GetBackupTargetsReq,
): Promise<RR.GetBackupTargetsRes> {
async getBackupTargets(params: {}): Promise<{
[id: string]: T.BackupTarget
}> {
return this.rpcRequest({ method: 'backup.target.list', params })
}
async addBackupTarget(
params: RR.AddBackupTargetReq,
): Promise<RR.AddBackupTargetRes> {
params: T.CifsAddParams,
): Promise<{ [id: string]: CifsBackupTarget }> {
params.path = params.path.replace('/\\/g', '/')
return this.rpcRequest({ method: 'backup.target.cifs.add', params })
}
async updateBackupTarget(
params: RR.UpdateBackupTargetReq,
): Promise<RR.UpdateBackupTargetRes> {
params: T.CifsUpdateParams,
): Promise<{ [id: string]: CifsBackupTarget }> {
return this.rpcRequest({ method: 'backup.target.cifs.update', params })
}
async removeBackupTarget(
params: RR.RemoveBackupTargetReq,
): Promise<RR.RemoveBackupTargetRes> {
async removeBackupTarget(params: T.CifsRemoveParams): Promise<null> {
return this.rpcRequest({ method: 'backup.target.cifs.remove', params })
}
async getBackupInfo(
params: RR.GetBackupInfoReq,
): Promise<RR.GetBackupInfoRes> {
async getBackupInfo(params: T.InfoParams): Promise<T.BackupInfo> {
return this.rpcRequest({ method: 'backup.target.info', params })
}
async createBackup(params: RR.CreateBackupReq): Promise<RR.CreateBackupRes> {
async createBackup(params: T.BackupParams): Promise<null> {
return this.rpcRequest({ method: 'backup.create', params })
}
@@ -532,77 +530,63 @@ export class LiveApiService extends ApiService {
// package
async getPackageLogs(
params: RR.GetPackageLogsReq,
): Promise<RR.GetPackageLogsRes> {
async getPackageLogs(params: GetPackageLogsReq): Promise<T.LogResponse> {
return this.rpcRequest({ method: 'package.logs', params })
}
async followPackageLogs(
params: RR.FollowPackageLogsReq,
): Promise<RR.FollowPackageLogsRes> {
params: FollowPackageLogsReq,
): Promise<T.LogFollowResponse> {
return this.rpcRequest({ method: 'package.logs.follow', params })
}
async installPackage(
params: RR.InstallPackageReq,
): Promise<RR.InstallPackageRes> {
async installPackage(params: T.InstallParams): Promise<null> {
return this.rpcRequest({ method: 'package.install', params })
}
async cancelInstallPackage(
params: RR.CancelInstallPackageReq,
): Promise<RR.CancelInstallPackageRes> {
async cancelInstallPackage(params: T.CancelInstallParams): Promise<null> {
return this.rpcRequest({ method: 'package.cancel-install', params })
}
async getActionInput(
params: RR.GetActionInputReq,
): Promise<RR.GetActionInputRes> {
params: T.GetActionInputParams,
): Promise<GetActionInputRes> {
return this.rpcRequest({ method: 'package.action.get-input', params })
}
async runAction(params: RR.ActionReq): Promise<RR.ActionRes> {
async runAction(params: T.RunActionParams): Promise<ActionRes> {
return this.rpcRequest({ method: 'package.action.run', params })
}
async clearTask(params: RR.ClearTaskReq): Promise<RR.ClearTaskRes> {
async clearTask(params: T.ClearTaskParams): Promise<null> {
return this.rpcRequest({ method: 'package.action.clear-task', params })
}
async restorePackages(
params: RR.RestorePackagesReq,
): Promise<RR.RestorePackagesRes> {
async restorePackages(params: T.RestorePackageParams): Promise<null> {
return this.rpcRequest({ method: 'package.backup.restore', params })
}
async startPackage(params: RR.StartPackageReq): Promise<RR.StartPackageRes> {
async startPackage(params: T.ControlParams): Promise<null> {
return this.rpcRequest({ method: 'package.start', params })
}
async restartPackage(
params: RR.RestartPackageReq,
): Promise<RR.RestartPackageRes> {
async restartPackage(params: T.ControlParams): Promise<null> {
return this.rpcRequest({ method: 'package.restart', params })
}
async stopPackage(params: RR.StopPackageReq): Promise<RR.StopPackageRes> {
async stopPackage(params: T.ControlParams): Promise<null> {
return this.rpcRequest({ method: 'package.stop', params })
}
async rebuildPackage(
params: RR.RebuildPackageReq,
): Promise<RR.RebuildPackageRes> {
async rebuildPackage(params: T.RebuildParams): Promise<null> {
return this.rpcRequest({ method: 'package.rebuild', params })
}
async uninstallPackage(
params: RR.UninstallPackageReq,
): Promise<RR.UninstallPackageRes> {
async uninstallPackage(params: T.UninstallParams): Promise<null> {
return this.rpcRequest({ method: 'package.uninstall', params })
}
async sideloadPackage(): Promise<RR.SideloadPackageRes> {
async sideloadPackage(): Promise<T.SideloadResponse> {
return this.rpcRequest({
method: 'package.sideload',
params: {},
@@ -615,14 +599,14 @@ export class LiveApiService extends ApiService {
// return this.rpcRequest({ method: 'package.proxy.set-outbound', params })
// }
async removeAcme(params: RR.RemoveAcmeReq): Promise<RR.RemoveAcmeRes> {
async removeAcme(params: T.RemoveAcmeParams): Promise<null> {
return this.rpcRequest({
method: 'net.acme.remove',
params,
})
}
async initAcme(params: RR.InitAcmeReq): Promise<RR.InitAcmeRes> {
async initAcme(params: T.InitAcmeParams): Promise<null> {
return this.rpcRequest({
method: 'net.acme.init',
params,
@@ -630,8 +614,8 @@ export class LiveApiService extends ApiService {
}
async serverBindingSetAddressEnabled(
params: RR.ServerBindingSetAddressEnabledReq,
): Promise<RR.ServerBindingSetAddressEnabledRes> {
params: ServerBindingSetAddressEnabledReq,
): Promise<null> {
return this.rpcRequest({
method: 'server.host.binding.set-address-enabled',
params,
@@ -639,35 +623,29 @@ export class LiveApiService extends ApiService {
}
async osUiAddPublicDomain(
params: RR.OsUiAddPublicDomainReq,
): Promise<RR.OsUiAddPublicDomainRes> {
params: T.AddPublicDomainParams,
): Promise<string | null> {
return this.rpcRequest({
method: 'server.host.address.domain.public.add',
params,
})
}
async osUiRemovePublicDomain(
params: RR.OsUiRemovePublicDomainReq,
): Promise<RR.OsUiRemovePublicDomainRes> {
async osUiRemovePublicDomain(params: T.RemoveDomainParams): Promise<null> {
return this.rpcRequest({
method: 'server.host.address.domain.public.remove',
params,
})
}
async osUiAddPrivateDomain(
params: RR.OsUiAddPrivateDomainReq,
): Promise<RR.OsUiAddPrivateDomainRes> {
async osUiAddPrivateDomain(params: T.AddPrivateDomainParams): Promise<null> {
return this.rpcRequest({
method: 'server.host.address.domain.private.add',
params,
})
}
async osUiRemovePrivateDomain(
params: RR.OsUiRemovePrivateDomainReq,
): Promise<RR.OsUiRemovePrivateDomainRes> {
async osUiRemovePrivateDomain(params: T.RemoveDomainParams): Promise<null> {
return this.rpcRequest({
method: 'server.host.address.domain.private.remove',
params,
@@ -675,8 +653,8 @@ export class LiveApiService extends ApiService {
}
async pkgBindingSetAddressEnabled(
params: RR.PkgBindingSetAddressEnabledReq,
): Promise<RR.PkgBindingSetAddressEnabledRes> {
params: PkgBindingSetAddressEnabledReq,
): Promise<null> {
return this.rpcRequest({
method: 'package.host.binding.set-address-enabled',
params,
@@ -684,26 +662,22 @@ export class LiveApiService extends ApiService {
}
async pkgAddPublicDomain(
params: RR.PkgAddPublicDomainReq,
): Promise<RR.PkgAddPublicDomainRes> {
params: PkgAddPublicDomainReq,
): Promise<string | null> {
return this.rpcRequest({
method: 'package.host.address.domain.public.add',
params,
})
}
async pkgRemovePublicDomain(
params: RR.PkgRemovePublicDomainReq,
): Promise<RR.PkgRemovePublicDomainRes> {
async pkgRemovePublicDomain(params: PkgRemovePublicDomainReq): Promise<null> {
return this.rpcRequest({
method: 'package.host.address.domain.public.remove',
params,
})
}
async pkgAddPrivateDomain(
params: RR.PkgAddPrivateDomainReq,
): Promise<RR.PkgAddPrivateDomainRes> {
async pkgAddPrivateDomain(params: PkgAddPrivateDomainReq): Promise<null> {
return this.rpcRequest({
method: 'package.host.address.domain.private.add',
params,
@@ -711,8 +685,8 @@ export class LiveApiService extends ApiService {
}
async pkgRemovePrivateDomain(
params: RR.PkgRemovePrivateDomainReq,
): Promise<RR.PkgRemovePrivateDomainRes> {
params: PkgRemovePrivateDomainReq,
): Promise<null> {
return this.rpcRequest({
method: 'package.host.address.domain.private.remove',
params,

View File

@@ -1,8 +1,14 @@
import { Injectable } from '@angular/core'
import { pauseFor, Log, RPCErrorDetails } from '@start9labs/shared'
import {
FullKeyboard,
pauseFor,
RPCErrorDetails,
SetLanguageParams,
} from '@start9labs/shared'
import { ApiService } from './embassy-api.service'
import {
AddOperation,
Dump,
Operation,
PatchOp,
pathFromArray,
@@ -11,12 +17,32 @@ import {
Revision,
} from 'patch-db-client'
import {
DataModel,
InstallingState,
PackageDataEntry,
StateInfo,
UpdatingState,
} from 'src/app/services/patch-db/data-model'
import { CifsBackupTarget, RR } from './api.types'
import { GetPackageRes, GetPackagesRes } from '@start9labs/marketplace'
import {
ActionRes,
CifsBackupTarget,
DiagnosticErrorRes,
FollowPackageLogsReq,
FollowServerLogsReq,
GetActionInputRes,
GetPackageLogsReq,
GetRegistryPackageReq,
GetRegistryPackagesReq,
PkgAddPrivateDomainReq,
PkgAddPublicDomainReq,
PkgBindingSetAddressEnabledReq,
PkgRemovePrivateDomainReq,
PkgRemovePublicDomainReq,
ServerBindingSetAddressEnabledReq,
ServerState,
WebsocketConfig,
} from './api.types'
import { Mock } from './api.fixures'
import { from, interval, map, shareReplay, startWith, Subject, tap } from 'rxjs'
import { mockPatchData } from './mock-patch'
@@ -86,7 +112,7 @@ export class MockApiService extends ApiService {
openWebsocket$<T>(
guid: string,
config: RR.WebsocketConfig<T> = {},
config: WebsocketConfig<T> = {},
): WebSocketSubject<T> {
if (guid === 'db-guid') {
return this.mockWsSource$.pipe<any>(
@@ -121,7 +147,7 @@ export class MockApiService extends ApiService {
// state
async echo(params: RR.EchoReq, url: string): Promise<RR.EchoRes> {
async echo(params: T.EchoParams, url: string): Promise<string> {
if (url) {
const num = Math.floor(Math.random() * 10) + 1
if (num > 8) return params.message
@@ -132,7 +158,7 @@ export class MockApiService extends ApiService {
}
private stateIndex = 0
async getState(): Promise<RR.ServerState> {
async getState(): Promise<ServerState> {
await pauseFor(1000)
this.stateIndex++
@@ -142,9 +168,10 @@ export class MockApiService extends ApiService {
// db
async subscribeToPatchDB(
params: RR.SubscribePatchReq,
): Promise<RR.SubscribePatchRes> {
async subscribeToPatchDB(params: {}): Promise<{
dump: Dump<DataModel>
guid: string
}> {
await pauseFor(2000)
return {
dump: { id: 1, value: mockPatchData },
@@ -155,9 +182,9 @@ export class MockApiService extends ApiService {
async setDbValue<T>(
pathArr: Array<string | number>,
value: T,
): Promise<RR.SetDBValueRes> {
): Promise<null> {
const pointer = pathFromArray(pathArr)
const params: RR.SetDBValueReq<T> = { pointer, value }
const params = { pointer, value }
await pauseFor(2000)
const patch = [
{
@@ -173,29 +200,27 @@ export class MockApiService extends ApiService {
// auth
async login(params: RR.LoginReq): Promise<RR.loginRes> {
async login(params: T.LoginParams): Promise<null> {
await pauseFor(2000)
return null
}
async logout(params: RR.LogoutReq): Promise<RR.LogoutRes> {
async logout(params: {}): Promise<null> {
await pauseFor(2000)
return null
}
async getSessions(params: RR.GetSessionsReq): Promise<RR.GetSessionsRes> {
async getSessions(params: {}): Promise<T.SessionList> {
await pauseFor(2000)
return Mock.Sessions
}
async killSessions(params: RR.KillSessionsReq): Promise<RR.KillSessionsRes> {
async killSessions(params: T.KillParams): Promise<null> {
await pauseFor(2000)
return null
}
async resetPassword(
params: RR.ResetPasswordReq,
): Promise<RR.ResetPasswordRes> {
async resetPassword(params: T.ResetPasswordParams): Promise<null> {
await pauseFor(2000)
return null
}
@@ -211,7 +236,7 @@ export class MockApiService extends ApiService {
}
}
async diagnosticGetError(): Promise<RR.DiagnosticErrorRes> {
async diagnosticGetError(): Promise<DiagnosticErrorRes> {
await pauseFor(1000)
return {
code: 15,
@@ -232,15 +257,13 @@ export class MockApiService extends ApiService {
await pauseFor(1000)
}
async diagnosticGetLogs(
params: RR.GetServerLogsReq,
): Promise<RR.GetServerLogsRes> {
async diagnosticGetLogs(params: T.LogsParams): Promise<T.LogResponse> {
return this.getServerLogs(params)
}
// init
async initFollowProgress(): Promise<RR.InitFollowProgressRes> {
async initFollowProgress(): Promise<T.SetupProgress> {
await pauseFor(250)
return {
progress: PROGRESS,
@@ -248,7 +271,7 @@ export class MockApiService extends ApiService {
}
}
async initFollowLogs(): Promise<RR.FollowServerLogsRes> {
async initFollowLogs(): Promise<T.LogFollowResponse> {
await pauseFor(2000)
return {
startCursor: 'start-cursor',
@@ -258,19 +281,15 @@ export class MockApiService extends ApiService {
// server
async getSystemTime(
params: RR.GetSystemTimeReq,
): Promise<RR.GetSystemTimeRes> {
async getSystemTime(params: {}): Promise<T.TimeInfo> {
await pauseFor(2000)
return {
now: new Date().toUTCString(),
uptime: 1234567,
uptime: 1234567n,
}
}
async getServerLogs(
params: RR.GetServerLogsReq,
): Promise<RR.GetServerLogsRes> {
async getServerLogs(params: T.LogsParams): Promise<T.LogResponse> {
await pauseFor(2000)
const entries = this.randomLogs(params.limit)
@@ -281,9 +300,7 @@ export class MockApiService extends ApiService {
}
}
async getKernelLogs(
params: RR.GetServerLogsReq,
): Promise<RR.GetServerLogsRes> {
async getKernelLogs(params: T.LogsParams): Promise<T.LogResponse> {
await pauseFor(2000)
const entries = this.randomLogs(params.limit)
@@ -295,8 +312,8 @@ export class MockApiService extends ApiService {
}
async followServerLogs(
params: RR.FollowServerLogsReq,
): Promise<RR.FollowServerLogsRes> {
params: FollowServerLogsReq,
): Promise<T.LogFollowResponse> {
await pauseFor(2000)
return {
startCursor: 'start-cursor',
@@ -305,8 +322,8 @@ export class MockApiService extends ApiService {
}
async followKernelLogs(
params: RR.FollowServerLogsReq,
): Promise<RR.FollowServerLogsRes> {
params: FollowServerLogsReq,
): Promise<T.LogFollowResponse> {
await pauseFor(2000)
return {
startCursor: 'start-cursor',
@@ -314,7 +331,7 @@ export class MockApiService extends ApiService {
}
}
private randomLogs(limit = 1): Log[] {
private randomLogs(limit = 1): T.LogEntry[] {
const arrLength = Math.ceil(limit / Mock.ServerLogs.length)
const logs = new Array(arrLength)
.fill(Mock.ServerLogs)
@@ -323,9 +340,7 @@ export class MockApiService extends ApiService {
return logs
}
async followServerMetrics(
params: RR.FollowServerMetricsReq,
): Promise<RR.FollowServerMetricsRes> {
async followServerMetrics(params: {}): Promise<T.MetricsFollowResponse> {
await pauseFor(2000)
return {
guid: 'metrics-guid',
@@ -333,7 +348,10 @@ export class MockApiService extends ApiService {
}
}
async updateServer(params?: RR.UpdateServerReq): Promise<RR.UpdateServerRes> {
async updateServer(params?: {
registry: string
targetVersion: string
}): Promise<'updating' | 'no-updates'> {
await pauseFor(2000)
const initialProgress = {
size: null,
@@ -356,9 +374,7 @@ export class MockApiService extends ApiService {
return 'updating'
}
async restartServer(
params: RR.RestartServerReq,
): Promise<RR.RestartServerRes> {
async restartServer(params: {}): Promise<null> {
await pauseFor(2000)
const patch = [
@@ -384,9 +400,7 @@ export class MockApiService extends ApiService {
return null
}
async shutdownServer(
params: RR.ShutdownServerReq,
): Promise<RR.ShutdownServerRes> {
async shutdownServer(params: {}): Promise<null> {
await pauseFor(2000)
const patch = [
@@ -412,7 +426,7 @@ export class MockApiService extends ApiService {
return null
}
async repairDisk(params: RR.RestartServerReq): Promise<RR.RestartServerRes> {
async repairDisk(params: {}): Promise<null> {
await pauseFor(2000)
return null
}
@@ -432,7 +446,7 @@ export class MockApiService extends ApiService {
return null
}
async setKeyboard(params: RR.SetKeyboardReq): Promise<RR.SetKeyboardRes> {
async setKeyboard(params: FullKeyboard): Promise<null> {
await pauseFor(1000)
const patch = [
@@ -447,7 +461,7 @@ export class MockApiService extends ApiService {
return null
}
async setLanguage(params: RR.SetLanguageReq): Promise<RR.SetLanguageRes> {
async setLanguage(params: SetLanguageParams): Promise<null> {
await pauseFor(1000)
const patch = [
@@ -462,7 +476,7 @@ export class MockApiService extends ApiService {
return null
}
async setDns(params: RR.SetDnsReq): Promise<RR.SetDnsRes> {
async setDns(params: T.SetStaticDnsParams): Promise<null> {
await pauseFor(2000)
const patch: ReplaceOperation<T.DnsSettings['staticServers']>[] = [
@@ -477,15 +491,16 @@ export class MockApiService extends ApiService {
return null
}
async queryDns(params: RR.QueryDnsReq): Promise<RR.QueryDnsRes> {
async queryDns(params: T.QueryDnsParams): Promise<string | null> {
await pauseFor(2000)
return null
}
async testPortForward(
params: RR.TestPortForwardReq,
): Promise<RR.TestPortForwardRes> {
async testPortForward(params: {
gateway: string
port: number
}): Promise<boolean> {
await pauseFor(2000)
return false
@@ -493,23 +508,22 @@ export class MockApiService extends ApiService {
// marketplace URLs
async checkOSUpdate(
params: RR.CheckOsUpdateReq,
): Promise<RR.CheckOsUpdateRes> {
async checkOSUpdate(params: {
registry: string
serverId: string
}): Promise<T.OsVersionInfoMap> {
await pauseFor(2000)
return Mock.RegistryOSUpdate
}
async getRegistryInfo(
params: RR.GetRegistryInfoReq,
): Promise<RR.GetRegistryInfoRes> {
async getRegistryInfo(params: { registry: string }): Promise<T.RegistryInfo> {
await pauseFor(2000)
return Mock.RegistryInfo
}
async getRegistryPackage(
params: RR.GetRegistryPackageReq,
): Promise<RR.GetRegistryPackageRes> {
params: GetRegistryPackageReq,
): Promise<GetPackageRes> {
await pauseFor(2000)
const { targetVersion, id } = params
@@ -522,8 +536,8 @@ export class MockApiService extends ApiService {
}
async getRegistryPackages(
params: RR.GetRegistryPackagesReq,
): Promise<RR.GetRegistryPackagesRes> {
params: GetRegistryPackagesReq,
): Promise<GetPackagesRes> {
await pauseFor(2000)
return Mock.RegistryPackages
}
@@ -531,37 +545,35 @@ export class MockApiService extends ApiService {
// notification
async getNotifications(
params: RR.GetNotificationsReq,
): Promise<RR.GetNotificationsRes> {
params: T.ListNotificationParams,
): Promise<T.NotificationWithId[]> {
await pauseFor(2000)
return Mock.Notifications
}
async deleteNotifications(
params: RR.DeleteNotificationsReq,
): Promise<RR.DeleteNotificationsRes> {
async deleteNotifications(params: T.ModifyNotificationParams): Promise<null> {
await pauseFor(2000)
return null
}
async markSeenNotifications(
params: RR.MarkSeenNotificationReq,
): Promise<RR.MarkSeenNotificationRes> {
params: T.ModifyNotificationParams,
): Promise<null> {
await pauseFor(2000)
return null
}
async markSeenAllNotifications(
params: RR.MarkSeenAllNotificationsReq,
): Promise<RR.MarkSeenAllNotificationsRes> {
params: T.ModifyNotificationBeforeParams,
): Promise<null> {
await pauseFor(2000)
return null
}
async markUnseenNotifications(
params: RR.MarkUnseenNotificationReq,
): Promise<RR.MarkUnseenNotificationRes> {
params: T.ModifyNotificationParams,
): Promise<null> {
await pauseFor(2000)
return null
}
@@ -569,7 +581,7 @@ export class MockApiService extends ApiService {
// proxies
private proxyId = 0
async addTunnel(params: RR.AddTunnelReq): Promise<RR.AddTunnelRes> {
async addTunnel(params: T.AddTunnelParams): Promise<{ id: string }> {
await pauseFor(2000)
const id = `wg${this.proxyId++}`
@@ -609,7 +621,7 @@ export class MockApiService extends ApiService {
return { id }
}
async updateTunnel(params: RR.UpdateTunnelReq): Promise<RR.UpdateTunnelRes> {
async updateTunnel(params: T.RenameGatewayParams): Promise<null> {
await pauseFor(2000)
const patch: ReplaceOperation<string>[] = [
@@ -624,7 +636,7 @@ export class MockApiService extends ApiService {
return null
}
async removeTunnel(params: RR.RemoveTunnelReq): Promise<RR.RemoveTunnelRes> {
async removeTunnel(params: T.RemoveTunnelParams): Promise<null> {
await pauseFor(2000)
const patch: RemoveOperation[] = [
{
@@ -637,9 +649,7 @@ export class MockApiService extends ApiService {
return null
}
async setDefaultOutbound(
params: RR.SetDefaultOutboundReq,
): Promise<RR.SetDefaultOutboundRes> {
async setDefaultOutbound(params: { gateway: string | null }): Promise<null> {
await pauseFor(2000)
const patch = [
{
@@ -653,9 +663,10 @@ export class MockApiService extends ApiService {
return null
}
async setServiceOutbound(
params: RR.SetServiceOutboundReq,
): Promise<RR.SetServiceOutboundRes> {
async setServiceOutbound(params: {
packageId: string
gateway: string | null
}): Promise<null> {
await pauseFor(2000)
const patch = [
{
@@ -671,13 +682,13 @@ export class MockApiService extends ApiService {
// wifi
async enableWifi(params: RR.EnabledWifiReq): Promise<RR.EnabledWifiRes> {
async enableWifi(params: T.SetWifiEnabledParams): Promise<null> {
await pauseFor(2000)
const patch = [
{
op: PatchOp.REPLACE,
path: '/serverInfo/network/wifi/enabled',
value: params.enable,
value: params.enabled,
},
]
this.mockRevision(patch)
@@ -685,36 +696,34 @@ export class MockApiService extends ApiService {
return null
}
async setWifiCountry(
params: RR.SetWifiCountryReq,
): Promise<RR.SetWifiCountryRes> {
async setWifiCountry(params: T.SetCountryParams): Promise<null> {
await pauseFor(2000)
return null
}
async getWifi(params: RR.GetWifiReq): Promise<RR.GetWifiRes> {
async getWifi(params: {}, timeout: number): Promise<T.WifiListInfo> {
await pauseFor(2000)
return Mock.Wifi
}
async addWifi(params: RR.AddWifiReq): Promise<RR.AddWifiRes> {
async addWifi(params: T.WifiAddParams): Promise<null> {
await pauseFor(2000)
return null
}
async connectWifi(params: RR.ConnectWifiReq): Promise<RR.ConnectWifiRes> {
async connectWifi(params: T.WifiSsidParams): Promise<null> {
await pauseFor(2000)
return null
}
async deleteWifi(params: RR.DeleteWifiReq): Promise<RR.DeleteWifiRes> {
async deleteWifi(params: T.WifiSsidParams): Promise<null> {
await pauseFor(2000)
return null
}
// smtp
async setSmtp(params: RR.SetSMTPReq): Promise<RR.SetSMTPRes> {
async setSmtp(params: T.SmtpValue): Promise<null> {
await pauseFor(2000)
const patch = [
{
@@ -728,7 +737,7 @@ export class MockApiService extends ApiService {
return null
}
async clearSmtp(params: RR.ClearSMTPReq): Promise<RR.ClearSMTPRes> {
async clearSmtp(params: {}): Promise<null> {
await pauseFor(2000)
const patch = [
{
@@ -742,40 +751,40 @@ export class MockApiService extends ApiService {
return null
}
async testSmtp(params: RR.TestSMTPReq): Promise<RR.TestSMTPRes> {
async testSmtp(params: T.TestSmtpParams): Promise<null> {
await pauseFor(2000)
return null
}
// ssh
async getSshKeys(params: RR.GetSSHKeysReq): Promise<RR.GetSSHKeysRes> {
async getSshKeys(params: {}): Promise<T.SshKeyResponse[]> {
await pauseFor(2000)
return Mock.SshKeys
}
async addSshKey(params: RR.AddSSHKeyReq): Promise<RR.AddSSHKeyRes> {
async addSshKey(params: T.SshAddParams): Promise<T.SshKeyResponse> {
await pauseFor(2000)
return Mock.SshKey
}
async deleteSshKey(params: RR.DeleteSSHKeyReq): Promise<RR.DeleteSSHKeyRes> {
async deleteSshKey(params: T.SshDeleteParams): Promise<null> {
await pauseFor(2000)
return null
}
// backup
async getBackupTargets(
params: RR.GetBackupTargetsReq,
): Promise<RR.GetBackupTargetsRes> {
async getBackupTargets(params: {}): Promise<{
[id: string]: T.BackupTarget
}> {
await pauseFor(2000)
return Mock.BackupTargets
}
async addBackupTarget(
params: RR.AddBackupTargetReq,
): Promise<RR.AddBackupTargetRes> {
params: T.CifsAddParams,
): Promise<{ [id: string]: CifsBackupTarget }> {
await pauseFor(2000)
const { hostname, path, username } = params
return {
@@ -791,8 +800,8 @@ export class MockApiService extends ApiService {
}
async updateBackupTarget(
params: RR.UpdateBackupTargetReq,
): Promise<RR.UpdateBackupTargetRes> {
params: T.CifsUpdateParams,
): Promise<{ [id: string]: CifsBackupTarget }> {
await pauseFor(2000)
const { id, hostname, path, username } = params
return {
@@ -805,24 +814,20 @@ export class MockApiService extends ApiService {
}
}
async removeBackupTarget(
params: RR.RemoveBackupTargetReq,
): Promise<RR.RemoveBackupTargetRes> {
async removeBackupTarget(params: T.CifsRemoveParams): Promise<null> {
await pauseFor(2000)
return null
}
async getBackupInfo(
params: RR.GetBackupInfoReq,
): Promise<RR.GetBackupInfoRes> {
async getBackupInfo(params: T.InfoParams): Promise<T.BackupInfo> {
await pauseFor(2000)
return Mock.BackupInfo
}
async createBackup(params: RR.CreateBackupReq): Promise<RR.CreateBackupRes> {
async createBackup(params: T.BackupParams): Promise<null> {
await pauseFor(2000)
const serverPath = '/serverInfo/statusInfo/backupProgress'
const ids = params.packageIds
const ids = params.packageIds || []
setTimeout(async () => {
for (let i = 0; i < ids.length; i++) {
@@ -978,9 +983,7 @@ export class MockApiService extends ApiService {
// package
async getPackageLogs(
params: RR.GetPackageLogsReq,
): Promise<RR.GetPackageLogsRes> {
async getPackageLogs(params: GetPackageLogsReq): Promise<T.LogResponse> {
await pauseFor(2000)
let entries
if (Math.random() < 0.2) {
@@ -1001,8 +1004,8 @@ export class MockApiService extends ApiService {
}
async followPackageLogs(
params: RR.FollowPackageLogsReq,
): Promise<RR.FollowPackageLogsRes> {
params: FollowPackageLogsReq,
): Promise<T.LogFollowResponse> {
await pauseFor(2000)
return {
startCursor: 'start-cursor',
@@ -1010,9 +1013,7 @@ export class MockApiService extends ApiService {
}
}
async installPackage(
params: RR.InstallPackageReq,
): Promise<RR.InstallPackageRes> {
async installPackage(params: T.InstallParams): Promise<null> {
await pauseFor(2000)
setTimeout(async () => {
@@ -1049,9 +1050,7 @@ export class MockApiService extends ApiService {
return null
}
async cancelInstallPackage(
params: RR.CancelInstallPackageReq,
): Promise<RR.CancelInstallPackageRes> {
async cancelInstallPackage(params: T.CancelInstallParams): Promise<null> {
await pauseFor(500)
const patch: RemoveOperation[] = [
@@ -1066,8 +1065,8 @@ export class MockApiService extends ApiService {
}
async getActionInput(
params: RR.GetActionInputReq,
): Promise<RR.GetActionInputRes> {
params: T.GetActionInputParams,
): Promise<GetActionInputRes> {
await pauseFor(2000)
return {
eventId: 'ANZXNWIFRTTBZ6T52KQPZILIQQODDHXQ',
@@ -1076,7 +1075,7 @@ export class MockApiService extends ApiService {
}
}
async runAction(params: RR.ActionReq): Promise<RR.ActionRes> {
async runAction(params: T.RunActionParams): Promise<ActionRes> {
await pauseFor(2000)
const patch: ReplaceOperation<{ [key: string]: T.TaskEntry }>[] = [
@@ -1093,7 +1092,7 @@ export class MockApiService extends ApiService {
// return Mock.ActionResSingle
}
async clearTask(params: RR.ClearTaskReq): Promise<RR.ClearTaskRes> {
async clearTask(params: T.ClearTaskParams): Promise<null> {
await pauseFor(2000)
const patch: RemoveOperation[] = [
@@ -1107,9 +1106,7 @@ export class MockApiService extends ApiService {
return null
}
async restorePackages(
params: RR.RestorePackagesReq,
): Promise<RR.RestorePackagesRes> {
async restorePackages(params: T.RestorePackageParams): Promise<null> {
await pauseFor(2000)
const patch: AddOperation<PackageDataEntry>[] = params.ids.map(id => {
setTimeout(async () => {
@@ -1137,7 +1134,7 @@ export class MockApiService extends ApiService {
return null
}
async startPackage(params: RR.StartPackageReq): Promise<RR.StartPackageRes> {
async startPackage(params: T.ControlParams): Promise<null> {
const path = `/packageData/${params.id}/statusInfo`
await pauseFor(2000)
@@ -1202,9 +1199,7 @@ export class MockApiService extends ApiService {
return null
}
async restartPackage(
params: RR.RestartPackageReq,
): Promise<RR.RestartPackageRes> {
async restartPackage(params: T.ControlParams): Promise<null> {
await pauseFor(2000)
const path = `/packageData/${params.id}/statusInfo`
@@ -1268,7 +1263,7 @@ export class MockApiService extends ApiService {
return null
}
async stopPackage(params: RR.StopPackageReq): Promise<RR.StopPackageRes> {
async stopPackage(params: T.ControlParams): Promise<null> {
await pauseFor(2000)
const path = `/packageData/${params.id}/statusInfo`
@@ -1306,15 +1301,11 @@ export class MockApiService extends ApiService {
return null
}
async rebuildPackage(
params: RR.RebuildPackageReq,
): Promise<RR.RebuildPackageRes> {
async rebuildPackage(params: T.RebuildParams): Promise<null> {
return this.restartPackage(params)
}
async uninstallPackage(
params: RR.UninstallPackageReq,
): Promise<RR.UninstallPackageRes> {
async uninstallPackage(params: T.UninstallParams): Promise<null> {
await pauseFor(2000)
setTimeout(async () => {
@@ -1340,7 +1331,7 @@ export class MockApiService extends ApiService {
return null
}
async sideloadPackage(): Promise<RR.SideloadPackageRes> {
async sideloadPackage(): Promise<T.SideloadResponse> {
await pauseFor(2000)
return {
upload: 'sideload-upload-guid', // no significance, randomly generated
@@ -1364,7 +1355,7 @@ export class MockApiService extends ApiService {
// return null
// }
async initAcme(params: RR.InitAcmeReq): Promise<RR.InitAcmeRes> {
async initAcme(params: T.InitAcmeParams): Promise<null> {
await pauseFor(2000)
const patch = [
@@ -1381,7 +1372,7 @@ export class MockApiService extends ApiService {
return null
}
async removeAcme(params: RR.RemoveAcmeReq): Promise<RR.RemoveAcmeRes> {
async removeAcme(params: T.RemoveAcmeParams): Promise<null> {
await pauseFor(2000)
const regex = new RegExp('/', 'g')
@@ -1398,8 +1389,8 @@ export class MockApiService extends ApiService {
}
async serverBindingSetAddressEnabled(
params: RR.ServerBindingSetAddressEnabledReq,
): Promise<RR.ServerBindingSetAddressEnabledRes> {
params: ServerBindingSetAddressEnabledReq,
): Promise<null> {
await pauseFor(2000)
const basePath = `/serverInfo/network/host/bindings/${params.internalPort}/addresses`
@@ -1409,8 +1400,8 @@ export class MockApiService extends ApiService {
}
async osUiAddPublicDomain(
params: RR.OsUiAddPublicDomainReq,
): Promise<RR.OsUiAddPublicDomainRes> {
params: T.AddPublicDomainParams,
): Promise<string | null> {
await pauseFor(2000)
const patch: Operation<any>[] = [
@@ -1438,9 +1429,7 @@ export class MockApiService extends ApiService {
return null
}
async osUiRemovePublicDomain(
params: RR.OsUiRemovePublicDomainReq,
): Promise<RR.OsUiRemovePublicDomainRes> {
async osUiRemovePublicDomain(params: T.RemoveDomainParams): Promise<null> {
await pauseFor(2000)
const patch: RemoveOperation[] = [
@@ -1454,9 +1443,7 @@ export class MockApiService extends ApiService {
return null
}
async osUiAddPrivateDomain(
params: RR.OsUiAddPrivateDomainReq,
): Promise<RR.OsUiAddPrivateDomainRes> {
async osUiAddPrivateDomain(params: T.AddPrivateDomainParams): Promise<null> {
await pauseFor(2000)
const patch: Operation<any>[] = [
@@ -1482,9 +1469,7 @@ export class MockApiService extends ApiService {
return null
}
async osUiRemovePrivateDomain(
params: RR.OsUiRemovePrivateDomainReq,
): Promise<RR.OsUiRemovePrivateDomainRes> {
async osUiRemovePrivateDomain(params: T.RemoveDomainParams): Promise<null> {
await pauseFor(2000)
const patch: RemoveOperation[] = [
@@ -1499,8 +1484,8 @@ export class MockApiService extends ApiService {
}
async pkgBindingSetAddressEnabled(
params: RR.PkgBindingSetAddressEnabledReq,
): Promise<RR.PkgBindingSetAddressEnabledRes> {
params: PkgBindingSetAddressEnabledReq,
): Promise<null> {
await pauseFor(2000)
const basePath = `/packageData/${params.package}/hosts/${params.host}/bindings/${params.internalPort}/addresses`
@@ -1510,8 +1495,8 @@ export class MockApiService extends ApiService {
}
async pkgAddPublicDomain(
params: RR.PkgAddPublicDomainReq,
): Promise<RR.PkgAddPublicDomainRes> {
params: PkgAddPublicDomainReq,
): Promise<string | null> {
await pauseFor(2000)
const patch: Operation<any>[] = [
@@ -1539,9 +1524,7 @@ export class MockApiService extends ApiService {
return null
}
async pkgRemovePublicDomain(
params: RR.PkgRemovePublicDomainReq,
): Promise<RR.PkgRemovePublicDomainRes> {
async pkgRemovePublicDomain(params: PkgRemovePublicDomainReq): Promise<null> {
await pauseFor(2000)
const patch: RemoveOperation[] = [
@@ -1555,9 +1538,7 @@ export class MockApiService extends ApiService {
return null
}
async pkgAddPrivateDomain(
params: RR.PkgAddPrivateDomainReq,
): Promise<RR.PkgAddPrivateDomainRes> {
async pkgAddPrivateDomain(params: PkgAddPrivateDomainReq): Promise<null> {
await pauseFor(2000)
const patch: Operation<any>[] = [
@@ -1584,8 +1565,8 @@ export class MockApiService extends ApiService {
}
async pkgRemovePrivateDomain(
params: RR.PkgRemovePrivateDomainReq,
): Promise<RR.PkgRemovePrivateDomainRes> {
params: PkgRemovePrivateDomainReq,
): Promise<null> {
await pauseFor(2000)
const patch: RemoveOperation[] = [

View File

@@ -110,6 +110,7 @@ export const mockPatchData: DataModel = {
},
publicDomains: {},
privateDomains: {},
portForwards: [],
},
gateways: {
eth0: {
@@ -607,6 +608,7 @@ export const mockPatchData: DataModel = {
privateDomains: {
'my-bitcoin.home': ['wlan0'],
},
portForwards: [],
},
bcdefgh: {
bindings: {
@@ -648,6 +650,7 @@ export const mockPatchData: DataModel = {
},
publicDomains: {},
privateDomains: {},
portForwards: [],
},
cdefghi: {
bindings: {
@@ -671,6 +674,7 @@ export const mockPatchData: DataModel = {
},
publicDomains: {},
privateDomains: {},
portForwards: [],
},
},
storeExposedDependents: [],

View File

@@ -28,7 +28,6 @@ import {
switchMap,
tap,
} from 'rxjs'
import { RR } from 'src/app/services/api/api.types'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { DataModel } from 'src/app/services/patch-db/data-model'
@@ -247,7 +246,7 @@ export class MarketplaceService {
version: string,
url: string,
): Promise<void> {
const params: RR.InstallPackageReq = {
const params: T.InstallParams = {
id,
version,
registry: url,

View File

@@ -10,8 +10,7 @@ import {
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { getServerInfo } from 'src/app/utils/get-server-info'
import { DataModel } from './patch-db/data-model'
import { Version } from '@start9labs/start-sdk'
import { RR } from './api/api.types'
import { T, Version } from '@start9labs/start-sdk'
@Injectable({
providedIn: 'root',
@@ -20,7 +19,7 @@ export class OSService {
private readonly api = inject(ApiService)
private readonly patch = inject<PatchDB<DataModel>>(PatchDB)
osUpdate?: RR.CheckOsUpdateRes
osUpdate?: T.OsVersionInfoMap
readonly updateAvailable$ = new BehaviorSubject<boolean>(false)
readonly updating$ = this.patch.watch$('serverInfo', 'statusInfo').pipe(

View File

@@ -15,7 +15,6 @@ import { getAllPackages } from '../utils/get-package-data'
import { hasCurrentDeps } from '../utils/has-deps'
import { ApiService } from './api/embassy-api.service'
import { DataModel } from './patch-db/data-model'
import { RR } from './api/api.types'
@Injectable({
providedIn: 'root',
@@ -78,7 +77,7 @@ export class StandardActionsService {
.subscribe(() => this.doUninstall({ id, force, soft }))
}
private async doUninstall(options: RR.UninstallPackageReq) {
private async doUninstall(options: T.UninstallParams) {
const loader = this.loader.open('Beginning uninstall').subscribe()
try {

View File

@@ -26,7 +26,7 @@ import {
takeUntil,
tap,
} from 'rxjs/operators'
import { RR } from 'src/app/services/api/api.types'
import { ServerState } from 'src/app/services/api/api.types'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { NetworkService } from 'src/app/services/network.service'
@@ -56,7 +56,7 @@ class DisconnectedToast {}
@Injectable({
providedIn: 'root',
})
export class StateService extends Observable<RR.ServerState | null> {
export class StateService extends Observable<ServerState | null> {
private readonly alerts = inject(TuiAlertService)
private readonly i18n = inject(i18nPipe)
private readonly api = inject(ApiService)
@@ -115,7 +115,7 @@ export class StateService extends Observable<RR.ServerState | null> {
setTimeout(() => this.trigger$.next(gracefully), delay)
}
private handleState(state: RR.ServerState): void {
private handleState(state: ServerState): void {
switch (state) {
case 'initializing':
this.router.navigate(['initializing'], { replaceUrl: true })
@@ -136,7 +136,7 @@ export class StateService extends Observable<RR.ServerState | null> {
}
}
export function stateNot(state: RR.ServerState[]): CanActivateFn {
export function stateNot(state: ServerState[]): CanActivateFn {
return () =>
inject(StateService).pipe(
filter(current => !current || !state.includes(current)),

View File

@@ -12,14 +12,15 @@ export class TimeService {
private readonly time$ = defer(() =>
inject(ApiService).getSystemTime({}),
).pipe(
switchMap(({ now, uptime }) =>
timer(0, 1000).pipe(
switchMap(({ now, uptime }) => {
const uptimeSecs = Number(uptime)
return timer(0, 1000).pipe(
map(index => ({
now: new Date(now).valueOf() + 1000 * index,
uptime: uptime + index,
uptime: uptimeSecs + index,
})),
),
),
)
}),
shareReplay(1),
)