many things

This commit is contained in:
Matt Hill
2021-10-13 09:11:05 -06:00
committed by Aiden McClelland
parent 7adb66cf4c
commit 9b8fccb431
18 changed files with 162 additions and 145 deletions

View File

@@ -8,7 +8,6 @@ import { PatchDbService } from '../services/patch-db/patch-db.service'
})
export class MaintenanceGuard implements CanActivate, CanActivateChild {
serverStatus: ServerStatus
isFullyDownloaded: boolean = false
constructor (
private readonly router: Router,
@@ -17,9 +16,6 @@ export class MaintenanceGuard implements CanActivate, CanActivateChild {
this.patch.watch$('server-info', 'status').subscribe(status => {
this.serverStatus = status
})
this.patch.watch$('server-info', 'update-progress').subscribe(progress => {
this.isFullyDownloaded = !!progress && (progress.size === progress.downloaded)
})
}
canActivate (): boolean {
@@ -31,7 +27,7 @@ export class MaintenanceGuard implements CanActivate, CanActivateChild {
}
private runServerStatusCheck (): boolean {
if (ServerStatus.BackingUp === this.serverStatus && !this.isFullyDownloaded) {
if (ServerStatus.BackingUp === this.serverStatus) {
this.router.navigate(['/maintenance'], { replaceUrl: true })
return false
} else {

View File

@@ -8,8 +8,6 @@ import { PatchDbService } from '../services/patch-db/patch-db.service'
})
export class UnmaintenanceGuard implements CanActivate {
serverStatus: ServerStatus
isFullyDownloaded: boolean = false
constructor (
private readonly router: Router,
@@ -18,13 +16,10 @@ export class UnmaintenanceGuard implements CanActivate {
this.patch.watch$('server-info', 'status').subscribe(status => {
this.serverStatus = status
})
this.patch.watch$('server-info', 'update-progress').subscribe(progress => {
this.isFullyDownloaded = !!progress && (progress.size === progress.downloaded)
})
}
canActivate (): boolean {
if (ServerStatus.BackingUp === this.serverStatus || this.isFullyDownloaded) {
if (ServerStatus.BackingUp === this.serverStatus) {
return true
} else {
this.router.navigate([''], { replaceUrl: true })

View File

@@ -6,7 +6,7 @@
</ion-button>
</ion-buttons>
<ion-title>Config</ion-title>
<ion-buttons *ngIf="!loadingText && hasConfig" slot="end" class="ion-padding-end">
<ion-buttons *ngIf="!loadingText && !loadingError && hasConfig" slot="end" class="ion-padding-end">
<ion-button fill="clear" (click)="resetDefaults()">
<ion-icon slot="start" name="refresh"></ion-icon>
Reset Defaults
@@ -23,62 +23,72 @@
<!-- not loading -->
<ng-template #loaded>
<ion-item *ngIf="hasConfig && !pkg.installed?.status.configured && !configForm.dirty">
<ion-item *ngIf="loadingError; else noError">
<ion-label>
<ion-text color="success">To use the default config for {{ pkg.manifest.title }}, click "Save" below.</ion-text>
<ion-text color="danger">
{{ loadingError }}
</ion-text>
</ion-label>
</ion-item>
<ng-container *ngIf="rec && showRec">
<ion-item class="rec-item">
<ng-template #noError>
<ion-item *ngIf="hasConfig && !pkg.installed.status.configured && !configForm.dirty">
<ion-label>
<h2 style="display: flex; align-items: center;">
<ion-icon size="small" style="margin: 4px" slot="start" color="primary" slot="start" name="ellipse"></ion-icon>
<ion-thumbnail style="width: 3vh; height: 3vh; margin: 0px 2px 0px 5px;" slot="start">
<img [src]="rec.dependentIcon" [alt]="rec.dependentTitle"/>
</ion-thumbnail>
<ion-text style="margin: 5px; font-family: 'Montserrat'; font-size: smaller;">{{ rec.dependentTitle }}</ion-text>
</h2>
<div style="margin: 7px 5px;">
<p style="font-size: small; color: var(--ion-color-medium)"> {{ pkg.manifest.title }} config has been modified to satisfy {{ rec.dependentTitle }}.
<ion-text color="dark">To accept the changes, click “Save” above.</ion-text>
</p>
<a style="font-size: small" *ngIf="!openRec" (click)="openRec = true">More Info</a>
<ng-container *ngIf="openRec">
<p style="margin-top: 10px; color: var(--ion-color-medium); font-size: small" [innerHTML]="rec.description"></p>
<a style="font-size: x-small; font-style: italic;" (click)="openRec = false">hide</a>
</ng-container>
<ion-button style="position: absolute; right: 0; top: 0" color="primary" fill="clear" (click)="dismissRec()">
<ion-icon name="close"></ion-icon>
</ion-button>
</div>
<ion-text color="success">To use the default config for {{ pkg.manifest.title }}, click "Save" below.</ion-text>
</ion-label>
</ion-item>
<ion-item-divider></ion-item-divider>
</ng-container>
<!-- no config -->
<ion-item *ngIf="!hasConfig">
<ion-label>
<p>No config options for {{ pkg.manifest.title }} {{ pkg.manifest.version }}.</p>
</ion-label>
</ion-item>
<!-- has config -->
<form *ngIf="hasConfig" [formGroup]="configForm" novalidate>
<form-object
[objectSpec]="configSpec"
[formGroup]="configForm"
[current]="current"
[showEdited]="true"
></form-object>
</form>
<ng-container *ngIf="rec && showRec">
<ion-item class="rec-item">
<ion-label>
<h2 style="display: flex; align-items: center;">
<ion-icon size="small" style="margin: 4px" slot="start" color="primary" slot="start" name="ellipse"></ion-icon>
<ion-thumbnail style="width: 3vh; height: 3vh; margin: 0px 2px 0px 5px;" slot="start">
<img [src]="rec.dependentIcon" [alt]="rec.dependentTitle"/>
</ion-thumbnail>
<ion-text style="margin: 5px; font-family: 'Montserrat'; font-size: smaller;">{{ rec.dependentTitle }}</ion-text>
</h2>
<div style="margin: 7px 5px;">
<p style="font-size: small; color: var(--ion-color-medium)"> {{ pkg.manifest.title }} config has been modified to satisfy {{ rec.dependentTitle }}.
<ion-text color="dark">To accept the changes, click “Save” above.</ion-text>
</p>
<a style="font-size: small" *ngIf="!openRec" (click)="openRec = true">More Info</a>
<ng-container *ngIf="openRec">
<p style="margin-top: 10px; color: var(--ion-color-medium); font-size: small" [innerHTML]="rec.description"></p>
<a style="font-size: x-small; font-style: italic;" (click)="openRec = false">hide</a>
</ng-container>
<ion-button style="position: absolute; right: 0; top: 0" color="primary" fill="clear" (click)="dismissRec()">
<ion-icon name="close"></ion-icon>
</ion-button>
</div>
</ion-label>
</ion-item>
<ion-item-divider></ion-item-divider>
</ng-container>
<!-- no config -->
<ion-item *ngIf="!hasConfig">
<ion-label>
<p>No config options for {{ pkg.manifest.title }} {{ pkg.manifest.version }}.</p>
</ion-label>
</ion-item>
<!-- has config -->
<form *ngIf="hasConfig" [formGroup]="configForm" novalidate>
<form-object
[objectSpec]="configSpec"
[formGroup]="configForm"
[current]="current"
[showEdited]="true"
></form-object>
</form>
</ng-template>
</ng-template>
</ion-content>
<ion-footer>
<ion-toolbar>
<ion-buttons *ngIf="!loadingText" slot="end" class="ion-padding-end">
<ion-buttons *ngIf="!loadingText && !loadingError" slot="end" class="ion-padding-end">
<ion-button *ngIf="hasConfig" fill="outline" [disabled]="saving" (click)="save()" class="enter-click" [class.no-click]="saving">
Save
</ion-button>

View File

@@ -1,5 +1,5 @@
import { Component, Input, ViewChild } from '@angular/core'
import { AlertController, ModalController, IonContent, LoadingController } from '@ionic/angular'
import { AlertController, ModalController, IonContent, LoadingController, IonicSafeString } from '@ionic/angular'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { isEmptyObject, isObject, Recommendation } from 'src/app/util/misc.util'
import { wizardModal } from 'src/app/components/install-wizard/install-wizard.component'
@@ -7,7 +7,7 @@ import { WizardBaker } from 'src/app/components/install-wizard/prebaked-wizards'
import { ConfigSpec } from 'src/app/pkg-config/config-types'
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
import { ErrorToastService } from 'src/app/services/error-toast.service'
import { ErrorToastService, getErrorMessage } from 'src/app/services/error-toast.service'
import { FormGroup } from '@angular/forms'
import { convertValuesRecursive, FormService } from 'src/app/services/form.service'
@@ -17,6 +17,7 @@ import { convertValuesRecursive, FormService } from 'src/app/services/form.servi
styleUrls: ['./app-config.page.scss'],
})
export class AppConfigPage {
@ViewChild(IonContent) content: IonContent
@Input() pkgId: string
@Input() rec: Recommendation | null = null
pkg: PackageDataEntry
@@ -26,11 +27,9 @@ export class AppConfigPage {
current: object
hasConfig = false
saving = false
showRec = true
openRec = false
@ViewChild(IonContent) content: IonContent
loadingError: string | IonicSafeString
constructor (
private readonly wizardBaker: WizardBaker,
@@ -59,7 +58,7 @@ export class AppConfigPage {
}
this.setConfig(spec, config, depConfig)
} catch (e) {
this.errToast.present(e)
this.loadingError = getErrorMessage(e)
} finally {
this.loadingText = undefined
}

View File

@@ -13,11 +13,7 @@
<ion-content class="ion-padding-top">
<!-- submitting -->
<text-spinner *ngIf="submitting" text="Initiating Backup"></text-spinner>
<!-- not submitting -->
<ion-item *ngIf="!submitting" class="ion-margin-bottom">
<ion-item class="ion-margin-bottom">
<ion-label>
<h2>
Select the drive containing the backup you would like to restore.
@@ -47,9 +43,20 @@
</ion-item>
</ng-container>
<!-- not loading and not submitting -->
<ion-content *ngIf="!loading && !submitting">
<ion-item-group>
<!-- not loading -->
<ng-container *ngIf="!loading">
<!-- error -->
<ion-item *ngIf="loadingError">
<ion-label>
<ion-text color="danger">
{{ loadingError }}
</ion-text>
</ion-label>
</ion-item>
<!-- no error -->
<ion-item-group *ngIf="!loadingError">
<div *ngFor="let disk of disks">
<ion-item-divider>{{ disk.logicalname }} - {{ disk.capacity | convertBytes }}</ion-item-divider>
<p class="item-subdivider" *ngIf="disk.vendor || disk.model">
@@ -66,5 +73,5 @@
</ion-item>
</div>
</ion-item-group>
</ion-content>
</ng-container>
</ion-content>

View File

@@ -1,10 +1,10 @@
import { Component, Input } from '@angular/core'
import { ModalController } from '@ionic/angular'
import { ModalController, IonicSafeString } from '@ionic/angular'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { GenericInputComponent } from 'src/app/modals/generic-input/generic-input.component'
import { DiskInfo } from 'src/app/services/api/api.types'
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
import { ErrorToastService } from 'src/app/services/error-toast.service'
import { getErrorMessage } from 'src/app/services/error-toast.service'
@Component({
selector: 'app-restore',
@@ -15,14 +15,13 @@ export class AppRestoreComponent {
@Input() pkgId: string
disks: DiskInfo[]
loading = true
submitting = false
allPartitionsMounted: boolean
modal: HTMLIonModalElement
loadingError: string | IonicSafeString
constructor (
private readonly modalCtrl: ModalController,
private readonly embassyApi: ApiService,
private readonly errToast: ErrorToastService,
public readonly patch: PatchDbService,
) { }
@@ -40,7 +39,7 @@ export class AppRestoreComponent {
try {
this.disks = await this.embassyApi.getDisks({ })
} catch (e) {
this.errToast.present(e)
this.loadingError = getErrorMessage(e)
} finally {
this.loading = false
}
@@ -54,7 +53,7 @@ export class AppRestoreComponent {
label: 'Password',
useMask: true,
buttonText: 'Restore',
submitFn: async (value: string) => await this.restore(logicalname, value),
submitFn: (value: string) => this.restore(logicalname, value),
},
cssClass: 'alertlike-modal',
presentingElement: await this.modalCtrl.getTop(),
@@ -73,17 +72,10 @@ export class AppRestoreComponent {
}
private async restore (logicalname: string, password: string): Promise<void> {
this.submitting = true
try {
await this.embassyApi.restorePackage({
id: this.pkgId,
logicalname,
password,
})
} catch (e) {
this.errToast.present(e)
} finally {
this.submitting = false
}
await this.embassyApi.restorePackage({
id: this.pkgId,
logicalname,
password,
})
}
}

View File

@@ -1,5 +1,6 @@
import { Component, Input } from '@angular/core'
import { ModalController } from '@ionic/angular'
import { ModalController, IonicSafeString, LoadingController } from '@ionic/angular'
import { getErrorMessage } from 'src/app/services/error-toast.service'
@Component({
selector: 'generic-input',
@@ -15,12 +16,14 @@ export class GenericInputComponent {
@Input() nullable = false
@Input() useMask = false
@Input() value = ''
@Input() loadingText = ''
@Input() submitFn: (value: string) => Promise<any>
unmasked = false
error: string
error: string | IonicSafeString
constructor (
private readonly modalCtrl: ModalController,
private readonly loadingCtrl: LoadingController,
) { }
toggleMask () {
@@ -33,7 +36,22 @@ export class GenericInputComponent {
async submit () {
// @TODO validate input?
await this.submitFn(this.value)
this.modalCtrl.dismiss(undefined, 'success')
const loader = await this.loadingCtrl.create({
spinner: 'lines',
cssClass: 'loader',
message: this.loadingText,
})
await loader.present()
try {
await this.submitFn(this.value)
this.modalCtrl.dismiss(undefined, 'success')
} catch (e) {
this.error = getErrorMessage(e)
}
finally {
loader.dismiss()
}
}
}

View File

@@ -13,6 +13,17 @@
<text-spinner *ngIf="loading; else loaded" [text]="'Loading ' + title | titlecase"></text-spinner>
<ng-template #loaded>
<div *ngIf="content" class="content-padding" [innerHTML]="content | markdown"></div>
<ion-item *ngIf="loadingError; else noError">
<ion-label>
<ion-text color="danger">
{{ loadingError }}
</ion-text>
</ion-label>
</ion-item>
<ng-template #noError>
<div class="content-padding" [innerHTML]="content | markdown"></div>
</ng-template>
</ng-template>
</ion-content>

View File

@@ -1,7 +1,7 @@
import { Component, Input } from '@angular/core'
import { ModalController } from '@ionic/angular'
import { ModalController, IonicSafeString } from '@ionic/angular'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { ErrorToastService } from 'src/app/services/error-toast.service'
import { getErrorMessage } from 'src/app/services/error-toast.service'
@Component({
selector: 'markdown',
@@ -13,10 +13,10 @@ export class MarkdownPage {
@Input() title: string
content: string
loading = true
loadingError: string | IonicSafeString
constructor (
private readonly modalCtrl: ModalController,
private readonly errToast: ErrorToastService,
private readonly embassyApi: ApiService,
) { }
@@ -32,7 +32,7 @@ export class MarkdownPage {
}
}
} catch (e) {
this.errToast.present(e)
this.loadingError = getErrorMessage(e)
} finally {
this.loading = false
}

View File

@@ -180,8 +180,8 @@ export class AppListPage {
private watchPkgs (): Observable<DataModel['package-data']> {
return this.patch.watch$('package-data')
.pipe(
filter(obj => {
return Object.keys(obj).length !== this.pkgs.length
filter(pkgs => {
return Object.keys(pkgs).length !== this.pkgs.length
}),
tap(pkgs => {
const ids = Object.keys(pkgs)
@@ -194,6 +194,8 @@ export class AppListPage {
}
})
this.empty = !this.pkgs.length
ids.forEach(id => {
// if already exists, return
const pkg = this.pkgs.find(p => p.entry.manifest.id === id)
@@ -223,7 +225,7 @@ export class AppListPage {
const statuses = renderPkgStatus(update)
const primaryRendering = PrimaryRendering[statuses.primary]
pkgInfo.entry = update
pkgInfo.installProgress = !isEmptyObject(pkg['install-progress']) ? this.pkgLoading.transform(pkg['install-progress']) : undefined
pkgInfo.installProgress = !isEmptyObject(update['install-progress']) ? this.pkgLoading.transform(update['install-progress']) : undefined
pkgInfo.primaryRendering = primaryRendering
pkgInfo.error = statuses.health === HealthStatus.Failure || [DependencyStatus.Issue, DependencyStatus.Critical].includes(statuses.dependency)
})

View File

@@ -118,9 +118,9 @@
</ion-item>
</ng-container>
<!-- @TODO better maintenance messaging -->
<ng-template #maintenance>
<!-- <ng-template #maintenance>
Service is undergoing maintenance.
</ng-template>
</ng-template> -->
</ng-container>
</ion-item-group>

View File

@@ -70,6 +70,12 @@ export class AppShowPage {
// 1
this.patch.watch$('package-data', this.pkgId)
.subscribe(pkg => {
// if package disappears, navigate to list page
if (!pkg) {
this.navCtrl.navigateRoot('/services')
return
}
this.pkg = pkg
this.installProgress = !isEmptyObject(pkg['install-progress']) ? this.packageLoadingService.transform(pkg['install-progress']) : undefined
this.statuses = renderPkgStatus(pkg)

View File

@@ -1,6 +1,6 @@
import { Component, ViewChild } from '@angular/core'
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
import { IonContent, LoadingController, ModalController } from '@ionic/angular'
import { IonContent, ModalController } from '@ionic/angular'
import { GenericInputComponent } from 'src/app/modals/generic-input/generic-input.component'
import { ConfigSpec } from 'src/app/pkg-config/config-types'
import { ApiService } from 'src/app/services/api/embassy-api.service'
@@ -18,8 +18,6 @@ export class PreferencesPage {
constructor (
private readonly modalCtrl: ModalController,
private readonly loadingCtrl: LoadingController,
private readonly errToast: ErrorToastService,
private readonly api: ApiService,
public readonly patch: PatchDbService,
) { }
@@ -43,7 +41,8 @@ export class PreferencesPage {
nullable: true,
value: this.patch.data.ui.name,
buttonText: 'Save',
submitFn: async (value: string) => await this.setDbValue('name', value || this.defaultName),
loadingText: 'Saving',
submitFn: (value: string) => this.setDbValue('name', value || this.defaultName),
},
cssClass: 'alertlike-modal',
presentingElement: await this.modalCtrl.getTop(),
@@ -54,20 +53,7 @@ export class PreferencesPage {
}
private async setDbValue (key: string, value: string): Promise<void> {
const loader = await this.loadingCtrl.create({
spinner: 'lines',
message: 'Saving...',
cssClass: 'loader',
})
await loader.present()
try {
await this.api.setDbValue({ pointer: `/${key}`, value })
} catch (e) {
this.errToast.present(e)
} finally {
loader.dismiss()
}
await this.api.setDbValue({ pointer: `/${key}`, value })
}
}

View File

@@ -46,7 +46,8 @@ export class SSHKeysPage {
title: name,
message: description,
label: name,
submitFn: async (pk: string) => await this.add(pk),
loadingText: 'Saving',
submitFn: (pk: string) => this.add(pk),
},
cssClass: 'alertlike-modal',
})
@@ -54,21 +55,8 @@ export class SSHKeysPage {
}
async add (pubkey: string): Promise<void> {
const loader = await this.loadingCtrl.create({
spinner: 'lines',
message: 'Saving...',
cssClass: 'loader',
})
await loader.present()
try {
const key = await this.embassyApi.addSshKey({ key: pubkey })
this.sshKeys.push(key)
} catch (e) {
this.errToast.present(e)
} finally {
loader.dismiss()
}
const key = await this.embassyApi.addSshKey({ key: pubkey })
this.sshKeys.push(key)
}
async presentAlertDelete (i: number) {

View File

@@ -38,7 +38,16 @@
<!-- loaded -->
<ng-container *ngIf="!loading">
<ion-item-group>
<!-- error -->
<ion-item *ngIf="loadingError">
<ion-label>
<ion-text color="danger">
{{ loadingError }}
</ion-text>
</ion-label>
</ion-item>
<ion-item-group *ngIf="!loadingError">
<div *ngFor="let disk of disks">
<ion-item-divider>{{ disk.logicalname }} - {{ disk.capacity | convertBytes }}</ion-item-divider>
<p class="item-subdivider" *ngIf="disk.vendor || disk.model">

View File

@@ -1,9 +1,9 @@
import { Component } from '@angular/core'
import { ModalController } from '@ionic/angular'
import { ModalController, IonicSafeString } from '@ionic/angular'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { GenericInputComponent } from 'src/app/modals/generic-input/generic-input.component'
import { DiskInfo } from 'src/app/services/api/api.types'
import { ErrorToastService } from 'src/app/services/error-toast.service'
import { getErrorMessage } from 'src/app/services/error-toast.service'
@Component({
selector: 'server-backup',
@@ -14,11 +14,11 @@ export class ServerBackupPage {
disks: DiskInfo[]
loading = true
allPartitionsMounted: boolean
loadingError: string | IonicSafeString
constructor (
private readonly modalCtrl: ModalController,
private readonly embassyApi: ApiService,
private readonly errToast: ErrorToastService,
) { }
ngOnInit () {
@@ -34,7 +34,7 @@ export class ServerBackupPage {
try {
this.disks = await this.embassyApi.getDisks({ })
} catch (e) {
this.errToast.present(e)
this.loadingError = getErrorMessage(e)
} finally {
this.loading = false
}

View File

@@ -47,13 +47,12 @@ export class ErrorToastService {
export function getErrorMessage (e: RequestError, link?: string): string | IonicSafeString {
let message: string | IonicSafeString
if (e.code) message = String(e.code)
if (e.message) message = `${message ? message + ' ' : ''}${e.message}`
if (e.details) message = `${message ? message + ': ' : ''}${e.details}`
if (!message) {
message = 'Unknown Error.'
link = 'https://docs.start9.com'
link = 'https://docs.start9.com/support/FAQ/index.html'
}
if (link) {

View File

@@ -400,7 +400,6 @@ export function convertValuesRecursive (configSpec: ConfigSpec, group: FormGroup
} else if (valueSpec.subtype === 'string') {
formArr.controls.forEach(control => {
if (!control.value) control.setValue(null)
control.setValue(control.value ? Number(control.value) : null)
})
} else if (valueSpec.subtype === 'object') {
formArr.controls.forEach((formGroup: FormGroup) => {