mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-31 04:23:40 +00:00
Merge pull request #2668 from Start9Labs/fix/backup-create
solve infinite recursion and promise returning true
This commit is contained in:
94
web/package-lock.json
generated
94
web/package-lock.json
generated
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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')
|
||||||
}
|
}
|
||||||
@@ -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(
|
||||||
|
|||||||
@@ -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!)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user