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