fix wizard and server metrics

This commit is contained in:
Matt Hill
2021-10-05 14:24:00 -06:00
committed by Aiden McClelland
parent f46d935bb0
commit b978d5062f
14 changed files with 620 additions and 584 deletions

View File

@@ -1,24 +1,16 @@
import { Component, Input, OnInit } from '@angular/core' import { Component, Input } from '@angular/core'
import { BehaviorSubject, Subject } from 'rxjs'
import { Loadable } from '../loadable'
@Component({ @Component({
selector: 'alert', selector: 'alert',
templateUrl: './alert.component.html', templateUrl: './alert.component.html',
styleUrls: ['../install-wizard.component.scss'], styleUrls: ['../install-wizard.component.scss'],
}) })
export class AlertComponent implements OnInit, Loadable { export class AlertComponent {
@Input() params: { @Input() params: {
title: string title: string
message: string message: string
titleColor: string titleColor: string
} }
loading$ = new BehaviorSubject(false)
cancel$ = new Subject<void>()
load () { } load () { }
constructor () { }
ngOnInit () { }
} }

View File

@@ -1,17 +1,4 @@
<div *ngIf="!(loading$ | async) && !params.skipCompletionDialogue" class="slide-content">
<div style="margin-top: 25px;">
<div style="margin: 15px; display: flex; justify-content: center; align-items: center;">
<ion-label [color]="color$ | async" style="font-size: xx-large; font-weight: bold;">
{{ successText }}
</ion-label>
</div>
<div class="long-message">
{{ summary }}
</div>
</div>
</div>
<div *ngIf="loading$ | async" class="center-spinner"> <div *ngIf="loading$ | async" class="center-spinner">
<ion-spinner color="warning" name="lines"></ion-spinner> <ion-spinner color="warning" name="lines"></ion-spinner>
<ion-label class="long-message">{{ label }}</ion-label> <ion-label class="long-message">{{ message }}</ion-label>
</div> </div>

View File

@@ -3,7 +3,6 @@ import { CommonModule } from '@angular/common'
import { CompleteComponent } from './complete.component' import { CompleteComponent } from './complete.component'
import { IonicModule } from '@ionic/angular' import { IonicModule } from '@ionic/angular'
import { RouterModule } from '@angular/router' import { RouterModule } from '@angular/router'
import { SharingModule } from 'src/app/modules/sharing.module'
@NgModule({ @NgModule({
declarations: [ declarations: [
@@ -13,7 +12,6 @@ import { SharingModule } from 'src/app/modules/sharing.module'
CommonModule, CommonModule,
IonicModule, IonicModule,
RouterModule.forChild([]), RouterModule.forChild([]),
SharingModule,
], ],
exports: [CompleteComponent], exports: [CompleteComponent],
}) })

View File

@@ -1,8 +1,8 @@
import { Component, Input, OnInit } from '@angular/core' import { Component, Input } from '@angular/core'
import { BehaviorSubject, from, Subject } from 'rxjs' import { BehaviorSubject, from, Subject } from 'rxjs'
import { takeUntil } from 'rxjs/operators' import { takeUntil } from 'rxjs/operators'
import { capitalizeFirstLetter } from 'src/app/util/misc.util' import { capitalizeFirstLetter } from 'src/app/util/misc.util'
import { Loadable, markAsLoadingDuring$ } from '../loadable' import { markAsLoadingDuring$ } from '../loadable'
import { WizardAction } from '../wizard-types' import { WizardAction } from '../wizard-types'
@Component({ @Component({
@@ -10,13 +10,12 @@ import { WizardAction } from '../wizard-types'
templateUrl: './complete.component.html', templateUrl: './complete.component.html',
styleUrls: ['../install-wizard.component.scss'], styleUrls: ['../install-wizard.component.scss'],
}) })
export class CompleteComponent implements OnInit, Loadable { export class CompleteComponent {
@Input() params: { @Input() params: {
action: WizardAction action: WizardAction
verb: string // loader verb: '*stopping* ...' verb: string // loader verb: '*stopping* ...'
title: string title: string
executeAction: () => Promise<any> executeAction: () => Promise<any>
skipCompletionDialogue?: boolean
} }
@Input() transitions: { @Input() transitions: {
@@ -27,61 +26,20 @@ export class CompleteComponent implements OnInit, Loadable {
} }
loading$ = new BehaviorSubject(false) loading$ = new BehaviorSubject(false)
color$ = new BehaviorSubject('medium')
cancel$ = new Subject<void>() cancel$ = new Subject<void>()
label: string message: string
summary: string
successText: string
load () { load () {
markAsLoadingDuring$(this.loading$, from(this.params.executeAction())).pipe(takeUntil(this.cancel$)).subscribe( markAsLoadingDuring$(this.loading$, from(this.params.executeAction())).pipe(takeUntil(this.cancel$)).subscribe(
{ {
error: e => this.transitions.error(new Error(`${this.params.action} failed: ${e.message || e}`)), error: e => this.transitions.error(new Error(`${this.params.action} failed: ${e.message || e}`)),
complete: () => this.params.skipCompletionDialogue && this.transitions.final(), complete: () => this.transitions.final(),
}, },
) )
} }
constructor () { }
ngOnInit () { ngOnInit () {
switch (this.params.action) { this.message = `${capitalizeFirstLetter(this.params.verb)} ${this.params.title}...`
case 'install':
this.summary = `Installation of ${this.params.title} is now in progress. You will receive a notification when the installation has completed.`
this.label = `${capitalizeFirstLetter(this.params.verb)} ${this.params.title}...`
this.color$.next('primary')
this.successText = 'In Progress'
break
case 'downgrade':
this.summary = `Downgrade for ${this.params.title} is now in progress. You will receive a notification when the downgrade has completed.`
this.label = `${capitalizeFirstLetter(this.params.verb)} ${this.params.title}...`
this.color$.next('primary')
this.successText = 'In Progress'
break
case 'update':
this.summary = `Update for ${this.params.title} is now in progress. You will receive a notification when the update has completed.`
this.label = `${capitalizeFirstLetter(this.params.verb)} ${this.params.title}...`
this.color$.next('primary')
this.successText = 'In Progress'
break
case 'uninstall':
this.summary = `${capitalizeFirstLetter(this.params.title)} has been successfully uninstalled.`
this.label = `${capitalizeFirstLetter(this.params.verb)} ${this.params.title}...`
this.color$.next('success')
this.successText = 'Success'
break
case 'stop':
this.summary = `${capitalizeFirstLetter(this.params.title)} has been successfully stopped.`
this.label = `${capitalizeFirstLetter(this.params.verb)} ${this.params.title}...`
this.color$.next('success')
this.successText = 'Success'
break
case 'configure':
this.summary = `New config for ${this.params.title} has been successfully saved.`
this.label = `${capitalizeFirstLetter(this.params.verb)} ${this.params.title}...`
this.color$.next('success')
this.successText = 'Success'
break
}
} }
} }

View File

@@ -16,11 +16,11 @@
</ion-label> </ion-label>
</div> </div>
<div *ngIf="longMessage" class="long-message" > <div *ngIf="message" class="long-message" >
{{ longMessage }} {{ message }}
</div> </div>
<div style="margin: 25px 0px;"> <div *ngIf="hasDependentViolation" style="margin: 25px 0px;">
<div *ngIf="hasDependentViolation" style="border-width: 0px 0px 1px 0px; font-size: unset; text-align: left; font-weight: bold; margin-left: 13px; border-style: solid; border-color: var(--ion-color-light-tint);"> <div style="border-width: 0px 0px 1px 0px; font-size: unset; text-align: left; font-weight: bold; margin-left: 13px; border-style: solid; border-color: var(--ion-color-light-tint);">
<ion-text color="warning">Affected Services</ion-text> <ion-text color="warning">Affected Services</ion-text>
</div> </div>
<ion-item <ion-item

View File

@@ -1,10 +1,10 @@
import { Component, Input, OnInit } from '@angular/core' import { Component, Input } from '@angular/core'
import { BehaviorSubject, from, Subject } from 'rxjs' import { BehaviorSubject, from, Subject } from 'rxjs'
import { takeUntil, tap } from 'rxjs/operators' import { takeUntil, tap } from 'rxjs/operators'
import { Breakages } from 'src/app/services/api/api.types' import { Breakages } from 'src/app/services/api/api.types'
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
import { capitalizeFirstLetter, isEmptyObject } from 'src/app/util/misc.util' import { capitalizeFirstLetter, isEmptyObject } from 'src/app/util/misc.util'
import { Loadable, markAsLoadingDuring$ } from '../loadable' import { markAsLoadingDuring$ } from '../loadable'
import { WizardAction } from '../wizard-types' import { WizardAction } from '../wizard-types'
@Component({ @Component({
@@ -12,13 +12,12 @@ import { WizardAction } from '../wizard-types'
templateUrl: './dependents.component.html', templateUrl: './dependents.component.html',
styleUrls: ['../install-wizard.component.scss'], styleUrls: ['../install-wizard.component.scss'],
}) })
export class DependentsComponent implements OnInit, Loadable { export class DependentsComponent {
@Input() params: { @Input() params: {
title: string, title: string,
action: WizardAction, //Are you sure you want to *uninstall*..., action: WizardAction, //Are you sure you want to *uninstall*...,
verb: string, // *Uninstalling* will cause problems... verb: string, // *Uninstalling* will cause problems...
fetchBreakages: () => Promise<Breakages>, fetchBreakages: () => Promise<Breakages>
skipConfirmationDialogue?: boolean
} }
@Input() transitions: { @Input() transitions: {
cancel: () => any cancel: () => any
@@ -29,33 +28,28 @@ export class DependentsComponent implements OnInit, Loadable {
dependentBreakages: Breakages dependentBreakages: Breakages
hasDependentViolation: boolean hasDependentViolation: boolean
longMessage: string | null = null message: string | null = null
color$ = new BehaviorSubject('medium') // this will display disabled while loading
loading$ = new BehaviorSubject(false) loading$ = new BehaviorSubject(false)
cancel$ = new Subject<void>() cancel$ = new Subject<void>()
constructor ( constructor (
public readonly patch: PatchDbService, public readonly patch: PatchDbService,
) { } ) { }
ngOnInit () { }
load () { load () {
this.color$.next('medium') markAsLoadingDuring$(this.loading$, from(this.params.fetchBreakages()))
markAsLoadingDuring$(this.loading$, from(this.params.fetchBreakages())).pipe( .pipe(
takeUntil(this.cancel$), takeUntil(this.cancel$),
tap(breakages => this.dependentBreakages = breakages), tap(breakages => this.dependentBreakages = breakages),
).subscribe( )
.subscribe(
{ {
complete: () => { complete: () => {
this.hasDependentViolation = this.dependentBreakages && !isEmptyObject(this.dependentBreakages) this.hasDependentViolation = this.dependentBreakages && !isEmptyObject(this.dependentBreakages)
if (this.hasDependentViolation) { if (this.hasDependentViolation) {
this.longMessage = `${capitalizeFirstLetter(this.params.verb)} ${this.params.title} will prohibit the following services from functioning properly and will cause them to stop if they are currently running.` this.message = `${capitalizeFirstLetter(this.params.verb)} ${this.params.title} will prohibit the following services from functioning properly and will cause them to stop if they are currently running.`
this.color$.next('warning')
} else if (this.params.skipConfirmationDialogue) {
this.transitions.next()
} else { } else {
this.longMessage = `No other services installed on your Embassy will be affected by this action.` this.message = `No other services installed on your Embassy will be affected by this action.`
this.color$.next('success')
} }
}, },
error: (e: Error) => this.transitions.error(new Error(`Fetching dependent service information failed: ${e.message || e}`)), error: (e: Error) => this.transitions.error(new Error(`Fetching dependent service information failed: ${e.message || e}`)),

View File

@@ -9,15 +9,15 @@ export interface Loadable {
cancel$: Subject<void> // will cancel load function cancel$: Subject<void> // will cancel load function
} }
export function markAsLoadingDuring$<T> ($trigger$: Subject<boolean>, o: Observable<T>): Observable<T> { export function markAsLoadingDuring$<T> (trigger$: Subject<boolean>, o: Observable<T>): Observable<T> {
let shouldBeOn = true let shouldBeOn = true
const displayIfItsBeenAtLeast = 5 // ms const displayIfItsBeenAtLeast = 5 // ms
return fromSync$(() => { return fromSync$(() => {
emitAfter$(displayIfItsBeenAtLeast).subscribe(() => { if (shouldBeOn) $trigger$.next(true) }) emitAfter$(displayIfItsBeenAtLeast).subscribe(() => { if (shouldBeOn) trigger$.next(true) })
}).pipe( }).pipe(
concatMap(() => o), concatMap(() => o),
finalize(() => { finalize(() => {
$trigger$.next(false) trigger$.next(false)
shouldBeOn = false shouldBeOn = false
}), }),
) )

View File

@@ -1,13 +1,11 @@
import { Component, Input, OnInit } from '@angular/core' import { Component, Input } from '@angular/core'
import { BehaviorSubject, Subject } from 'rxjs'
import { Loadable } from '../loadable'
@Component({ @Component({
selector: 'notes', selector: 'notes',
templateUrl: './notes.component.html', templateUrl: './notes.component.html',
styleUrls: ['../install-wizard.component.scss'], styleUrls: ['../install-wizard.component.scss'],
}) })
export class NotesComponent implements OnInit, Loadable { export class NotesComponent {
@Input() params: { @Input() params: {
notes: { [version: string]: string } notes: { [version: string]: string }
title: string title: string
@@ -15,14 +13,8 @@ export class NotesComponent implements OnInit, Loadable {
headline: string headline: string
} }
loading$ = new BehaviorSubject(false)
cancel$ = new Subject<void>()
load () { } load () { }
constructor () { }
ngOnInit () { }
asIsOrder () { asIsOrder () {
return 0 return 0
} }

View File

@@ -41,7 +41,6 @@ export class WizardBaker {
slide: { slide: {
selector: 'dependents', selector: 'dependents',
params: { params: {
skipConfirmationDialogue: true,
action, action,
verb: 'updating', verb: 'updating',
title, title,
@@ -143,7 +142,6 @@ export class WizardBaker {
{ slide: { { slide: {
selector: 'dependents', selector: 'dependents',
params: { params: {
skipConfirmationDialogue: true,
action, action,
verb: 'downgrading', verb: 'downgrading',
title, title,

View File

@@ -104,9 +104,9 @@
<!-- ** menu ** --> <!-- ** menu ** -->
<ion-item-divider>Menu</ion-item-divider> <ion-item-divider>Menu</ion-item-divider>
<ion-item button detail *ngFor="let button of buttons" (click)="button.action()"> <ion-item button detail *ngFor="let button of buttons" (click)="button.action()">
<ion-icon slot="start" [name]="button.icon" color="dark"></ion-icon> <ion-icon slot="start" [name]="button.icon"></ion-icon>
<ion-label> <ion-label>
<h2><b>{{ button.title }}</b></h2> <h2>{{ button.title }}</h2>
<p *ngIf="button.description">{{ button.description }}</p> <p *ngIf="button.description">{{ button.description }}</p>
</ion-label> </ion-label>
</ion-item> </ion-item>

View File

@@ -64,7 +64,6 @@ export class AppShowPage {
async ngOnInit () { async ngOnInit () {
this.pkgId = this.route.snapshot.paramMap.get('pkgId') this.pkgId = this.route.snapshot.paramMap.get('pkgId')
// this.setServiceValues(this.patch.data['package-data'])
this.subs = [ this.subs = [
// 1 // 1

View File

@@ -48,8 +48,10 @@ export class ServerMetricsPage {
async getMetrics (): Promise<void> { async getMetrics (): Promise<void> {
try { try {
const metrics = await this.embassyApi.getServerMetrics({ }) const metrics = await this.embassyApi.getServerMetrics({ })
Object.entries(metrics).forEach(([key, val]) => { Object.entries(metrics).forEach(([groupKey, groupVal]) => {
this.metrics[key] = val Object.entries(groupVal).forEach(([key, val]) => {
this.metrics[groupKey][key] = val
})
}) })
} catch (e) { } catch (e) {
this.errToast.present(e) this.errToast.present(e)

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@ import { Injectable } from '@angular/core'
import { pauseFor } from '../../util/misc.util' import { pauseFor } from '../../util/misc.util'
import { ApiService } from './embassy-api.service' import { ApiService } from './embassy-api.service'
import { PatchOp } from 'patch-db-client' import { PatchOp } from 'patch-db-client'
import { InstallProgress, PackageDataEntry, PackageMainStatus, PackageState, ServerStatus } from 'src/app/services/patch-db/data-model' import { DependencyErrorType, InstallProgress, PackageDataEntry, PackageMainStatus, PackageState, ServerStatus } from 'src/app/services/patch-db/data-model'
import { RR, WithRevision } from './api.types' import { RR, WithRevision } from './api.types'
import { parsePropertiesPermissive } from 'src/app/util/properties.util' import { parsePropertiesPermissive } from 'src/app/util/properties.util'
import { Mock } from './api.fixures' import { Mock } from './api.fixures'
@@ -333,12 +333,14 @@ export class MockApiService extends ApiService {
unpacked: 0, unpacked: 0,
'unpack-complete': false, 'unpack-complete': false,
} }
const pkg: PackageDataEntry = { const pkg: PackageDataEntry = {
...Mock.Pkgs[params.id], ...Mock.LocalPkgs[params.id],
state: PackageState.Installing, state: PackageState.Installing,
'install-progress': initialProgress, 'install-progress': initialProgress,
installed: undefined, installed: undefined,
} }
const patch = [ const patch = [
{ {
op: PatchOp.ADD, op: PatchOp.ADD,
@@ -346,6 +348,7 @@ export class MockApiService extends ApiService {
value: pkg, value: pkg,
}, },
] ]
const res = await this.http.rpcRequest<WithRevision<null>>({ method: 'db.patch', params: { patch } }) const res = await this.http.rpcRequest<WithRevision<null>>({ method: 'db.patch', params: { patch } })
setTimeout(async () => { setTimeout(async () => {
this.updateProgress(params.id, initialProgress) this.updateProgress(params.id, initialProgress)
@@ -411,12 +414,16 @@ export class MockApiService extends ApiService {
async startPackageRaw (params: RR.StartPackageReq): Promise<RR.StartPackageRes> { async startPackageRaw (params: RR.StartPackageReq): Promise<RR.StartPackageRes> {
await pauseFor(2000) await pauseFor(2000)
const path = `/package-data/${params.id}/installed/status/main/status` const path = `/package-data/${params.id}/installed/status/main`
const patch = [ const patch = [
{ {
op: PatchOp.REPLACE, op: PatchOp.REPLACE,
path, path,
value: PackageMainStatus.Running, value: {
status: PackageMainStatus.Running,
started: new Date().toISOString(), // UTC date string
health: { },
},
}, },
] ]
return this.http.rpcRequest<WithRevision<null>>({ method: 'db.patch', params: { patch } }) return this.http.rpcRequest<WithRevision<null>>({ method: 'db.patch', params: { patch } })
@@ -424,17 +431,26 @@ export class MockApiService extends ApiService {
async dryStopPackage (params: RR.DryStopPackageReq): Promise<RR.DryStopPackageRes> { async dryStopPackage (params: RR.DryStopPackageReq): Promise<RR.DryStopPackageRes> {
await pauseFor(2000) await pauseFor(2000)
return { } return {
'lnd': {
dependency: 'bitcoind',
error: {
type: DependencyErrorType.NotRunning,
},
},
}
} }
async stopPackageRaw (params: RR.StopPackageReq): Promise<RR.StopPackageRes> { async stopPackageRaw (params: RR.StopPackageReq): Promise<RR.StopPackageRes> {
await pauseFor(2000) await pauseFor(2000)
const path = `/package-data/${params.id}/installed/status/main/status` const path = `/package-data/${params.id}/installed/status/main`
const patch = [ const patch = [
{ {
op: PatchOp.REPLACE, op: PatchOp.REPLACE,
path, path,
value: PackageMainStatus.Stopping, value: {
status: PackageMainStatus.Stopping,
},
}, },
] ]
const res = await this.http.rpcRequest<WithRevision<null>>({ method: 'db.patch', params: { patch } }) const res = await this.http.rpcRequest<WithRevision<null>>({ method: 'db.patch', params: { patch } })
@@ -442,7 +458,7 @@ export class MockApiService extends ApiService {
const patch = [ const patch = [
{ {
op: PatchOp.REPLACE, op: PatchOp.REPLACE,
path, path: path + '/status',
value: PackageMainStatus.Stopped, value: PackageMainStatus.Stopped,
}, },
] ]
@@ -540,7 +556,7 @@ export class MockApiService extends ApiService {
{ {
op: PatchOp.REPLACE, op: PatchOp.REPLACE,
path: `/package-data/${id}`, path: `/package-data/${id}`,
value: Mock.Pkgs[id], value: Mock.LocalPkgs[id],
}, },
] ]
this.http.rpcRequest<WithRevision<null>>({ method: 'db.patch', params: { patch } }) this.http.rpcRequest<WithRevision<null>>({ method: 'db.patch', params: { patch } })