From 657aac0d68e4f3b0205c9b616bd177a0f5a15194 Mon Sep 17 00:00:00 2001 From: waterplea Date: Sat, 10 Aug 2024 14:26:15 +0400 Subject: [PATCH 1/3] fix: fix merge issues for setup-wizard project --- web/angular.json | 2 +- .../setup-wizard/src/app/app.component.ts | 2 +- .../setup-wizard/src/app/app.module.ts | 9 +- .../src/app/components/cifs.component.ts | 68 +++++----- .../src/app/components/matrix.component.ts | 13 +- .../src/app/components/password.component.ts | 26 +--- .../src/app/components/server.component.ts | 46 +++++++ .../src/app/components/servers.component.ts | 37 ++++++ .../server-backup-select.module.ts | 13 -- .../server-backup-select.page.html | 24 ---- .../server-backup-select.page.scss | 0 .../server-backup-select.page.ts | 44 ------- .../setup-wizard/src/app/pages/home.page.ts | 2 +- .../src/app/pages/loading.page.ts | 100 ++++++++++++-- .../src/app/pages/recover.page.ts | 123 ++++++++---------- .../src/app/pages/storage.page.ts | 52 ++++---- .../src/app/services/live-api.service.ts | 10 +- .../src/app/services/mock-api.service.ts | 21 +-- .../initializing/initializing.component.ts | 53 ++------ .../src/components/loading/loading.module.ts | 13 -- .../shared/src/mocks/get-setup-status.ts | 25 ---- web/projects/shared/src/public-api.ts | 4 - .../shared/src/services/setup.service.ts | 59 --------- web/projects/shared/src/types/api.ts | 6 - .../app/services/api/embassy-api.service.ts | 9 +- .../services/api/embassy-mock-api.service.ts | 1 - web/projects/ui/src/polyfills.ts | 57 +------- 27 files changed, 322 insertions(+), 497 deletions(-) create mode 100644 web/projects/setup-wizard/src/app/components/server.component.ts create mode 100644 web/projects/setup-wizard/src/app/components/servers.component.ts delete mode 100644 web/projects/setup-wizard/src/app/modals/server-backup-select/server-backup-select.module.ts delete mode 100644 web/projects/setup-wizard/src/app/modals/server-backup-select/server-backup-select.page.html delete mode 100644 web/projects/setup-wizard/src/app/modals/server-backup-select/server-backup-select.page.scss delete mode 100644 web/projects/setup-wizard/src/app/modals/server-backup-select/server-backup-select.page.ts delete mode 100644 web/projects/shared/src/components/loading/loading.module.ts delete mode 100644 web/projects/shared/src/mocks/get-setup-status.ts delete mode 100644 web/projects/shared/src/services/setup.service.ts diff --git a/web/angular.json b/web/angular.json index edc4de6e3..4b7c0b9e1 100644 --- a/web/angular.json +++ b/web/angular.json @@ -17,7 +17,7 @@ "outputPath": "dist/raw/ui", "index": "projects/ui/src/index.html", "main": "projects/ui/src/main.ts", - "polyfills": ["zone.js"], + "polyfills": ["zone.js", "projects/ui/src/polyfills.ts"], "tsConfig": "projects/ui/tsconfig.json", "inlineStyleLanguage": "scss", "assets": [ diff --git a/web/projects/setup-wizard/src/app/app.component.ts b/web/projects/setup-wizard/src/app/app.component.ts index bebbb22cc..cecb85eec 100644 --- a/web/projects/setup-wizard/src/app/app.component.ts +++ b/web/projects/setup-wizard/src/app/app.component.ts @@ -14,7 +14,7 @@ export class AppComponent { async ngOnInit() { try { - const inProgress = await this.api.getSetupStatus() + const inProgress = await this.api.getStatus() let route = 'home' diff --git a/web/projects/setup-wizard/src/app/app.module.ts b/web/projects/setup-wizard/src/app/app.module.ts index a8a52a69c..fc23c15f2 100644 --- a/web/projects/setup-wizard/src/app/app.module.ts +++ b/web/projects/setup-wizard/src/app/app.module.ts @@ -2,12 +2,7 @@ import { HttpClientModule } from '@angular/common/http' import { NgModule } from '@angular/core' import { BrowserAnimationsModule } from '@angular/platform-browser/animations' import { PreloadAllModules, RouterModule } from '@angular/router' -import { - provideSetupLogsService, - provideSetupService, - RELATIVE_URL, - WorkspaceConfig, -} from '@start9labs/shared' +import { RELATIVE_URL, WorkspaceConfig } from '@start9labs/shared' import { tuiButtonOptionsProvider, TuiRoot } from '@taiga-ui/core' import { NG_EVENT_PLUGINS } from '@taiga-ui/event-plugins' import { ApiService } from 'src/app/services/api.service' @@ -34,8 +29,6 @@ const { ], providers: [ NG_EVENT_PLUGINS, - provideSetupService(ApiService), - provideSetupLogsService(ApiService), tuiButtonOptionsProvider({ size: 'm' }), { provide: ApiService, diff --git a/web/projects/setup-wizard/src/app/components/cifs.component.ts b/web/projects/setup-wizard/src/app/components/cifs.component.ts index 43a687f6a..095a2d8f0 100644 --- a/web/projects/setup-wizard/src/app/components/cifs.component.ts +++ b/web/projects/setup-wizard/src/app/components/cifs.component.ts @@ -1,6 +1,5 @@ -import { TuiInputModule, TuiInputPasswordModule } from '@taiga-ui/legacy' import { CommonModule } from '@angular/common' -import { Component, inject, Inject } from '@angular/core' +import { Component, inject } from '@angular/core' import { FormControl, FormGroup, @@ -9,24 +8,26 @@ import { Validators, } from '@angular/forms' import { LoadingService, StartOSDiskInfo } from '@start9labs/shared' +import { T } from '@start9labs/start-sdk' import { + TuiButton, TuiDialogContext, TuiDialogService, TuiError, - TuiButton, } from '@taiga-ui/core' import { TUI_VALIDATION_ERRORS, TuiFieldErrorPipe } from '@taiga-ui/kit' -import { POLYMORPHEUS_CONTEXT } from '@taiga-ui/polymorpheus' -import { PASSWORD } from 'src/app/components/password.component' +import { TuiInputModule, TuiInputPasswordModule } from '@taiga-ui/legacy' import { - ApiService, - CifsBackupTarget, - CifsRecoverySource, -} from 'src/app/services/api.service' + POLYMORPHEUS_CONTEXT, + PolymorpheusComponent, +} from '@taiga-ui/polymorpheus' +import { SERVERS, ServersResponse } from 'src/app/components/servers.component' +import { ApiService } from 'src/app/services/api.service' -interface Context { - cifs: CifsRecoverySource - recoveryPassword: string +export interface CifsResponse { + cifs: T.Cifs + serverId: string + password: string } @Component({ @@ -34,10 +35,10 @@ interface Context { template: `
- Hostname + Hostname * - Path + Path * - Username + Username * >(POLYMORPHEUS_CONTEXT) + inject>(POLYMORPHEUS_CONTEXT) readonly form = new FormGroup({ hostname: new FormControl('', { @@ -141,7 +142,6 @@ export class CifsComponent { try { const diskInfo = await this.api.verifyCifs({ ...this.form.getRawValue(), - type: 'cifs', password: this.form.value.password ? await this.api.encrypt(String(this.form.value.password)) : null, @@ -149,35 +149,31 @@ export class CifsComponent { loader.unsubscribe() - this.presentModalPassword(diskInfo) + this.selectServer(diskInfo) } catch (e) { loader.unsubscribe() - this.presentAlertFailed() + this.onFail() } } - private presentModalPassword(diskInfo: StartOSDiskInfo) { - const target: CifsBackupTarget = { - ...this.form.getRawValue(), - mountable: true, - startOs: diskInfo, - } - + private selectServer(servers: Record) { this.dialogs - .open(PASSWORD, { - label: 'Unlock Drive', - size: 's', - data: { target }, + .open(SERVERS, { + label: 'Select Server to Restore', + data: { + servers: Object.keys(servers).map(id => ({ id, ...servers[id] })), + }, }) - .subscribe(recoveryPassword => { + .subscribe(({ password, serverId }) => { this.context.completeWith({ - cifs: { ...this.form.getRawValue(), type: 'cifs' }, - recoveryPassword, + cifs: { ...this.form.getRawValue() }, + serverId, + password, }) }) } - private presentAlertFailed() { + private onFail() { this.dialogs .open( 'Unable to connect to shared folder. Ensure (1) target computer is connected to LAN, (2) target folder is being shared, and (3) hostname, path, and credentials are accurate.', @@ -189,3 +185,5 @@ export class CifsComponent { .subscribe() } } + +export const CIFS = new PolymorpheusComponent(CifsComponent) diff --git a/web/projects/setup-wizard/src/app/components/matrix.component.ts b/web/projects/setup-wizard/src/app/components/matrix.component.ts index e2b04a6b3..7a58e11f0 100644 --- a/web/projects/setup-wizard/src/app/components/matrix.component.ts +++ b/web/projects/setup-wizard/src/app/components/matrix.component.ts @@ -1,12 +1,5 @@ -import { - Component, - Directive, - ElementRef, - inject, - NgZone, - OnInit, -} from '@angular/core' -import { WINDOW } from '@ng-web-apis/common' +import { Component, ElementRef, inject, NgZone, OnInit } from '@angular/core' +import { WA_WINDOW } from '@ng-web-apis/common' // a higher fade factor will make the characters fade quicker const FADE_FACTOR = 0.07 @@ -19,7 +12,7 @@ const FADE_FACTOR = 0.07 }) export class MatrixComponent implements OnInit { private readonly ngZone = inject(NgZone) - private readonly window = inject(WINDOW) + private readonly window = inject(WA_WINDOW) private readonly el: HTMLCanvasElement = inject(ElementRef).nativeElement private readonly ctx = this.el.getContext('2d')! diff --git a/web/projects/setup-wizard/src/app/components/password.component.ts b/web/projects/setup-wizard/src/app/components/password.component.ts index 61587e2ec..4d35edbb9 100644 --- a/web/projects/setup-wizard/src/app/components/password.component.ts +++ b/web/projects/setup-wizard/src/app/components/password.component.ts @@ -8,13 +8,9 @@ import { POLYMORPHEUS_CONTEXT, PolymorpheusComponent, } from '@taiga-ui/polymorpheus' -import { - CifsBackupTarget, - DiskBackupTarget, -} from 'src/app/services/api.service' interface DialogData { - target?: CifsBackupTarget | DiskBackupTarget + passwordHash?: string storageDrive?: boolean } @@ -73,24 +69,14 @@ export class PasswordComponent { private readonly context = inject>(POLYMORPHEUS_CONTEXT) - readonly target = this.context.data.target readonly storageDrive = this.context.data.storageDrive readonly password = new FormControl('', { nonNullable: true }) readonly confirm = new FormControl('', { nonNullable: true }) get passwordError(): string | null { - if (!this.password.touched || this.target) return null - - if (!this.storageDrive && !this.target?.['embassy-os']) - return 'No recovery target' // unreachable - - if (this.password.value.length < 12) - return 'Must be 12 characters or greater' - - if (this.password.value.length > 64) - return 'Must be less than 65 characters' - - return null + return this.password.touched && this.password.value.length < 12 + ? 'Must be 12 characters or greater' + : null } get confirmError(): string | null { @@ -107,9 +93,7 @@ export class PasswordComponent { } try { - const passwordHash = this.target!.startOs?.passwordHash || '' - - argon2.verify(passwordHash, this.password.value) + argon2.verify(this.context.data.passwordHash || '', this.password.value) this.context.completeWith(this.password.value) } catch (e) { this.errorService.handleError('Incorrect password provided') diff --git a/web/projects/setup-wizard/src/app/components/server.component.ts b/web/projects/setup-wizard/src/app/components/server.component.ts new file mode 100644 index 000000000..2a5440232 --- /dev/null +++ b/web/projects/setup-wizard/src/app/components/server.component.ts @@ -0,0 +1,46 @@ +import { DatePipe } from '@angular/common' +import { Component, ElementRef, inject, input, Output } from '@angular/core' +import { StartOSDiskInfo } from '@start9labs/shared' +import { TuiDialogService, TuiIcon, TuiTitle } from '@taiga-ui/core' +import { TuiCell } from '@taiga-ui/layout' +import { filter, fromEvent, switchMap } from 'rxjs' +import { PASSWORD } from 'src/app/components/password.component' + +@Component({ + standalone: true, + selector: 'button[server]', + template: ` + + + {{ server().hostname }}.local + + StartOS Version + : {{ server().version }} + + + Created + : {{ server().timestamp | date: 'medium' }} + + + `, + styles: ':host { width: stretch; border-radius: var(--tui-radius-l); }', + hostDirectives: [TuiCell], + imports: [DatePipe, TuiIcon, TuiTitle], +}) +export class ServerComponent { + private readonly dialogs = inject(TuiDialogService) + + readonly server = input.required() + + @Output() + readonly password = fromEvent(inject(ElementRef).nativeElement, 'click').pipe( + switchMap(() => + this.dialogs.open(PASSWORD, { + label: 'Unlock Drive', + size: 's', + data: { passwordHash: this.server().passwordHash }, + }), + ), + filter(Boolean), + ) +} diff --git a/web/projects/setup-wizard/src/app/components/servers.component.ts b/web/projects/setup-wizard/src/app/components/servers.component.ts new file mode 100644 index 000000000..b8b98ca02 --- /dev/null +++ b/web/projects/setup-wizard/src/app/components/servers.component.ts @@ -0,0 +1,37 @@ +import { Component, inject } from '@angular/core' +import { TuiDialogContext } from '@taiga-ui/core' +import { + POLYMORPHEUS_CONTEXT, + PolymorpheusComponent, +} from '@taiga-ui/polymorpheus' +import { ServerComponent } from 'src/app/components/server.component' +import { StartOSDiskInfoWithId } from 'src/app/services/api.service' + +interface Data { + servers: StartOSDiskInfoWithId[] +} + +export interface ServersResponse { + password: string + serverId: string +} + +@Component({ + standalone: true, + template: ` + @for (server of context.data.servers; track $index) { + + } + `, + imports: [ServerComponent], +}) +export class ServersComponent { + readonly context = + inject>(POLYMORPHEUS_CONTEXT) + + select(password: string, serverId: string) { + this.context.completeWith({ serverId, password }) + } +} + +export const SERVERS = new PolymorpheusComponent(ServersComponent) diff --git a/web/projects/setup-wizard/src/app/modals/server-backup-select/server-backup-select.module.ts b/web/projects/setup-wizard/src/app/modals/server-backup-select/server-backup-select.module.ts deleted file mode 100644 index 8305cd2a6..000000000 --- a/web/projects/setup-wizard/src/app/modals/server-backup-select/server-backup-select.module.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { NgModule } from '@angular/core' -import { CommonModule } from '@angular/common' -import { IonicModule } from '@ionic/angular' -import { FormsModule } from '@angular/forms' -import { ServerBackupSelectModal } from './server-backup-select.page' -import { PasswordPageModule } from '../password/password.module' - -@NgModule({ - declarations: [ServerBackupSelectModal], - imports: [CommonModule, FormsModule, IonicModule, PasswordPageModule], - exports: [ServerBackupSelectModal], -}) -export class ServerBackupSelectModule {} diff --git a/web/projects/setup-wizard/src/app/modals/server-backup-select/server-backup-select.page.html b/web/projects/setup-wizard/src/app/modals/server-backup-select/server-backup-select.page.html deleted file mode 100644 index 37736d78e..000000000 --- a/web/projects/setup-wizard/src/app/modals/server-backup-select/server-backup-select.page.html +++ /dev/null @@ -1,24 +0,0 @@ - - - Select Server to Restore - - - - - - -

- Local Hostname - : {{ server.hostname }}.local -

-

- StartOS Version - : {{ server.version }} -

-

- Created - : {{ server.timestamp | date : 'medium' }} -

-
-
-
diff --git a/web/projects/setup-wizard/src/app/modals/server-backup-select/server-backup-select.page.scss b/web/projects/setup-wizard/src/app/modals/server-backup-select/server-backup-select.page.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/projects/setup-wizard/src/app/modals/server-backup-select/server-backup-select.page.ts b/web/projects/setup-wizard/src/app/modals/server-backup-select/server-backup-select.page.ts deleted file mode 100644 index 9b0d8cf8b..000000000 --- a/web/projects/setup-wizard/src/app/modals/server-backup-select/server-backup-select.page.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { Component, Input } from '@angular/core' -import { ModalController } from '@ionic/angular' -import { StartOSDiskInfoWithId } from 'src/app/services/api/api.service' -import { PasswordPage } from '../password/password.page' - -@Component({ - selector: 'server-backup-select', - templateUrl: 'server-backup-select.page.html', - styleUrls: ['server-backup-select.page.scss'], -}) -export class ServerBackupSelectModal { - @Input() servers: StartOSDiskInfoWithId[] = [] - - constructor(private readonly modalController: ModalController) {} - - cancel() { - this.modalController.dismiss() - } - - async select(server: StartOSDiskInfoWithId): Promise { - this.presentModalPassword(server) - } - - private async presentModalPassword( - server: StartOSDiskInfoWithId, - ): Promise { - const modal = await this.modalController.create({ - component: PasswordPage, - componentProps: { passwordHash: server.passwordHash }, - }) - modal.onDidDismiss().then(res => { - if (res.role === 'success') { - this.modalController.dismiss( - { - serverId: server.id, - recoveryPassword: res.data.password, - }, - 'success', - ) - } - }) - await modal.present() - } -} diff --git a/web/projects/setup-wizard/src/app/pages/home.page.ts b/web/projects/setup-wizard/src/app/pages/home.page.ts index 37aa652de..87fea8795 100644 --- a/web/projects/setup-wizard/src/app/pages/home.page.ts +++ b/web/projects/setup-wizard/src/app/pages/home.page.ts @@ -26,7 +26,7 @@ import { StateService } from 'src/app/services/state.service' Back } - {{ recover ? 'StartOS Setup' : 'Recover Options' }} + {{ recover ? 'Recover Options' : 'StartOS Setup' }}
diff --git a/web/projects/setup-wizard/src/app/pages/loading.page.ts b/web/projects/setup-wizard/src/app/pages/loading.page.ts index e65e791e7..e146b9aa8 100644 --- a/web/projects/setup-wizard/src/app/pages/loading.page.ts +++ b/web/projects/setup-wizard/src/app/pages/loading.page.ts @@ -1,20 +1,104 @@ import { ChangeDetectionStrategy, Component, inject } from '@angular/core' +import { toSignal } from '@angular/core/rxjs-interop' import { Router } from '@angular/router' -import { InitializingComponent } from '@start9labs/shared' +import { ErrorService, InitializingComponent } from '@start9labs/shared' +import { T } from '@start9labs/start-sdk' +import { + catchError, + EMPTY, + filter, + from, + interval, + map, + startWith, + switchMap, + take, + tap, +} from 'rxjs' +import { ApiService } from 'src/app/services/api.service' import { StateService } from 'src/app/services/state.service' @Component({ standalone: true, - template: ` - - `, + template: '', imports: [InitializingComponent], changeDetection: ChangeDetectionStrategy.OnPush, }) export default class LoadingPage { - readonly stateService = inject(StateService) + private readonly api = inject(ApiService) + private readonly errorService = inject(ErrorService) + + readonly type = inject(StateService).setupType readonly router = inject(Router) + readonly progress = toSignal( + from(this.getStatus()).pipe( + filter(Boolean), + take(1), + switchMap(({ guid, progress }) => + this.api.openProgressWebsocket$(guid).pipe( + startWith(progress), + catchError((_, watch$) => + interval(2000).pipe( + switchMap(() => + from(this.api.getStatus()).pipe(catchError(() => EMPTY)), + ), + take(1), + switchMap(() => watch$), + ), + ), + tap(({ overall }) => { + if (overall === true) { + this.getStatus() + } + }), + ), + ), + map(({ phases, overall }) => ({ + total: getDecimal(overall), + message: phases + .filter(p => p.progress !== true && p.progress !== null) + .map(p => `${p.name}${getPhaseBytes(p.progress)}`) + .join(','), + })), + catchError(e => { + this.errorService.handleError(e) + return EMPTY + }), + ), + { initialValue: { total: 0, message: '' } }, + ) + + private async getStatus(): Promise<{ + status: 'running' + guid: string + progress: T.FullProgress + } | null> { + const res = await this.api.getStatus() + + if (!res) { + this.router.navigate(['home']) + return null + } else if (res.status === 'complete') { + this.router.navigate(['success']) + return null + } else { + return res + } + } +} + +function getDecimal(progress: T.Progress): number { + if (progress === true) { + return 1 + } else if (!progress || !progress.total) { + return 0 + } else { + return progress.total && progress.done / progress.total + } +} + +function getPhaseBytes(progress: T.Progress): string { + return progress === true || !progress + ? '' + : `: (${progress.done}/${progress.total})` } diff --git a/web/projects/setup-wizard/src/app/pages/recover.page.ts b/web/projects/setup-wizard/src/app/pages/recover.page.ts index 0a6647202..9ba144af0 100644 --- a/web/projects/setup-wizard/src/app/pages/recover.page.ts +++ b/web/projects/setup-wizard/src/app/pages/recover.page.ts @@ -1,6 +1,7 @@ +import { DatePipe } from '@angular/common' import { Component, inject } from '@angular/core' import { Router } from '@angular/router' -import { DriveComponent, ErrorService } from '@start9labs/shared' +import { ErrorService } from '@start9labs/shared' import { TuiButton, TuiDialogService, @@ -9,15 +10,9 @@ import { TuiTitle, } from '@taiga-ui/core' import { TuiCardLarge, TuiCell } from '@taiga-ui/layout' -import { PolymorpheusComponent } from '@taiga-ui/polymorpheus' -import { filter } from 'rxjs' -import { CifsComponent } from 'src/app/components/cifs.component' -import { PASSWORD } from 'src/app/components/password.component' -import { - ApiService, - CifsRecoverySource, - DiskBackupTarget, -} from 'src/app/services/api.service' +import { CIFS, CifsResponse } from 'src/app/components/cifs.component' +import { ServerComponent } from 'src/app/components/server.component' +import { ApiService, StartOSDiskInfoWithId } from 'src/app/services/api.service' import { StateService } from 'src/app/services/state.service' @Component({ @@ -38,8 +33,10 @@ import { StateService } from 'src/app/services/state.service'

Physical Drive

- Restore StartOS data from a physical drive that is plugged directly into - your server. +
+ Restore StartOS data from a physical drive that is plugged directly + into your server. +
Warning. Do not use this option if you are using a Raspberry Pi with an external SSD as your main data drive. The Raspberry Pi cannot not @@ -47,18 +44,11 @@ import { StateService } from 'src/app/services/state.service' cause data corruption. - @for (d of drives; track d) { - + @for (server of servers; track $index) { + } } - @@ -81,21 +76,24 @@ export default class StoragePage { const disks = await this.api.getDrives() if (this.stateService.setupType === 'fresh') { this.drives = disks - } else if (this.stateService.setupType === 'restore') { - this.drives = disks.filter( - d => - !d.partitions - .map(p => p.logicalname) - .includes( - ( - (this.stateService.recoverySource as BackupRecoverySource) - ?.target as DiskRecoverySource - )?.logicalname, - ), - ) - } else if (this.stateService.setupType === 'transfer') { - const guid = (this.stateService.recoverySource as DiskMigrateSource) - .guid + } else if ( + this.stateService.setupType === 'restore' && + this.stateService.recoverySource?.type === 'backup' + ) { + if (this.stateService.recoverySource.target.type === 'disk') { + const logicalname = + this.stateService.recoverySource.target.logicalname + this.drives = disks.filter( + d => !d.partitions.map(p => p.logicalname).includes(logicalname), + ) + } else { + this.drives = disks + } + } else if ( + this.stateService.setupType === 'transfer' && + this.stateService.recoverySource?.type === 'migrate' + ) { + const guid = this.stateService.recoverySource.guid this.drives = disks.filter(d => { return ( d.guid !== guid && !d.partitions.map(p => p.guid).includes(guid) @@ -130,19 +128,19 @@ export default class StoragePage { .pipe(filter(Boolean)) .subscribe(() => { // for backup recoveries - if (this.stateService.recoveryPassword) { + if (this.stateService.recoverySource?.type === 'backup') { this.setupEmbassy( drive.logicalname, - this.stateService.recoveryPassword, + this.stateService.recoverySource.password, ) } else { // for migrations and fresh setups - this.presentModalPassword(drive.logicalname) + this.promptPassword(drive.logicalname) } }) } - private presentModalPassword(logicalname: string) { + private promptPassword(logicalname: string) { this.dialogs .open(PASSWORD, { label: 'Set Password', @@ -162,7 +160,7 @@ export default class StoragePage { try { await this.stateService.setupEmbassy(logicalname, password) - await this.router.navigate([`loading`]) + await this.router.navigate(['loading']) } catch (e: any) { this.errorService.handleError(e) } finally { diff --git a/web/projects/setup-wizard/src/app/services/live-api.service.ts b/web/projects/setup-wizard/src/app/services/live-api.service.ts index ea534bb6b..161b611ca 100644 --- a/web/projects/setup-wizard/src/app/services/live-api.service.ts +++ b/web/projects/setup-wizard/src/app/services/live-api.service.ts @@ -1,23 +1,19 @@ +import { DOCUMENT } from '@angular/common' import { Inject, Injectable } from '@angular/core' import { DiskListResponse, - StartOSDiskInfo, encodeBase64, HttpService, isRpcError, - Log, RpcError, RPCOptions, - SetupStatus, - FollowLogsRes, - FollowLogsReq, + StartOSDiskInfo, } from '@start9labs/shared' import { T } from '@start9labs/start-sdk' -import { ApiService } from './api.service' import * as jose from 'node-jose' import { Observable } from 'rxjs' -import { DOCUMENT } from '@angular/common' import { webSocket } from 'rxjs/webSocket' +import { ApiService } from './api.service' @Injectable({ providedIn: 'root', diff --git a/web/projects/setup-wizard/src/app/services/mock-api.service.ts b/web/projects/setup-wizard/src/app/services/mock-api.service.ts index 96164333f..6199e9c2a 100644 --- a/web/projects/setup-wizard/src/app/services/mock-api.service.ts +++ b/web/projects/setup-wizard/src/app/services/mock-api.service.ts @@ -1,27 +1,14 @@ import { Injectable } from '@angular/core' import { DiskListResponse, - StartOSDiskInfo, encodeBase64, pauseFor, + StartOSDiskInfo, } from '@start9labs/shared' -import { ApiService } from './api.service' -import * as jose from 'node-jose' import { T } from '@start9labs/start-sdk' -import { - Observable, - concatMap, - delay, - from, - interval, - map, - mergeScan, - of, - startWith, - switchMap, - switchScan, - takeWhile, -} from 'rxjs' +import * as jose from 'node-jose' +import { Observable, of } from 'rxjs' +import { ApiService } from './api.service' @Injectable({ providedIn: 'root', diff --git a/web/projects/shared/src/components/initializing/initializing.component.ts b/web/projects/shared/src/components/initializing/initializing.component.ts index 180019682..524c80ff8 100644 --- a/web/projects/shared/src/components/initializing/initializing.component.ts +++ b/web/projects/shared/src/components/initializing/initializing.component.ts @@ -1,39 +1,29 @@ -import { TuiLet } from '@taiga-ui/cdk' import { CommonModule } from '@angular/common' -import { - ChangeDetectionStrategy, - Component, - inject, - Input, - Output, -} from '@angular/core' +import { ChangeDetectionStrategy, Component, Input } from '@angular/core' +import { TuiLet } from '@taiga-ui/cdk' import { TuiProgress } from '@taiga-ui/kit' -import { delay, filter } from 'rxjs' import { LogsWindowComponent } from './logs-window.component' -import { SetupService } from '../../services/setup.service' @Component({ standalone: true, selector: 'app-initializing', template: ` -
-

- Initializing StartOS +
+

+ Setting up your server

-
- Progress: {{ (progress * 100).toFixed(0) }}% +
+ Progress: {{ (progress.total * 100).toFixed(0) }}%
- -

{{ getMessage(progress) }}

+

{{ progress.message }}

- + `, styles: ` section { @@ -64,28 +54,9 @@ import { SetupService } from '../../services/setup.service' changeDetection: ChangeDetectionStrategy.OnPush, }) export class InitializingComponent { - readonly progress$ = inject(SetupService) + @Input() + progress: { total: number; message: string } = { total: 0, message: '' } @Input() setupType?: 'fresh' | 'restore' | 'attach' | 'transfer' - - @Output() - readonly finished = this.progress$.pipe( - filter(progress => progress === 1), - delay(500), - ) - - getMessage(progress: number | null): string { - if (['fresh', 'attach'].includes(this.setupType || '')) { - return 'Setting up your server' - } - - if (!progress) { - return 'Preparing data. This can take a while' - } else if (progress < 1) { - return 'Copying data' - } else { - return 'Finalizing' - } - } } diff --git a/web/projects/shared/src/components/loading/loading.module.ts b/web/projects/shared/src/components/loading/loading.module.ts deleted file mode 100644 index 4a3798041..000000000 --- a/web/projects/shared/src/components/loading/loading.module.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { NgModule } from '@angular/core' -import { TuiLoaderModule } from '@taiga-ui/core' -import { tuiAsDialog } from '@taiga-ui/cdk' -import { LoadingComponent } from './loading.component' -import { LoadingService } from './loading.service' - -@NgModule({ - imports: [TuiLoaderModule], - declarations: [LoadingComponent], - exports: [LoadingComponent], - providers: [tuiAsDialog(LoadingService)], -}) -export class LoadingModule {} diff --git a/web/projects/shared/src/mocks/get-setup-status.ts b/web/projects/shared/src/mocks/get-setup-status.ts deleted file mode 100644 index 0abf1360f..000000000 --- a/web/projects/shared/src/mocks/get-setup-status.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { SetupStatus } from '../types/api' -import { pauseFor } from '../util/misc.util' - -let tries: number | undefined - -export async function getSetupStatusMock(): Promise { - const restoreOrMigrate = true - const total = 4 - - await pauseFor(1000) - - if (tries === undefined) { - tries = 0 - return null - } - - tries++ - const progress = tries - 1 - - return { - bytesTransferred: restoreOrMigrate ? progress : 0, - totalBytes: restoreOrMigrate ? total : null, - complete: progress === total, - } -} diff --git a/web/projects/shared/src/public-api.ts b/web/projects/shared/src/public-api.ts index 7d5767e33..4728e2b9a 100644 --- a/web/projects/shared/src/public-api.ts +++ b/web/projects/shared/src/public-api.ts @@ -9,7 +9,6 @@ export * from './components/initializing/logs-window.component' export * from './components/initializing/initializing.component' export * from './components/loading/loading.component' export * from './components/loading/loading.component' -export * from './components/loading/loading.module' export * from './components/loading/loading.service' export * from './components/markdown/markdown.component' export * from './components/markdown/markdown.component.module' @@ -20,8 +19,6 @@ export * from './components/drive.component' export * from './directives/drag-scroller.directive' export * from './directives/safe-links.directive' -export * from './mocks/get-setup-status' - export * from './pipes/exver/exver.module' export * from './pipes/exver/exver.pipe' export * from './pipes/markdown/markdown.module' @@ -38,7 +35,6 @@ export * from './services/download-html.service' export * from './services/exver.service' export * from './services/error.service' export * from './services/http.service' -export * from './services/setup.service' export * from './services/setup-logs.service' export * from './types/api' diff --git a/web/projects/shared/src/services/setup.service.ts b/web/projects/shared/src/services/setup.service.ts deleted file mode 100644 index 645a0fb12..000000000 --- a/web/projects/shared/src/services/setup.service.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { inject, StaticClassProvider } from '@angular/core' -import { - catchError, - EMPTY, - exhaustMap, - filter, - from, - interval, - map, - Observable, - shareReplay, - takeWhile, -} from 'rxjs' -import { SetupStatus } from '../types/api' -import { Constructor } from '../types/constructor' -import { ErrorService } from './error.service' - -export function provideSetupService( - api: Constructor[0]>, -): StaticClassProvider { - return { - provide: SetupService, - deps: [api], - useClass: SetupService, - } -} - -export class SetupService extends Observable { - private readonly errorService = inject(ErrorService) - private readonly progress$ = interval(500).pipe( - exhaustMap(() => - from(this.api.getSetupStatus()).pipe( - catchError(e => { - this.errorService.handleError(e) - - return EMPTY - }), - ), - ), - filter(Boolean), - map(progress => { - if (progress.complete) { - return 1 - } - - return progress.totalBytes - ? progress.bytesTransferred / progress.totalBytes - : 0 - }), - takeWhile(value => value !== 1, true), - shareReplay(1), - ) - - constructor( - private readonly api: { getSetupStatus: () => Promise }, - ) { - super(subscriber => this.progress$.subscribe(subscriber)) - } -} diff --git a/web/projects/shared/src/types/api.ts b/web/projects/shared/src/types/api.ts index 3abfc7059..5a621d489 100644 --- a/web/projects/shared/src/types/api.ts +++ b/web/projects/shared/src/types/api.ts @@ -49,9 +49,3 @@ export type StartOSDiskInfo = { passwordHash: string | null wrappedKey: string | null } - -export interface SetupStatus { - bytesTransferred: number - totalBytes: number | null - complete: boolean -} diff --git a/web/projects/ui/src/app/services/api/embassy-api.service.ts b/web/projects/ui/src/app/services/api/embassy-api.service.ts index 0d42664d2..103620697 100644 --- a/web/projects/ui/src/app/services/api/embassy-api.service.ts +++ b/web/projects/ui/src/app/services/api/embassy-api.service.ts @@ -1,13 +1,12 @@ -import { Observable } from 'rxjs' -import { RR, BackupTargetType, Metrics } from './api.types' -import { SetupStatus } from '@start9labs/shared' -import { RPCOptions } from '@start9labs/shared' -import { T } from '@start9labs/start-sdk' import { GetPackageRes, GetPackagesRes, MarketplacePkg, } from '@start9labs/marketplace' +import { RPCOptions } from '@start9labs/shared' +import { T } from '@start9labs/start-sdk' +import { Observable } from 'rxjs' +import { BackupTargetType, RR } from './api.types' export abstract class ApiService { // http diff --git a/web/projects/ui/src/app/services/api/embassy-mock-api.service.ts b/web/projects/ui/src/app/services/api/embassy-mock-api.service.ts index 91279f0fc..19c46ea5e 100644 --- a/web/projects/ui/src/app/services/api/embassy-mock-api.service.ts +++ b/web/projects/ui/src/app/services/api/embassy-mock-api.service.ts @@ -2,7 +2,6 @@ import { Injectable } from '@angular/core' import { pauseFor, Log, - getSetupStatusMock, RPCErrorDetails, RPCOptions, } from '@start9labs/shared' diff --git a/web/projects/ui/src/polyfills.ts b/web/projects/ui/src/polyfills.ts index 6060f5df4..fe0eaed42 100644 --- a/web/projects/ui/src/polyfills.ts +++ b/web/projects/ui/src/polyfills.ts @@ -1,60 +1,5 @@ -/** - * This file includes polyfills needed by Angular and is loaded before the app. - * You can add your own extra polyfills to this file. - * - * This file is divided into 2 sections: - * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. - * 2. Application imports. Files imported after ZoneJS that should be loaded before your main - * file. - * - * The current setup is for so-called "evergreen" browsers; the last versions of browsers that - * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), - * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. - * - * Learn more in https://angular.io/guide/browser-support - */ +import { Buffer } from 'buffer' -/*************************************************************************************************** - * BROWSER POLYFILLS - */ - -/** IE10 and IE11 requires the following for NgClass support on SVG elements */ -// import 'classlist.js'; // Run `npm install --save classlist.js`. - -/** - * Web Animations `@angular/platform-browser/animations` - * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. - * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). - */ -// import 'web-animations-js'; // Run `npm install --save web-animations-js`. - -/** - * By default, zone.js will patch all possible macroTask and DomEvents - * user can disable parts of macroTask/DomEvents patch by setting following flags - * because those flags need to be set before `zone.js` being loaded, and webpack - * will put import in the top of bundle, so user need to create a separate file - * in this directory (for example: zone-flags.ts), and put the following flags - * into that file, and then add the following code before importing zone.js. - * import './zone-flags.ts'; - * - * The flags allowed in zone-flags.ts are listed here. - * - * The following flags will work for all browsers. - * - * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame - * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick - * (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames - * - * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js - * with the following flag, it will bypass `zone.js` patch for IE/Edge - * - * (window as any).__Zone_enable_cross_context_check = true; - * - */ - -// @TODO Alex include in angular.json ;(window as any).global = window ;(window as any).process = { env: { DEBUG: undefined }, browser: true } - -import { Buffer } from 'buffer' ;(window as any).Buffer = Buffer From 846189b15b763dbd0075c0b2901ecff640933df9 Mon Sep 17 00:00:00 2001 From: Matt Hill Date: Sat, 10 Aug 2024 05:57:33 -0600 Subject: [PATCH 2/3] address comments --- .../src/app/pages/recover.page.ts | 13 ++-- .../routes/diagnostic/diagnostic.module.ts | 12 ---- .../app/routes/diagnostic/home/home.page.ts | 66 +------------------ .../app/routes/diagnostic/logs/logs.page.ts | 6 +- .../ui/src/app/routes/loading/loading.page.ts | 1 - 5 files changed, 10 insertions(+), 88 deletions(-) diff --git a/web/projects/setup-wizard/src/app/pages/recover.page.ts b/web/projects/setup-wizard/src/app/pages/recover.page.ts index 9ba144af0..480e7269b 100644 --- a/web/projects/setup-wizard/src/app/pages/recover.page.ts +++ b/web/projects/setup-wizard/src/app/pages/recover.page.ts @@ -12,7 +12,7 @@ import { import { TuiCardLarge, TuiCell } from '@taiga-ui/layout' import { CIFS, CifsResponse } from 'src/app/components/cifs.component' import { ServerComponent } from 'src/app/components/server.component' -import { ApiService, StartOSDiskInfoWithId } from 'src/app/services/api.service' +import { ApiService, StartOSDiskInfoFull } from 'src/app/services/api.service' import { StateService } from 'src/app/services/state.service' @Component({ @@ -47,7 +47,7 @@ import { StateService } from 'src/app/services/state.service' @for (server of servers; track $index) { } @@ -76,7 +76,7 @@ export default class RecoverPage { private readonly stateService = inject(StateService) loading = true - servers: StartOSDiskInfoWithId[] = [] + servers: StartOSDiskInfoFull[] = [] async ngOnInit() { this.stateService.setupType = 'restore' @@ -111,15 +111,14 @@ export default class RecoverPage { } } - select(password: string, serverId: string) { + select(password: string, server: StartOSDiskInfoFull) { this.stateService.recoverySource = { type: 'backup', target: { type: 'disk', - // @TODO Matt where to get logicalname? - logicalname: '', + logicalname: server.partition.logicalname, }, - serverId, + serverId: server.id, password, } this.router.navigate(['storage']) diff --git a/web/projects/ui/src/app/routes/diagnostic/diagnostic.module.ts b/web/projects/ui/src/app/routes/diagnostic/diagnostic.module.ts index ddb8d4def..c18f0f011 100644 --- a/web/projects/ui/src/app/routes/diagnostic/diagnostic.module.ts +++ b/web/projects/ui/src/app/routes/diagnostic/diagnostic.module.ts @@ -1,11 +1,5 @@ import { NgModule } from '@angular/core' import { RouterModule, Routes } from '@angular/router' -import { WorkspaceConfig } from '@start9labs/shared' -import { DiagnosticService } from './services/diagnostic.service' -import { MockDiagnosticService } from './services/mock-diagnostic.service' -import { LiveDiagnosticService } from './services/live-diagnostic.service' - -const { useMocks } = require('../../../../../../config.json') as WorkspaceConfig const ROUTES: Routes = [ { @@ -22,11 +16,5 @@ const ROUTES: Routes = [ @NgModule({ imports: [RouterModule.forChild(ROUTES)], - providers: [ - { - provide: DiagnosticService, - useClass: useMocks ? MockDiagnosticService : LiveDiagnosticService, - }, - ], }) export class DiagnosticModule {} diff --git a/web/projects/ui/src/app/routes/diagnostic/home/home.page.ts b/web/projects/ui/src/app/routes/diagnostic/home/home.page.ts index 9040dc069..636568977 100644 --- a/web/projects/ui/src/app/routes/diagnostic/home/home.page.ts +++ b/web/projects/ui/src/app/routes/diagnostic/home/home.page.ts @@ -1,17 +1,10 @@ -<<<<<<<< HEAD:web/projects/ui/src/app/routes/diagnostic/home/home.page.ts import { TUI_CONFIRM } from '@taiga-ui/kit' import { Component, Inject } from '@angular/core' import { WINDOW } from '@ng-web-apis/common' import { LoadingService } from '@start9labs/shared' import { TuiDialogService } from '@taiga-ui/core' import { filter } from 'rxjs' -import { DiagnosticService } from '../services/diagnostic.service' -======== -import { Component } from '@angular/core' -import { AlertController } from '@ionic/angular' -import { LoadingService } from '@start9labs/shared' import { ApiService } from 'src/app/services/api/embassy-api.service' ->>>>>>>> 94a5075b6daa1375433420abf5d121171dae72cb:web/projects/ui/src/app/pages/diagnostic-routes/home/home.page.ts @Component({ selector: 'diagnostic-home', @@ -29,14 +22,9 @@ export class HomePage { constructor( private readonly loader: LoadingService, -<<<<<<<< HEAD:web/projects/ui/src/app/routes/diagnostic/home/home.page.ts - private readonly api: DiagnosticService, + private readonly api: ApiService, private readonly dialogs: TuiDialogService, @Inject(WINDOW) private readonly window: Window, -======== - private readonly api: ApiService, - private readonly alertCtrl: AlertController, ->>>>>>>> 94a5075b6daa1375433420abf5d121171dae72cb:web/projects/ui/src/app/pages/diagnostic-routes/home/home.page.ts ) {} async ngOnInit() { @@ -102,11 +90,7 @@ export class HomePage { } async restart(): Promise { -<<<<<<<< HEAD:web/projects/ui/src/app/routes/diagnostic/home/home.page.ts - const loader = this.loader.open('').subscribe() -======== const loader = this.loader.open('Loading...').subscribe() ->>>>>>>> 94a5075b6daa1375433420abf5d121171dae72cb:web/projects/ui/src/app/pages/diagnostic-routes/home/home.page.ts try { await this.api.diagnosticRestart() @@ -119,11 +103,7 @@ export class HomePage { } async forgetDrive(): Promise { -<<<<<<<< HEAD:web/projects/ui/src/app/routes/diagnostic/home/home.page.ts - const loader = this.loader.open('').subscribe() -======== const loader = this.loader.open('Loading...').subscribe() ->>>>>>>> 94a5075b6daa1375433420abf5d121171dae72cb:web/projects/ui/src/app/pages/diagnostic-routes/home/home.page.ts try { await this.api.diagnosticForgetDrive() @@ -136,31 +116,6 @@ export class HomePage { } } -<<<<<<<< HEAD:web/projects/ui/src/app/routes/diagnostic/home/home.page.ts - async presentAlertSystemRebuild() { - this.dialogs - .open(TUI_CONFIRM, { - label: 'Warning', - size: 's', - data: { - no: 'Cancel', - yes: 'Rebuild', - content: - '

This action will tear down all service containers and rebuild them from scratch. No data will be deleted.

A system rebuild can be useful if your system gets into a bad state, and it should only be performed if you are experiencing general performance or reliability issues.

It may take up to an hour to complete. During this time, you will lose all connectivity to your Start9 server.

', - }, - }) - .pipe(filter(Boolean)) - .subscribe(() => { - try { - this.systemRebuild() - } catch (e) { - console.error(e) - } - }) - } - -======== ->>>>>>>> 94a5075b6daa1375433420abf5d121171dae72cb:web/projects/ui/src/app/pages/diagnostic-routes/home/home.page.ts async presentAlertRepairDisk() { this.dialogs .open(TUI_CONFIRM, { @@ -187,27 +142,8 @@ export class HomePage { this.window.location.reload() } -<<<<<<<< HEAD:web/projects/ui/src/app/routes/diagnostic/home/home.page.ts - private async systemRebuild(): Promise { - const loader = this.loader.open('').subscribe() - - try { - await this.api.systemRebuild() - await this.api.restart() - this.restarted = true - } catch (e) { - console.error(e) - } finally { - loader.unsubscribe() - } - } - - private async repairDisk(): Promise { - const loader = this.loader.open('').subscribe() -======== private async repairDisk(): Promise { const loader = this.loader.open('Loading...').subscribe() ->>>>>>>> 94a5075b6daa1375433420abf5d121171dae72cb:web/projects/ui/src/app/pages/diagnostic-routes/home/home.page.ts try { await this.api.diagnosticRepairDisk() diff --git a/web/projects/ui/src/app/routes/diagnostic/logs/logs.page.ts b/web/projects/ui/src/app/routes/diagnostic/logs/logs.page.ts index fb3a3d76b..15c44adb7 100644 --- a/web/projects/ui/src/app/routes/diagnostic/logs/logs.page.ts +++ b/web/projects/ui/src/app/routes/diagnostic/logs/logs.page.ts @@ -2,7 +2,7 @@ import { Component, ElementRef, inject, OnInit, ViewChild } from '@angular/core' import { INTERSECTION_ROOT } from '@ng-web-apis/intersection-observer' import { convertAnsi, ErrorService } from '@start9labs/shared' import { TuiScrollbar } from '@taiga-ui/core' -import { DiagnosticService } from 'src/app/routes/diagnostic/services/diagnostic.service' +import { ApiService } from 'src/app/services/api/embassy-api.service' @Component({ selector: 'logs', @@ -28,7 +28,7 @@ import { DiagnosticService } from 'src/app/routes/diagnostic/services/diagnostic export class LogsPage implements OnInit { @ViewChild(TuiScrollbar, { read: ElementRef }) private readonly scrollbar?: ElementRef - private readonly api = inject(DiagnosticService) + private readonly api = inject(ApiService) private readonly errorService = inject(ErrorService) startCursor?: string @@ -59,7 +59,7 @@ export class LogsPage implements OnInit { this.loading = true try { - const response = await this.api.getLogs({ + const response = await this.api.diagnosticGetLogs({ cursor: this.startCursor, before: !!this.startCursor, limit: 200, diff --git a/web/projects/ui/src/app/routes/loading/loading.page.ts b/web/projects/ui/src/app/routes/loading/loading.page.ts index 2fee80fad..243a88eb6 100644 --- a/web/projects/ui/src/app/routes/loading/loading.page.ts +++ b/web/projects/ui/src/app/routes/loading/loading.page.ts @@ -5,7 +5,6 @@ import { provideSetupLogsService, provideSetupService, } from '@start9labs/shared' - import { ApiService } from 'src/app/services/api/embassy-api.service' @Component({ From 7850681ce188379d0bb1171277a058b937a20295 Mon Sep 17 00:00:00 2001 From: waterplea Date: Sun, 11 Aug 2024 00:41:26 +0400 Subject: [PATCH 3/3] chore: add back logs --- .../setup-wizard/src/app/app.module.ts | 7 +++++- .../src/app/pages/loading.page.ts | 1 + .../src/app/services/api.service.ts | 7 ++++++ .../src/app/services/live-api.service.ts | 13 +++++++++- .../src/app/services/mock-api.service.ts | 24 ++++++++++++++++++- .../initializing/initializing.component.ts | 2 +- 6 files changed, 50 insertions(+), 4 deletions(-) diff --git a/web/projects/setup-wizard/src/app/app.module.ts b/web/projects/setup-wizard/src/app/app.module.ts index fc23c15f2..cbfe318e6 100644 --- a/web/projects/setup-wizard/src/app/app.module.ts +++ b/web/projects/setup-wizard/src/app/app.module.ts @@ -2,7 +2,11 @@ import { HttpClientModule } from '@angular/common/http' import { NgModule } from '@angular/core' import { BrowserAnimationsModule } from '@angular/platform-browser/animations' import { PreloadAllModules, RouterModule } from '@angular/router' -import { RELATIVE_URL, WorkspaceConfig } from '@start9labs/shared' +import { + provideSetupLogsService, + RELATIVE_URL, + WorkspaceConfig, +} from '@start9labs/shared' import { tuiButtonOptionsProvider, TuiRoot } from '@taiga-ui/core' import { NG_EVENT_PLUGINS } from '@taiga-ui/event-plugins' import { ApiService } from 'src/app/services/api.service' @@ -29,6 +33,7 @@ const { ], providers: [ NG_EVENT_PLUGINS, + provideSetupLogsService(ApiService), tuiButtonOptionsProvider({ size: 'm' }), { provide: ApiService, diff --git a/web/projects/setup-wizard/src/app/pages/loading.page.ts b/web/projects/setup-wizard/src/app/pages/loading.page.ts index e146b9aa8..c367968d5 100644 --- a/web/projects/setup-wizard/src/app/pages/loading.page.ts +++ b/web/projects/setup-wizard/src/app/pages/loading.page.ts @@ -21,6 +21,7 @@ import { StateService } from 'src/app/services/state.service' @Component({ standalone: true, template: '', + styles: ':host { max-width: unset; align-items: stretch; }', imports: [InitializingComponent], changeDetection: ChangeDetectionStrategy.OnPush, }) diff --git a/web/projects/setup-wizard/src/app/services/api.service.ts b/web/projects/setup-wizard/src/app/services/api.service.ts index 882d656ae..912f2378f 100644 --- a/web/projects/setup-wizard/src/app/services/api.service.ts +++ b/web/projects/setup-wizard/src/app/services/api.service.ts @@ -2,6 +2,9 @@ import * as jose from 'node-jose' import { DiskInfo, DiskListResponse, + FollowLogsReq, + FollowLogsRes, + Log, PartitionInfo, StartOSDiskInfo, } from '@start9labs/shared' @@ -22,6 +25,10 @@ export abstract class ApiService { abstract execute(setupInfo: T.SetupExecuteParams): Promise // setup.execute abstract complete(): Promise // setup.complete abstract exit(): Promise // setup.exit + abstract followServerLogs(params: FollowLogsReq): Promise // setup.logs.follow + abstract openLogsWebsocket$( + config: WebSocketSubjectConfig, + ): Observable abstract openProgressWebsocket$(guid: string): Observable async encrypt(toEncrypt: string): Promise { diff --git a/web/projects/setup-wizard/src/app/services/live-api.service.ts b/web/projects/setup-wizard/src/app/services/live-api.service.ts index 161b611ca..797a8a176 100644 --- a/web/projects/setup-wizard/src/app/services/live-api.service.ts +++ b/web/projects/setup-wizard/src/app/services/live-api.service.ts @@ -3,8 +3,11 @@ import { Inject, Injectable } from '@angular/core' import { DiskListResponse, encodeBase64, + FollowLogsReq, + FollowLogsRes, HttpService, isRpcError, + Log, RpcError, RPCOptions, StartOSDiskInfo, @@ -12,7 +15,7 @@ import { import { T } from '@start9labs/start-sdk' import * as jose from 'node-jose' import { Observable } from 'rxjs' -import { webSocket } from 'rxjs/webSocket' +import { webSocket, WebSocketSubjectConfig } from 'rxjs/webSocket' import { ApiService } from './api.service' @Injectable({ @@ -96,6 +99,14 @@ export class LiveApiService extends ApiService { }) } + async followServerLogs(params: FollowLogsReq): Promise { + return this.rpcRequest({ method: 'setup.logs.follow', params }) + } + + openLogsWebsocket$({ url }: WebSocketSubjectConfig): Observable { + return webSocket(`http://start.local/ws/${url}`) + } + async complete(): Promise { const res = await this.rpcRequest({ method: 'setup.complete', diff --git a/web/projects/setup-wizard/src/app/services/mock-api.service.ts b/web/projects/setup-wizard/src/app/services/mock-api.service.ts index 6199e9c2a..ed8435ddf 100644 --- a/web/projects/setup-wizard/src/app/services/mock-api.service.ts +++ b/web/projects/setup-wizard/src/app/services/mock-api.service.ts @@ -2,12 +2,16 @@ import { Injectable } from '@angular/core' import { DiskListResponse, encodeBase64, + FollowLogsReq, + FollowLogsRes, + Log, pauseFor, StartOSDiskInfo, } from '@start9labs/shared' import { T } from '@start9labs/start-sdk' import * as jose from 'node-jose' -import { Observable, of } from 'rxjs' +import { interval, map, Observable, of } from 'rxjs' +import { WebSocketSubjectConfig } from 'rxjs/webSocket' import { ApiService } from './api.service' @Injectable({ @@ -266,6 +270,24 @@ export class MockApiService extends ApiService { } } + async followServerLogs(params: FollowLogsReq): Promise { + await pauseFor(1000) + return { + startCursor: 'fakestartcursor', + guid: 'fake-guid', + } + } + + openLogsWebsocket$(config: WebSocketSubjectConfig): Observable { + return interval(500).pipe( + map(() => ({ + timestamp: new Date().toISOString(), + message: 'fake log entry', + bootId: 'boot-id', + })), + ) + } + async complete(): Promise { await pauseFor(1000) return { diff --git a/web/projects/shared/src/components/initializing/initializing.component.ts b/web/projects/shared/src/components/initializing/initializing.component.ts index 524c80ff8..fd471cbb1 100644 --- a/web/projects/shared/src/components/initializing/initializing.component.ts +++ b/web/projects/shared/src/components/initializing/initializing.component.ts @@ -23,7 +23,7 @@ import { LogsWindowComponent } from './logs-window.component' >

{{ progress.message }}

- + `, styles: ` section {