Merge pull request #2668 from Start9Labs/fix/backup-create

solve infinite recursion and promise returning true
This commit is contained in:
Matt Hill
2024-07-12 11:16:00 -06:00
committed by GitHub
11 changed files with 174 additions and 116 deletions

94
web/package-lock.json generated
View File

@@ -28,12 +28,12 @@
"@start9labs/argon2": "^0.2.2", "@start9labs/argon2": "^0.2.2",
"@start9labs/emver": "^0.1.5", "@start9labs/emver": "^0.1.5",
"@start9labs/start-sdk": "file:../sdk/dist", "@start9labs/start-sdk": "file:../sdk/dist",
"@taiga-ui/addon-charts": "3.84.0", "@taiga-ui/addon-charts": "3.86.0",
"@taiga-ui/cdk": "3.84.0", "@taiga-ui/cdk": "3.86.0",
"@taiga-ui/core": "3.84.0", "@taiga-ui/core": "3.86.0",
"@taiga-ui/experimental": "3.84.0", "@taiga-ui/experimental": "3.86.0",
"@taiga-ui/icons": "3.84.0", "@taiga-ui/icons": "3.86.0",
"@taiga-ui/kit": "3.84.0", "@taiga-ui/kit": "3.86.0",
"@tinkoff/ng-dompurify": "4.0.0", "@tinkoff/ng-dompurify": "4.0.0",
"@tinkoff/ng-event-plugins": "3.2.0", "@tinkoff/ng-event-plugins": "3.2.0",
"angular-svg-round-progressbar": "^9.0.0", "angular-svg-round-progressbar": "^9.0.0",
@@ -4128,9 +4128,9 @@
} }
}, },
"node_modules/@taiga-ui/addon-charts": { "node_modules/@taiga-ui/addon-charts": {
"version": "3.84.0", "version": "3.86.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-charts/-/addon-charts-3.84.0.tgz", "resolved": "https://registry.npmjs.org/@taiga-ui/addon-charts/-/addon-charts-3.86.0.tgz",
"integrity": "sha512-XR7UFywnrv4NRLHOCbba63gXDYYDL4Rt0MbjnF54p5U2EXnbt2of7VbjlB6cPx40XkQqfqa3CNayYxWZP82Ijg==", "integrity": "sha512-Du/85qqaj8hpFSI6hPuFeIhtE93Z6WSkYZLt0gvnsaCb2qSAg8D4oHSogrtF1rsWGGoM+fvXjD7UEUw9GzFIPg==",
"dependencies": { "dependencies": {
"tslib": "^2.6.2" "tslib": "^2.6.2"
}, },
@@ -4138,15 +4138,15 @@
"@angular/common": ">=12.0.0", "@angular/common": ">=12.0.0",
"@angular/core": ">=12.0.0", "@angular/core": ">=12.0.0",
"@ng-web-apis/common": "^3.0.6", "@ng-web-apis/common": "^3.0.6",
"@taiga-ui/cdk": "^3.84.0", "@taiga-ui/cdk": "^3.86.0",
"@taiga-ui/core": "^3.84.0", "@taiga-ui/core": "^3.86.0",
"@tinkoff/ng-polymorpheus": "^4.3.0" "@tinkoff/ng-polymorpheus": "^4.3.0"
} }
}, },
"node_modules/@taiga-ui/addon-commerce": { "node_modules/@taiga-ui/addon-commerce": {
"version": "3.84.0", "version": "3.86.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-commerce/-/addon-commerce-3.84.0.tgz", "resolved": "https://registry.npmjs.org/@taiga-ui/addon-commerce/-/addon-commerce-3.86.0.tgz",
"integrity": "sha512-1zqLwnZLAYYcHvjH89d7JmtV2+QeZ2YnSJ3YWEMNLjGPzpev4RvQXtDfglIyu0LCyTxqpXmuzes9v/cgq2P5TQ==", "integrity": "sha512-8QSB490ckI4jnU+1sQ3x8os2GVE162hbvzPVYIZ0TruoeXl076dAz6PT2WRaFwjcaCAIGsuaQgQ4Cv02NjkiYQ==",
"peer": true, "peer": true,
"dependencies": { "dependencies": {
"tslib": "^2.6.2" "tslib": "^2.6.2"
@@ -4159,18 +4159,18 @@
"@maskito/core": "^1.9.0", "@maskito/core": "^1.9.0",
"@maskito/kit": "^1.9.0", "@maskito/kit": "^1.9.0",
"@ng-web-apis/common": "^3.0.6", "@ng-web-apis/common": "^3.0.6",
"@taiga-ui/cdk": "^3.84.0", "@taiga-ui/cdk": "^3.86.0",
"@taiga-ui/core": "^3.84.0", "@taiga-ui/core": "^3.86.0",
"@taiga-ui/i18n": "^3.84.0", "@taiga-ui/i18n": "^3.86.0",
"@taiga-ui/kit": "^3.84.0", "@taiga-ui/kit": "^3.86.0",
"@tinkoff/ng-polymorpheus": "^4.3.0", "@tinkoff/ng-polymorpheus": "^4.3.0",
"rxjs": ">=6.0.0" "rxjs": ">=6.0.0"
} }
}, },
"node_modules/@taiga-ui/cdk": { "node_modules/@taiga-ui/cdk": {
"version": "3.84.0", "version": "3.86.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/cdk/-/cdk-3.84.0.tgz", "resolved": "https://registry.npmjs.org/@taiga-ui/cdk/-/cdk-3.86.0.tgz",
"integrity": "sha512-0umw/CUmYNEYOCUNQVTQS53zXzxZsH/6+lj1mFVzocvfJFJWAUT6ltCH9QvxYmxSDDGWwNGg16AaVo2K+aGL0w==", "integrity": "sha512-aVbnW01Oh0Er1sHKVGHP8W05mOSKxjSzFE3Qx4iF4T6KW7Rlz9HZoNx5ADMg0TATYChtWh9Kwjo8I4LSVj2ZUw==",
"dependencies": { "dependencies": {
"@ng-web-apis/common": "3.0.6", "@ng-web-apis/common": "3.0.6",
"@ng-web-apis/mutation-observer": "3.1.0", "@ng-web-apis/mutation-observer": "3.1.0",
@@ -4221,11 +4221,11 @@
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
}, },
"node_modules/@taiga-ui/core": { "node_modules/@taiga-ui/core": {
"version": "3.84.0", "version": "3.86.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/core/-/core-3.84.0.tgz", "resolved": "https://registry.npmjs.org/@taiga-ui/core/-/core-3.86.0.tgz",
"integrity": "sha512-FZy77z0E4qjYcszVcp+qPFkPwJPl8qXZb7t2P+juUtJvSmSn2foQHHdyhbIYN808H26tqCdgkTMG1BWQxVuDSg==", "integrity": "sha512-diQKOnPtDDfxPOMk6wLRq8nyDVfNSPSNy+1TeyqzUgOvJ6XAjfaBXGsL3iuR7AN8+sz/b3rJmBce+vdw6FjMLQ==",
"dependencies": { "dependencies": {
"@taiga-ui/i18n": "^3.84.0", "@taiga-ui/i18n": "^3.86.0",
"tslib": "^2.6.2" "tslib": "^2.6.2"
}, },
"peerDependencies": { "peerDependencies": {
@@ -4237,35 +4237,35 @@
"@angular/router": ">=12.0.0", "@angular/router": ">=12.0.0",
"@ng-web-apis/common": "^3.0.6", "@ng-web-apis/common": "^3.0.6",
"@ng-web-apis/mutation-observer": "^3.1.0", "@ng-web-apis/mutation-observer": "^3.1.0",
"@taiga-ui/cdk": "^3.84.0", "@taiga-ui/cdk": "^3.86.0",
"@taiga-ui/i18n": "^3.84.0", "@taiga-ui/i18n": "^3.86.0",
"@tinkoff/ng-event-plugins": "^3.2.0", "@tinkoff/ng-event-plugins": "^3.2.0",
"@tinkoff/ng-polymorpheus": "^4.3.0", "@tinkoff/ng-polymorpheus": "^4.3.0",
"rxjs": ">=6.0.0" "rxjs": ">=6.0.0"
} }
}, },
"node_modules/@taiga-ui/experimental": { "node_modules/@taiga-ui/experimental": {
"version": "3.84.0", "version": "3.86.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/experimental/-/experimental-3.84.0.tgz", "resolved": "https://registry.npmjs.org/@taiga-ui/experimental/-/experimental-3.86.0.tgz",
"integrity": "sha512-q0hNVy+EmywCG8hpZlg/+haKIFhnmxicQiSeV/D1P7CHO10safjGo0ptT6e1hYMFa5/cJZOM4OwDPen2xs17Wg==", "integrity": "sha512-ACjoRVeX5MgsNJsiu2ukliXLD2mfEWm8Vtmk78vqcnkyPUmy1ZWK4sG3p5ybFN8AdIMHkblVq0l+x2qAwr/+LQ==",
"dependencies": { "dependencies": {
"tslib": "^2.6.2" "tslib": "^2.6.2"
}, },
"peerDependencies": { "peerDependencies": {
"@angular/common": ">=12.0.0", "@angular/common": ">=12.0.0",
"@angular/core": ">=12.0.0", "@angular/core": ">=12.0.0",
"@taiga-ui/addon-commerce": "^3.84.0", "@taiga-ui/addon-commerce": "^3.86.0",
"@taiga-ui/cdk": "^3.84.0", "@taiga-ui/cdk": "^3.86.0",
"@taiga-ui/core": "^3.84.0", "@taiga-ui/core": "^3.86.0",
"@taiga-ui/kit": "^3.84.0", "@taiga-ui/kit": "^3.86.0",
"@tinkoff/ng-polymorpheus": "^4.3.0", "@tinkoff/ng-polymorpheus": "^4.3.0",
"rxjs": ">=6.0.0" "rxjs": ">=6.0.0"
} }
}, },
"node_modules/@taiga-ui/i18n": { "node_modules/@taiga-ui/i18n": {
"version": "3.85.0", "version": "3.86.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/i18n/-/i18n-3.85.0.tgz", "resolved": "https://registry.npmjs.org/@taiga-ui/i18n/-/i18n-3.86.0.tgz",
"integrity": "sha512-CGoxfq9WY+psX5ZOfWmuQZ6OA/0CAPYJTlbHkw5sRKAyhEQ3NM/Wbx3xcwrcYRRJDnt9yOlfibz+3a+WDF2bFA==", "integrity": "sha512-8zkNhMo/QtxZ2Zp6EP/nxo4SOLwaIrX+P3X/Wt+1cjFNZUYWWfdvfHLLdNviKFPVl4RAOxvkhDfza/wkrwv+iQ==",
"dependencies": { "dependencies": {
"tslib": "^2.6.2" "tslib": "^2.6.2"
}, },
@@ -4276,20 +4276,20 @@
} }
}, },
"node_modules/@taiga-ui/icons": { "node_modules/@taiga-ui/icons": {
"version": "3.84.0", "version": "3.86.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/icons/-/icons-3.84.0.tgz", "resolved": "https://registry.npmjs.org/@taiga-ui/icons/-/icons-3.86.0.tgz",
"integrity": "sha512-KiH7BJRZ6wbkOHlJAS0XHq2gYnQTpRgdEogKW+GoD0da/4trCdM66vhDk2j0DwDFdBGq5U0inHJCjnskBI1nSQ==", "integrity": "sha512-jVBEbvE/r9JG+knmXMTn/l/js3JjYi8nSGbrLCryJZZoS2izRnQARN2txABieUJm8H463CoF0rcdXlHKRuA4Ew==",
"dependencies": { "dependencies": {
"tslib": "^2.6.2" "tslib": "^2.6.2"
}, },
"peerDependencies": { "peerDependencies": {
"@taiga-ui/cdk": "^3.84.0" "@taiga-ui/cdk": "^3.86.0"
} }
}, },
"node_modules/@taiga-ui/kit": { "node_modules/@taiga-ui/kit": {
"version": "3.84.0", "version": "3.86.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/kit/-/kit-3.84.0.tgz", "resolved": "https://registry.npmjs.org/@taiga-ui/kit/-/kit-3.86.0.tgz",
"integrity": "sha512-lSUPDco5FeBYK3ESnXeEPLCdMCmNXwcdHNK/we+0ZoH4VPx/OGg2hpEP0Fej7jfGHwXFTzDbufQD0hT6WlfTAw==", "integrity": "sha512-naAy4pyhCaQ9+vWxqSMjbV+9KwnMxT5ybrw+MAJgMn2evzRq0FjqzyFZFog7oiRbRvgVdoWPQfBNKaaLhJcpsw==",
"dependencies": { "dependencies": {
"@maskito/angular": "1.9.0", "@maskito/angular": "1.9.0",
"@maskito/core": "1.9.0", "@maskito/core": "1.9.0",
@@ -4306,9 +4306,9 @@
"@ng-web-apis/common": "3.0.6", "@ng-web-apis/common": "3.0.6",
"@ng-web-apis/mutation-observer": "^3.1.0", "@ng-web-apis/mutation-observer": "^3.1.0",
"@ng-web-apis/resize-observer": "^3.0.6", "@ng-web-apis/resize-observer": "^3.0.6",
"@taiga-ui/cdk": "^3.84.0", "@taiga-ui/cdk": "^3.86.0",
"@taiga-ui/core": "^3.84.0", "@taiga-ui/core": "^3.86.0",
"@taiga-ui/i18n": "^3.84.0", "@taiga-ui/i18n": "^3.86.0",
"@tinkoff/ng-polymorpheus": "^4.3.0", "@tinkoff/ng-polymorpheus": "^4.3.0",
"rxjs": ">=6.0.0" "rxjs": ">=6.0.0"
} }

View File

@@ -51,12 +51,12 @@
"@start9labs/argon2": "^0.2.2", "@start9labs/argon2": "^0.2.2",
"@start9labs/emver": "^0.1.5", "@start9labs/emver": "^0.1.5",
"@start9labs/start-sdk": "file:../sdk/dist", "@start9labs/start-sdk": "file:../sdk/dist",
"@taiga-ui/addon-charts": "3.84.0", "@taiga-ui/addon-charts": "3.86.0",
"@taiga-ui/cdk": "3.84.0", "@taiga-ui/cdk": "3.86.0",
"@taiga-ui/core": "3.84.0", "@taiga-ui/core": "3.86.0",
"@taiga-ui/experimental": "3.84.0", "@taiga-ui/experimental": "3.86.0",
"@taiga-ui/icons": "3.84.0", "@taiga-ui/icons": "3.86.0",
"@taiga-ui/kit": "3.84.0", "@taiga-ui/kit": "3.86.0",
"@tinkoff/ng-dompurify": "4.0.0", "@tinkoff/ng-dompurify": "4.0.0",
"@tinkoff/ng-event-plugins": "3.2.0", "@tinkoff/ng-event-plugins": "3.2.0",
"angular-svg-round-progressbar": "^9.0.0", "angular-svg-round-progressbar": "^9.0.0",

View File

@@ -15,7 +15,6 @@ export class ErrorService extends ErrorHandler {
this.alerts this.alerts
.open(getErrorMessage(error, link), { .open(getErrorMessage(error, link), {
label: 'Error', label: 'Error',
autoClose: false,
status: TuiNotification.Error, status: TuiNotification.Error,
}) })
.subscribe() .subscribe()

View File

@@ -61,7 +61,7 @@ export class BackupService {
) )
} }
async hasThisBackup(target: BackupTarget, id: string): Promise<boolean> { hasThisBackup(target: BackupTarget, id: string): boolean {
return ( return (
target.startOs[id] && target.startOs[id] &&
this.emver.compare(target.startOs[id].version, '0.3.6') !== -1 this.emver.compare(target.startOs[id].version, '0.3.6') !== -1

View File

@@ -32,7 +32,7 @@ export class BadgeMenuComponent {
constructor( constructor(
private readonly splitPane: SplitPaneTracker, private readonly splitPane: SplitPaneTracker,
private readonly patch: PatchDB<DataModel>, private readonly patch: PatchDB<DataModel>,
private readonly dialog: TuiDialogService, private readonly dialogs: TuiDialogService,
private readonly clientStorageService: ClientStorageService, private readonly clientStorageService: ClientStorageService,
) {} ) {}
@@ -44,6 +44,6 @@ export class BadgeMenuComponent {
} }
onWidgets() { onWidgets() {
this.dialog.open(WIDGETS_COMPONENT, { label: 'Widgets' }).subscribe() this.dialogs.open(WIDGETS_COMPONENT, { label: 'Widgets' }).subscribe()
} }
} }

View File

@@ -11,6 +11,7 @@
<span *ngIf="spec.required">*</span> <span *ngIf="spec.required">*</span>
<select <select
tuiSelect tuiSelect
[placeholder]="spec.name"
[items]="items" [items]="items"
[disabledItemHandler]="disabledItemHandler" [disabledItemHandler]="disabledItemHandler"
></select> ></select>

View File

@@ -6,6 +6,10 @@ import {
LoadingService, LoadingService,
StartOSDiskInfo, StartOSDiskInfo,
} from '@start9labs/shared' } from '@start9labs/shared'
import {
PasswordPromptComponent,
PromptOptions,
} from 'src/app/modals/password-prompt.component'
import { import {
BackupInfo, BackupInfo,
CifsBackupTarget, CifsBackupTarget,
@@ -14,7 +18,6 @@ import {
import { ApiService } from 'src/app/services/api/embassy-api.service' import { ApiService } from 'src/app/services/api/embassy-api.service'
import { MappedBackupTarget } from 'src/app/types/mapped-backup-target' import { MappedBackupTarget } from 'src/app/types/mapped-backup-target'
import { AppRecoverSelectPage } from '../app-recover-select/app-recover-select.page' import { AppRecoverSelectPage } from '../app-recover-select/app-recover-select.page'
import { PasswordPromptModal } from './password-prompt.modal'
@Component({ @Component({
selector: 'backup-server-select', selector: 'backup-server-select',
@@ -38,23 +41,35 @@ export class BackupServerSelectModal {
async presentModalPassword( async presentModalPassword(
serverId: string, serverId: string,
server: StartOSDiskInfo, { passwordHash }: StartOSDiskInfo,
): Promise<void> { ): Promise<void> {
const options: PromptOptions = {
title: 'Password Required',
message:
'Enter the password that was used to encrypt this backup. On the next screen, you will select the individual services you want to restore.',
label: 'Decrypt Backup',
placeholder: 'Enter password',
buttonText: 'Next',
}
const modal = await this.modalCtrl.create({ const modal = await this.modalCtrl.create({
component: PasswordPromptModal, component: PasswordPromptComponent,
componentProps: { options },
canDismiss: async password => {
if (password === null) {
return true
}
try {
argon2.verify(passwordHash!, password)
await this.restoreFromBackup(serverId, password)
return true
} catch (e: any) {
this.errorService.handleError(e)
return false
}
},
}) })
modal.present() modal.present()
const { data, role } = await modal.onWillDismiss()
if (role === 'confirm') {
try {
argon2.verify(server.passwordHash!, data)
await this.restoreFromBackup(serverId, data)
} catch (e: any) {
this.errorService.handleError(e)
}
}
} }
private async restoreFromBackup( private async restoreFromBackup(

View File

@@ -1,14 +1,29 @@
import { Component } from '@angular/core' import {
AfterViewInit,
Component,
ElementRef,
Input,
ViewChild,
} from '@angular/core'
import { FormsModule } from '@angular/forms' import { FormsModule } from '@angular/forms'
import { IonicModule, ModalController } from '@ionic/angular' import { IonicModule, ModalController } from '@ionic/angular'
import { TuiTextfieldComponent } from '@taiga-ui/core'
import { TuiInputPasswordModule } from '@taiga-ui/kit' import { TuiInputPasswordModule } from '@taiga-ui/kit'
export interface PromptOptions {
title: string
message: string
label: string
placeholder: string
buttonText: string
}
@Component({ @Component({
standalone: true, standalone: true,
template: ` template: `
<ion-header> <ion-header>
<ion-toolbar> <ion-toolbar>
<ion-title>Decrypt Backup</ion-title> <ion-title>{{ options.title }}</ion-title>
<ion-buttons slot="end"> <ion-buttons slot="end">
<ion-button (click)="cancel()"> <ion-button (click)="cancel()">
<ion-icon slot="icon-only" name="close"></ion-icon> <ion-icon slot="icon-only" name="close"></ion-icon>
@@ -18,13 +33,11 @@ import { TuiInputPasswordModule } from '@taiga-ui/kit'
</ion-header> </ion-header>
<ion-content class="ion-padding"> <ion-content class="ion-padding">
<p>{{ options.message }}</p>
<p> <p>
Enter the password that was used to encrypt this backup. On the next <tui-input-password [(ngModel)]="password" (keydown.enter)="confirm()">
screen, you will select the individual services you want to restore. {{ options.label }}
</p> <input tuiTextfield [placeholder]="options.placeholder" />
<p>
<tui-input-password [(ngModel)]="password">
Enter password
</tui-input-password> </tui-input-password>
</p> </p>
</ion-content> </ion-content>
@@ -47,18 +60,30 @@ import { TuiInputPasswordModule } from '@taiga-ui/kit'
[disabled]="!password" [disabled]="!password"
(click)="confirm()" (click)="confirm()"
> >
Next {{ options.buttonText }}
</ion-button> </ion-button>
</ion-toolbar> </ion-toolbar>
</ion-footer> </ion-footer>
`, `,
imports: [IonicModule, FormsModule, TuiInputPasswordModule], imports: [IonicModule, FormsModule, TuiInputPasswordModule],
}) })
export class PasswordPromptModal { export class PasswordPromptComponent implements AfterViewInit {
@ViewChild(TuiTextfieldComponent, { read: ElementRef })
input?: ElementRef<HTMLInputElement>
@Input()
options!: PromptOptions
password = '' password = ''
constructor(private modalCtrl: ModalController) {} constructor(private modalCtrl: ModalController) {}
ngAfterViewInit() {
setTimeout(() => {
this.input?.nativeElement.focus({ preventScroll: true })
}, 300)
}
cancel() { cancel() {
return this.modalCtrl.dismiss(null, 'cancel') return this.modalCtrl.dismiss(null, 'cancel')
} }

View File

@@ -1,11 +1,13 @@
import { Component } from '@angular/core' import { Component } from '@angular/core'
import { ModalController, NavController } from '@ionic/angular' import { ModalController, NavController } from '@ionic/angular'
import { LoadingService } from '@start9labs/shared' import { ErrorService, LoadingService } from '@start9labs/shared'
import { TuiDialogService } from '@taiga-ui/core' import {
import { PROMPT, PromptOptions } from 'src/app/modals/prompt.component' PasswordPromptComponent,
PromptOptions,
} from 'src/app/modals/password-prompt.component'
import { ApiService } from 'src/app/services/api/embassy-api.service' import { ApiService } from 'src/app/services/api/embassy-api.service'
import { PatchDB } from 'patch-db-client' import { PatchDB } from 'patch-db-client'
import { skip, take, takeUntil } from 'rxjs/operators' import { skip, takeUntil } from 'rxjs/operators'
import { MappedBackupTarget } from 'src/app/types/mapped-backup-target' import { MappedBackupTarget } from 'src/app/types/mapped-backup-target'
import * as argon2 from '@start9labs/argon2' import * as argon2 from '@start9labs/argon2'
import { TuiDestroyService } from '@taiga-ui/cdk' import { TuiDestroyService } from '@taiga-ui/cdk'
@@ -22,7 +24,6 @@ import { BackupService } from 'src/app/components/backup-drives/backup.service'
@Component({ @Component({
selector: 'server-backup', selector: 'server-backup',
templateUrl: './server-backup.page.html', templateUrl: './server-backup.page.html',
styleUrls: ['./server-backup.page.scss'],
providers: [TuiDestroyService], providers: [TuiDestroyService],
}) })
export class ServerBackupPage { export class ServerBackupPage {
@@ -31,8 +32,8 @@ export class ServerBackupPage {
readonly backingUp$ = this.eosService.backingUp$ readonly backingUp$ = this.eosService.backingUp$
constructor( constructor(
private readonly errorService: ErrorService,
private readonly loader: LoadingService, private readonly loader: LoadingService,
private readonly dialogs: TuiDialogService,
private readonly modalCtrl: ModalController, private readonly modalCtrl: ModalController,
private readonly embassyApi: ApiService, private readonly embassyApi: ApiService,
private readonly navCtrl: NavController, private readonly navCtrl: NavController,
@@ -60,7 +61,7 @@ export class ServerBackupPage {
component: BackupSelectPage, component: BackupSelectPage,
}) })
modal.onWillDismiss().then(res => { modal.onDidDismiss().then(res => {
if (res.data) { if (res.data) {
this.serviceIds = res.data this.serviceIds = res.data
this.presentModalPassword(target) this.presentModalPassword(target)
@@ -74,28 +75,35 @@ export class ServerBackupPage {
target: MappedBackupTarget<CifsBackupTarget | DiskBackupTarget>, target: MappedBackupTarget<CifsBackupTarget | DiskBackupTarget>,
): Promise<void> { ): Promise<void> {
const options: PromptOptions = { const options: PromptOptions = {
title: 'Master Password Needed',
message: 'Enter your master password to encrypt this backup.', message: 'Enter your master password to encrypt this backup.',
label: 'Master Password', label: 'Master Password',
placeholder: 'Enter master password', placeholder: 'Enter master password',
useMask: true,
buttonText: 'Create Backup', buttonText: 'Create Backup',
} }
this.dialogs const modal = await this.modalCtrl.create({
.open<string>(PROMPT, { component: PasswordPromptComponent,
label: 'Master Password Needed', componentProps: { options },
data: options, canDismiss: async password => {
}) if (password === null) {
.pipe(take(1)) return true
.subscribe(async (password: string) => { }
const { passwordHash, id } = await getServerInfo(this.patch) const { passwordHash, id } = await getServerInfo(this.patch)
// confirm password matches current master password // confirm password matches current master password
argon2.verify(passwordHash, password) try {
argon2.verify(passwordHash, password)
} catch (e: any) {
this.errorService.handleError(e)
return false
}
// first time backup // first time backup
if (!this.backupService.hasThisBackup(target.entry, id)) { if (!this.backupService.hasThisBackup(target.entry, id)) {
await this.createBackup(target, password) this.createBackup(target, password)
return true
// existing backup // existing backup
} else { } else {
try { try {
@@ -103,41 +111,51 @@ export class ServerBackupPage {
} catch { } catch {
setTimeout( setTimeout(
() => this.presentModalOldPassword(target, password), () => this.presentModalOldPassword(target, password),
500, 250,
) )
return return true
} }
await this.createBackup(target, password) await this.createBackup(target, password)
return true
} }
}) },
})
modal.present()
} }
private async presentModalOldPassword( private async presentModalOldPassword(
target: MappedBackupTarget<CifsBackupTarget | DiskBackupTarget>, target: MappedBackupTarget<CifsBackupTarget | DiskBackupTarget>,
password: string, password: string,
): Promise<void> { ): Promise<void> {
const { id } = await getServerInfo(this.patch)
const options: PromptOptions = { const options: PromptOptions = {
title: 'Original Password Needed',
message: message:
'This backup was created with a different password. Enter the ORIGINAL password that was used to encrypt this backup.', 'This backup was created with a different password. Enter the ORIGINAL password that was used to encrypt this backup.',
label: 'Original Password', label: 'Original Password',
placeholder: 'Enter original password', placeholder: 'Enter original password',
useMask: true,
buttonText: 'Create Backup', buttonText: 'Create Backup',
} }
const { id } = await getServerInfo(this.patch) const modal = await this.modalCtrl.create({
component: PasswordPromptComponent,
componentProps: { options },
canDismiss: async oldPassword => {
if (oldPassword === null) {
return true
}
this.dialogs try {
.open<string>(PROMPT, { argon2.verify(target.entry.startOs[id].passwordHash!, oldPassword)
label: 'Original Password Needed', await this.createBackup(target, password, oldPassword)
data: options, return true
}) } catch (e: any) {
.pipe(take(1)) this.errorService.handleError(e)
.subscribe(async (oldPassword: string) => { return false
const passwordHash = target.entry.startOs[id].passwordHash! }
argon2.verify(passwordHash, oldPassword) },
await this.createBackup(target, password, oldPassword) })
}) modal.present()
} }
private async createBackup( private async createBackup(

View File

@@ -56,7 +56,7 @@ export class WidgetsPage {
@Optional() @Optional()
@Inject(POLYMORPHEUS_CONTEXT) @Inject(POLYMORPHEUS_CONTEXT)
readonly context: TuiDialogContext | null, readonly context: TuiDialogContext | null,
private readonly dialog: TuiDialogService, private readonly dialogs: TuiDialogService,
private readonly patch: PatchDB<DataModel>, private readonly patch: PatchDB<DataModel>,
private readonly cdr: ChangeDetectorRef, private readonly cdr: ChangeDetectorRef,
private readonly api: ApiService, private readonly api: ApiService,
@@ -83,7 +83,7 @@ export class WidgetsPage {
} }
add() { add() {
this.dialog.open(ADD_WIDGET, { label: 'Add widget' }).subscribe(widget => { this.dialogs.open(ADD_WIDGET, { label: 'Add widget' }).subscribe(widget => {
this.addWidget(widget!) this.addWidget(widget!)
}) })
} }