mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 20:14:49 +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="refresh"></ion-icon>
|
||||||
<ion-icon name="reload"></ion-icon>
|
<ion-icon name="reload"></ion-icon>
|
||||||
<ion-icon name="remove"></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="save-outline"></ion-icon>
|
||||||
<ion-icon name="shield-checkmark-outline"></ion-icon>
|
<ion-icon name="shield-checkmark-outline"></ion-icon>
|
||||||
<ion-icon name="storefront-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-header>
|
||||||
<ion-toolbar>
|
<ion-toolbar>
|
||||||
<ion-buttons slot="start">
|
<ion-title>{{ title | titlecase }}</ion-title>
|
||||||
|
<ion-buttons slot="end">
|
||||||
<ion-button (click)="dismiss()">
|
<ion-button (click)="dismiss()">
|
||||||
<ion-icon slot="icon-only" name="close"></ion-icon>
|
<ion-icon slot="icon-only" name="close"></ion-icon>
|
||||||
</ion-button>
|
</ion-button>
|
||||||
</ion-buttons>
|
</ion-buttons>
|
||||||
<ion-title>{{ title | titlecase }}</ion-title>
|
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</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 { SharingModule } from 'src/app/modules/sharing.module'
|
||||||
import { InstallWizardComponentModule } from 'src/app/components/install-wizard/install-wizard.component.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 { AppConfigPageModule } from 'src/app/modals/app-config/app-config.module'
|
||||||
|
import { MarkdownPageModule } from 'src/app/modals/markdown/markdown.module'
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
@@ -24,6 +25,7 @@ const routes: Routes = [
|
|||||||
InstallWizardComponentModule,
|
InstallWizardComponentModule,
|
||||||
AppConfigPageModule,
|
AppConfigPageModule,
|
||||||
SharingModule,
|
SharingModule,
|
||||||
|
MarkdownPageModule,
|
||||||
],
|
],
|
||||||
declarations: [AppShowPage],
|
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 { AppConfigPage } from 'src/app/modals/app-config/app-config.page'
|
||||||
import { PackageLoadingService, ProgressData } from 'src/app/services/package-loading.service'
|
import { PackageLoadingService, ProgressData } from 'src/app/services/package-loading.service'
|
||||||
import { filter } from 'rxjs/operators'
|
import { filter } from 'rxjs/operators'
|
||||||
|
import { MarkdownPage } from 'src/app/modals/markdown/markdown.page'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-show',
|
selector: 'app-show',
|
||||||
@@ -211,6 +212,18 @@ export class AppShowPage {
|
|||||||
await modal.present()
|
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 {
|
private setDepValues (id: string, errors: { [id: string]: DependencyError }): DependencyInfo {
|
||||||
let errorText = ''
|
let errorText = ''
|
||||||
let actionText = 'View'
|
let actionText = 'View'
|
||||||
@@ -330,7 +343,7 @@ export class AppShowPage {
|
|||||||
this.buttons = [
|
this.buttons = [
|
||||||
// instructions
|
// instructions
|
||||||
{
|
{
|
||||||
action: () => this.navCtrl.navigateForward(['instructions'], { relativeTo: this.route }),
|
action: () => this.presentModalInstructions(),
|
||||||
title: 'Instructions',
|
title: 'Instructions',
|
||||||
description: `Understand how to use ${pkgTitle}`,
|
description: `Understand how to use ${pkgTitle}`,
|
||||||
icon: 'list-outline',
|
icon: 'list-outline',
|
||||||
|
|||||||
@@ -19,10 +19,6 @@ const routes: Routes = [
|
|||||||
path: ':pkgId/actions',
|
path: ':pkgId/actions',
|
||||||
loadChildren: () => import('./app-actions/app-actions.module').then(m => m.AppActionsPageModule),
|
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',
|
path: ':pkgId/interfaces',
|
||||||
loadChildren: () => import('./app-interfaces/app-interfaces.module').then(m => m.AppInterfacesPageModule),
|
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 { NotificationsPage } from './notifications.page'
|
||||||
import { BadgeMenuComponentModule } from 'src/app/components/badge-menu-button/badge-menu.component.module'
|
import { BadgeMenuComponentModule } from 'src/app/components/badge-menu-button/badge-menu.component.module'
|
||||||
import { SharingModule } from 'src/app/modules/sharing.module'
|
import { SharingModule } from 'src/app/modules/sharing.module'
|
||||||
|
import { BackupReportPageModule } from 'src/app/modals/backup-report/backup-report.module'
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
@@ -20,6 +21,7 @@ const routes: Routes = [
|
|||||||
RouterModule.forChild(routes),
|
RouterModule.forChild(routes),
|
||||||
BadgeMenuComponentModule,
|
BadgeMenuComponentModule,
|
||||||
SharingModule,
|
SharingModule,
|
||||||
|
BackupReportPageModule,
|
||||||
],
|
],
|
||||||
declarations: [NotificationsPage],
|
declarations: [NotificationsPage],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -66,7 +66,7 @@
|
|||||||
<h2 class="notification-message">
|
<h2 class="notification-message">
|
||||||
{{ not.message }}
|
{{ not.message }}
|
||||||
<a *ngIf="not.code === 1" style="text-decoration: none; cursor: pointer;" (click)="viewBackupReport(not)">
|
<a *ngIf="not.code === 1" style="text-decoration: none; cursor: pointer;" (click)="viewBackupReport(not)">
|
||||||
View Report
|
- View Report
|
||||||
</a>
|
</a>
|
||||||
</h2>
|
</h2>
|
||||||
<p>
|
<p>
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { Component } from '@angular/core'
|
import { Component } from '@angular/core'
|
||||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||||
import { ServerNotification, ServerNotifications } from 'src/app/services/api/api.types'
|
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 { ActivatedRoute } from '@angular/router'
|
||||||
import { ErrorToastService } from 'src/app/services/error-toast.service'
|
import { ErrorToastService } from 'src/app/services/error-toast.service'
|
||||||
|
import { BackupReportPage } from 'src/app/modals/backup-report/backup-report.page'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'notifications',
|
selector: 'notifications',
|
||||||
@@ -21,8 +22,8 @@ export class NotificationsPage {
|
|||||||
constructor (
|
constructor (
|
||||||
private readonly embassyApi: ApiService,
|
private readonly embassyApi: ApiService,
|
||||||
private readonly loadingCtrl: LoadingController,
|
private readonly loadingCtrl: LoadingController,
|
||||||
|
private readonly modalCtrl: ModalController,
|
||||||
private readonly errToast: ErrorToastService,
|
private readonly errToast: ErrorToastService,
|
||||||
private readonly alertCtrl: AlertController,
|
|
||||||
private readonly route: ActivatedRoute,
|
private readonly route: ActivatedRoute,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
@@ -90,40 +91,14 @@ export class NotificationsPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async viewBackupReport (notification: ServerNotification<1>) {
|
async viewBackupReport (notification: ServerNotification<1>) {
|
||||||
const data = notification.data
|
const modal = await this.modalCtrl.create({
|
||||||
|
component: BackupReportPage,
|
||||||
const embassyFailed = !!data.server.error
|
componentProps: {
|
||||||
const packagesFailed = Object.values(data.packages).some(val => val.error)
|
report: notification.data,
|
||||||
|
timestamp: notification['created-at'],
|
||||||
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',
|
|
||||||
},
|
},
|
||||||
]
|
|
||||||
|
|
||||||
if (embassyFailed || packagesFailed) {
|
|
||||||
buttons.push({
|
|
||||||
text: 'Retry',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const alert = await this.alertCtrl.create({
|
|
||||||
header: 'Backup Report',
|
|
||||||
message,
|
|
||||||
buttons,
|
|
||||||
})
|
})
|
||||||
|
await modal.present()
|
||||||
await alert.present()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -740,12 +740,12 @@ export module Mock {
|
|||||||
message: 'Embassy and services have been successfully backed up.',
|
message: 'Embassy and services have been successfully backed up.',
|
||||||
data: {
|
data: {
|
||||||
server: {
|
server: {
|
||||||
attempted: true,
|
attempted: false,
|
||||||
error: null,
|
error: null,
|
||||||
},
|
},
|
||||||
packages: {
|
packages: {
|
||||||
'bitcoind': {
|
'bitcoind': {
|
||||||
error: null,
|
error: 'An error ocurred while backing up',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -27,8 +27,6 @@ export class PatchDbService {
|
|||||||
private patchSub: Subscription
|
private patchSub: Subscription
|
||||||
data: DataModel
|
data: DataModel
|
||||||
|
|
||||||
getData () { return this.patchDb.store.cache.data }
|
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
@Inject(PATCH_SOURCE) private readonly source: Source<DataModel>,
|
@Inject(PATCH_SOURCE) private readonly source: Source<DataModel>,
|
||||||
@Inject(PATCH_HTTP) private readonly http: ApiService,
|
@Inject(PATCH_HTTP) private readonly http: ApiService,
|
||||||
|
|||||||
Reference in New Issue
Block a user