mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 10:21:52 +00:00
better backup reports
This commit is contained in:
committed by
Aiden McClelland
parent
502fdeea78
commit
54a65e465a
@@ -103,6 +103,7 @@
|
||||
<ion-icon name="refresh"></ion-icon>
|
||||
<ion-icon name="reload"></ion-icon>
|
||||
<ion-icon name="remove"></ion-icon>
|
||||
<ion-icon name="remove-circle-outline"></ion-icon>
|
||||
<ion-icon name="save-outline"></ion-icon>
|
||||
<ion-icon name="shield-checkmark-outline"></ion-icon>
|
||||
<ion-icon name="storefront-outline"></ion-icon>
|
||||
|
||||
14
ui/src/app/modals/backup-report/backup-report.module.ts
Normal file
14
ui/src/app/modals/backup-report/backup-report.module.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { NgModule } from '@angular/core'
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { IonicModule } from '@ionic/angular'
|
||||
import { BackupReportPage } from './backup-report.page'
|
||||
|
||||
@NgModule({
|
||||
declarations: [BackupReportPage],
|
||||
imports: [
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
],
|
||||
exports: [BackupReportPage],
|
||||
})
|
||||
export class BackupReportPageModule { }
|
||||
30
ui/src/app/modals/backup-report/backup-report.page.html
Normal file
30
ui/src/app/modals/backup-report/backup-report.page.html
Normal file
@@ -0,0 +1,30 @@
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Backup Report</ion-title>
|
||||
<ion-buttons slot="end">
|
||||
<ion-button (click)="dismiss()">
|
||||
<ion-icon slot="icon-only" name="close"></ion-icon>
|
||||
</ion-button>
|
||||
</ion-buttons>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content>
|
||||
<ion-item-group>
|
||||
<ion-item-divider>Completed: {{ timestamp | date : 'short' }}</ion-item-divider>
|
||||
<ion-item>
|
||||
<ion-label>
|
||||
<h2>System data</h2>
|
||||
<p><ion-text [color]="system.color">{{ system.result }}</ion-text></p>
|
||||
</ion-label>
|
||||
<ion-icon slot="end" [name]="system.icon" [color]="system.color"></ion-icon>
|
||||
</ion-item>
|
||||
<ion-item *ngFor="let pkg of report.packages | keyvalue">
|
||||
<ion-label>
|
||||
<h2>{{ pkg.key }}</h2>
|
||||
<p><ion-text [color]="pkg.value.error ? 'danger' : 'success'">{{ pkg.value.error ? 'Failed: ' + pkg.value.error : 'Succeeded' }}</ion-text></p>
|
||||
</ion-label>
|
||||
<ion-icon slot="end" [name]="pkg.value.error ? 'remove-circle-outline' : 'checkmark'" [color]="pkg.value.error ? 'danger' : 'success'"></ion-icon>
|
||||
</ion-item>
|
||||
</ion-item-group>
|
||||
</ion-content>
|
||||
48
ui/src/app/modals/backup-report/backup-report.page.ts
Normal file
48
ui/src/app/modals/backup-report/backup-report.page.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { Component, Input } from '@angular/core'
|
||||
import { ModalController } from '@ionic/angular'
|
||||
import { BackupReport } from 'src/app/services/api/api.types'
|
||||
|
||||
@Component({
|
||||
selector: 'backup-report',
|
||||
templateUrl: './backup-report.page.html',
|
||||
styleUrls: ['./backup-report.page.scss'],
|
||||
})
|
||||
export class BackupReportPage {
|
||||
@Input() report: BackupReport
|
||||
@Input() timestamp: string
|
||||
system: {
|
||||
result: string
|
||||
icon: 'remove' | 'remove-circle-outline' | 'checkmark'
|
||||
color: 'dark' | 'danger' | 'success'
|
||||
}
|
||||
|
||||
constructor (
|
||||
private readonly modalCtrl: ModalController,
|
||||
) { }
|
||||
|
||||
ngOnInit () {
|
||||
if (!this.report.server.attempted) {
|
||||
this.system = {
|
||||
result: 'Not Attempted',
|
||||
icon: 'remove',
|
||||
color: 'dark',
|
||||
}
|
||||
} else if (this.report.server.error) {
|
||||
this.system = {
|
||||
result: `Failed: ${this.report.server.error}`,
|
||||
icon: 'remove-circle-outline',
|
||||
color: 'danger',
|
||||
}
|
||||
} else {
|
||||
this.system = {
|
||||
result: 'Succeeded',
|
||||
icon: 'checkmark',
|
||||
color: 'success',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async dismiss () {
|
||||
return this.modalCtrl.dismiss(true)
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<ion-title>{{ title | titlecase }}</ion-title>
|
||||
<ion-buttons slot="end">
|
||||
<ion-button (click)="dismiss()">
|
||||
<ion-icon slot="icon-only" name="close"></ion-icon>
|
||||
</ion-button>
|
||||
</ion-buttons>
|
||||
<ion-title>{{ title | titlecase }}</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
import { NgModule } from '@angular/core'
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { Routes, RouterModule } from '@angular/router'
|
||||
import { IonicModule } from '@ionic/angular'
|
||||
import { AppInstructionsPage } from './app-instructions.page'
|
||||
import { SharingModule } from 'src/app/modules/sharing.module'
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: AppInstructionsPage,
|
||||
},
|
||||
]
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
RouterModule.forChild(routes),
|
||||
SharingModule,
|
||||
],
|
||||
declarations: [
|
||||
AppInstructionsPage,
|
||||
],
|
||||
})
|
||||
export class AppInstructionsPageModule { }
|
||||
@@ -1,16 +0,0 @@
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<pwa-back-button></pwa-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title>Instructions</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content class="ion-padding">
|
||||
<text-spinner *ngIf="loading; else loaded" text="Loading Instructions"></text-spinner>
|
||||
|
||||
<ng-template #loaded>
|
||||
<div *ngIf="instructions" class="instuctions-padding" [innerHTML]="instructions | markdown"></div>
|
||||
</ng-template>
|
||||
</ion-content>
|
||||
@@ -1,3 +0,0 @@
|
||||
.instructions-padding {
|
||||
padding: 0 16px 16px 16px
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
import { Component, ViewChild } from '@angular/core'
|
||||
import { ActivatedRoute } from '@angular/router'
|
||||
import { IonContent } from '@ionic/angular'
|
||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { ErrorToastService } from 'src/app/services/error-toast.service'
|
||||
|
||||
@Component({
|
||||
selector: 'app-instructions',
|
||||
templateUrl: './app-instructions.page.html',
|
||||
styleUrls: ['./app-instructions.page.scss'],
|
||||
})
|
||||
export class AppInstructionsPage {
|
||||
instructions: string
|
||||
loading = true
|
||||
|
||||
@ViewChild(IonContent) content: IonContent
|
||||
|
||||
constructor (
|
||||
private readonly route: ActivatedRoute,
|
||||
private readonly errToast: ErrorToastService,
|
||||
private readonly embassyApi: ApiService,
|
||||
private readonly patch: PatchDbService,
|
||||
) { }
|
||||
|
||||
async ngOnInit () {
|
||||
const pkgId = this.route.snapshot.paramMap.get('pkgId')
|
||||
|
||||
const url = this.patch.getData()['package-data'][pkgId]['static-files'].instructions
|
||||
|
||||
try {
|
||||
this.instructions = await this.embassyApi.getStatic(url)
|
||||
} catch (e) {
|
||||
this.errToast.present(e)
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import { StatusComponentModule } from 'src/app/components/status/status.componen
|
||||
import { SharingModule } from 'src/app/modules/sharing.module'
|
||||
import { InstallWizardComponentModule } from 'src/app/components/install-wizard/install-wizard.component.module'
|
||||
import { AppConfigPageModule } from 'src/app/modals/app-config/app-config.module'
|
||||
import { MarkdownPageModule } from 'src/app/modals/markdown/markdown.module'
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
@@ -24,6 +25,7 @@ const routes: Routes = [
|
||||
InstallWizardComponentModule,
|
||||
AppConfigPageModule,
|
||||
SharingModule,
|
||||
MarkdownPageModule,
|
||||
],
|
||||
declarations: [AppShowPage],
|
||||
})
|
||||
|
||||
@@ -15,6 +15,7 @@ import { ErrorToastService } from 'src/app/services/error-toast.service'
|
||||
import { AppConfigPage } from 'src/app/modals/app-config/app-config.page'
|
||||
import { PackageLoadingService, ProgressData } from 'src/app/services/package-loading.service'
|
||||
import { filter } from 'rxjs/operators'
|
||||
import { MarkdownPage } from 'src/app/modals/markdown/markdown.page'
|
||||
|
||||
@Component({
|
||||
selector: 'app-show',
|
||||
@@ -211,6 +212,18 @@ export class AppShowPage {
|
||||
await modal.present()
|
||||
}
|
||||
|
||||
async presentModalInstructions () {
|
||||
const modal = await this.modalCtrl.create({
|
||||
componentProps: {
|
||||
title: 'Instructions',
|
||||
contentUrl: this.pkg['static-files']['instructions'],
|
||||
},
|
||||
component: MarkdownPage,
|
||||
})
|
||||
|
||||
await modal.present()
|
||||
}
|
||||
|
||||
private setDepValues (id: string, errors: { [id: string]: DependencyError }): DependencyInfo {
|
||||
let errorText = ''
|
||||
let actionText = 'View'
|
||||
@@ -330,7 +343,7 @@ export class AppShowPage {
|
||||
this.buttons = [
|
||||
// instructions
|
||||
{
|
||||
action: () => this.navCtrl.navigateForward(['instructions'], { relativeTo: this.route }),
|
||||
action: () => this.presentModalInstructions(),
|
||||
title: 'Instructions',
|
||||
description: `Understand how to use ${pkgTitle}`,
|
||||
icon: 'list-outline',
|
||||
|
||||
@@ -19,10 +19,6 @@ const routes: Routes = [
|
||||
path: ':pkgId/actions',
|
||||
loadChildren: () => import('./app-actions/app-actions.module').then(m => m.AppActionsPageModule),
|
||||
},
|
||||
{
|
||||
path: ':pkgId/instructions',
|
||||
loadChildren: () => import('./app-instructions/app-instructions.module').then(m => m.AppInstructionsPageModule),
|
||||
},
|
||||
{
|
||||
path: ':pkgId/interfaces',
|
||||
loadChildren: () => import('./app-interfaces/app-interfaces.module').then(m => m.AppInterfacesPageModule),
|
||||
|
||||
@@ -5,6 +5,7 @@ import { RouterModule, Routes } from '@angular/router'
|
||||
import { NotificationsPage } from './notifications.page'
|
||||
import { BadgeMenuComponentModule } from 'src/app/components/badge-menu-button/badge-menu.component.module'
|
||||
import { SharingModule } from 'src/app/modules/sharing.module'
|
||||
import { BackupReportPageModule } from 'src/app/modals/backup-report/backup-report.module'
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
@@ -20,6 +21,7 @@ const routes: Routes = [
|
||||
RouterModule.forChild(routes),
|
||||
BadgeMenuComponentModule,
|
||||
SharingModule,
|
||||
BackupReportPageModule,
|
||||
],
|
||||
declarations: [NotificationsPage],
|
||||
})
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
<h2 class="notification-message">
|
||||
{{ not.message }}
|
||||
<a *ngIf="not.code === 1" style="text-decoration: none; cursor: pointer;" (click)="viewBackupReport(not)">
|
||||
View Report
|
||||
- View Report
|
||||
</a>
|
||||
</h2>
|
||||
<p>
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { Component } from '@angular/core'
|
||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { ServerNotification, ServerNotifications } from 'src/app/services/api/api.types'
|
||||
import { AlertController, LoadingController, AlertButton } from '@ionic/angular'
|
||||
import { LoadingController, ModalController } from '@ionic/angular'
|
||||
import { ActivatedRoute } from '@angular/router'
|
||||
import { ErrorToastService } from 'src/app/services/error-toast.service'
|
||||
import { BackupReportPage } from 'src/app/modals/backup-report/backup-report.page'
|
||||
|
||||
@Component({
|
||||
selector: 'notifications',
|
||||
@@ -21,8 +22,8 @@ export class NotificationsPage {
|
||||
constructor (
|
||||
private readonly embassyApi: ApiService,
|
||||
private readonly loadingCtrl: LoadingController,
|
||||
private readonly modalCtrl: ModalController,
|
||||
private readonly errToast: ErrorToastService,
|
||||
private readonly alertCtrl: AlertController,
|
||||
private readonly route: ActivatedRoute,
|
||||
) { }
|
||||
|
||||
@@ -90,40 +91,14 @@ export class NotificationsPage {
|
||||
}
|
||||
|
||||
async viewBackupReport (notification: ServerNotification<1>) {
|
||||
const data = notification.data
|
||||
|
||||
const embassyFailed = !!data.server.error
|
||||
const packagesFailed = Object.values(data.packages).some(val => val.error)
|
||||
|
||||
let message: string
|
||||
|
||||
if (embassyFailed || packagesFailed) {
|
||||
message = 'There was an issue backing up one or more items. Click "Retry" to retry ONLY the items that failed.'
|
||||
} else {
|
||||
message = 'All items were successfully backed up'
|
||||
}
|
||||
|
||||
const buttons: AlertButton[] = [
|
||||
{
|
||||
text: 'Dismiss',
|
||||
role: 'cancel',
|
||||
const modal = await this.modalCtrl.create({
|
||||
component: BackupReportPage,
|
||||
componentProps: {
|
||||
report: notification.data,
|
||||
timestamp: notification['created-at'],
|
||||
},
|
||||
]
|
||||
|
||||
if (embassyFailed || packagesFailed) {
|
||||
buttons.push({
|
||||
text: 'Retry',
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
const alert = await this.alertCtrl.create({
|
||||
header: 'Backup Report',
|
||||
message,
|
||||
buttons,
|
||||
})
|
||||
|
||||
await alert.present()
|
||||
await modal.present()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -740,12 +740,12 @@ export module Mock {
|
||||
message: 'Embassy and services have been successfully backed up.',
|
||||
data: {
|
||||
server: {
|
||||
attempted: true,
|
||||
attempted: false,
|
||||
error: null,
|
||||
},
|
||||
packages: {
|
||||
'bitcoind': {
|
||||
error: null,
|
||||
error: 'An error ocurred while backing up',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -27,8 +27,6 @@ export class PatchDbService {
|
||||
private patchSub: Subscription
|
||||
data: DataModel
|
||||
|
||||
getData () { return this.patchDb.store.cache.data }
|
||||
|
||||
constructor (
|
||||
@Inject(PATCH_SOURCE) private readonly source: Source<DataModel>,
|
||||
@Inject(PATCH_HTTP) private readonly http: ApiService,
|
||||
|
||||
Reference in New Issue
Block a user