show page done

This commit is contained in:
Matt Hill
2021-09-20 17:25:43 -06:00
committed by Aiden McClelland
parent d7ecc0b6e7
commit 5467383e8d
5 changed files with 154 additions and 98 deletions

View File

@@ -1,5 +1,5 @@
import { Component, Input } from '@angular/core'
import { PkgStatusRendering } from 'src/app/services/pkg-status-rendering.service'
import { StatusRendering } from 'src/app/services/pkg-status-rendering.service'
@Component({
selector: 'status',
@@ -7,7 +7,7 @@ import { PkgStatusRendering } from 'src/app/services/pkg-status-rendering.servic
styleUrls: ['./status.component.scss'],
})
export class StatusComponent {
@Input() rendering: PkgStatusRendering
@Input() rendering: StatusRendering
@Input() size?: string
@Input() style?: string = 'regular'
@Input() weight?: string = 'normal'

View File

@@ -23,23 +23,31 @@
<ion-item-divider>Status</ion-item-divider>
<ion-item>
<ion-label style="overflow: visible;">
<status [disconnected]="connectionFailure" size="x-large" weight="500" [rendering]="rendering"></status>
<status [disconnected]="connectionFailure" size="x-large" weight="500" [rendering]="PR[statuses.primary]"></status>
<span *ngIf="statuses.dependency">
Dependencies:
<status [disconnected]="connectionFailure" size="medium" weight="500" [rendering]="DR[statuses.dependency]"></status>
</span>
<span *ngIf="statuses.health">
Health:
<status [disconnected]="connectionFailure" size="medium" weight="500" [rendering]="HR[statuses.health]"></status>
</span>
</ion-label>
<ion-button slot="end" class="action-button" *ngIf="pkg.state === PackageState.Installed && (pkg.manifest.interfaces | hasUi)" [disabled]="!(pkg.state | isLaunchable : pkg.installed.status.main.status : pkg.manifest.interfaces)" (click)="launchUi()">
<ion-icon slot="start" name="open-outline"></ion-icon>
Open UI
</ion-button>
<ng-container *ngIf="!connectionFailure">
<ion-button slot="end" class="action-button" *ngIf="rendering.feStatus === FeStatus.NeedsConfig" (click)="presentModalConfig()">
<ng-container *ngIf="pkg.state === PackageState.Installed && !connectionFailure">
<ion-button slot="end" class="action-button" *ngIf="pkg.manifest.interfaces | hasUi" [disabled]="!(pkg.state | isLaunchable : pkg.installed.status.main.status : pkg.manifest.interfaces)" (click)="launchUi()">
<ion-icon slot="start" name="open-outline"></ion-icon>
Open UI
</ion-button>
<ion-button slot="end" class="action-button" *ngIf="!pkg.installed.status.configured" (click)="presentModalConfig()">
Configure
</ion-button>
<ion-button slot="end" class="action-button" *ngIf="[FeStatus.Starting, FeStatus.Running, FeStatus.NeedsAttention] | includes : rendering.feStatus" color="danger" (click)="stop()">
<ion-button slot="end" class="action-button" *ngIf="statuses.primary === PS.Running" color="danger" (click)="stop()">
Stop
</ion-button>
<ion-button slot="end" class="action-button" *ngIf="rendering.feStatus === FeStatus.DependencyIssue" (click)="scrollToRequirements()">
<ion-button slot="end" class="action-button" *ngIf="statuses.dependency && status.dependency !== DS.Satisfied" (click)="scrollToRequirements()">
Fix
</ion-button>
<ion-button slot="end" class="action-button" *ngIf="rendering.feStatus === FeStatus.Stopped" color="success" (click)="tryStart()">
<ion-button slot="end" class="action-button" *ngIf="statuses.primary === PS.Stopped && statuses.dependency !== DS.Critical" color="success" (click)="tryStart()">
Start
</ion-button>
</ng-container>
@@ -48,12 +56,12 @@
<!-- ** installed ** -->
<ng-container *ngIf="pkg.state === PackageState.Installed">
<!-- ** !restoring/backing-up ** -->
<ng-container *ngIf="!([PackageMainStatus.BackingUp, PackageMainStatus.Restoring] | includes : mainStatus.status); else maintenance">
<ng-container *ngIf="!([PS.BackingUp, PS.Restoring] | includes : statuses.primary); else maintenance">
<!-- ** health checks ** -->
<ng-container *ngIf="!($any(mainStatus).health | empty)">
<ng-container *ngIf="!(healthChecks | empty)">
<ion-item-divider>Health Checks</ion-item-divider>
<ng-container *ngIf="connectionFailure">
<ion-item *ngFor="let health of $any(mainStatus).health | keyvalue">
<ion-item *ngFor="let health of healthChecks | keyvalue">
<ion-avatar slot="start">
<ion-skeleton-text style="width: 20px; height: 20px; border-radius: 0;"></ion-skeleton-text>
</ion-avatar>
@@ -64,16 +72,18 @@
</ion-item>
</ng-container>
<ng-container *ngIf="!connectionFailure">
<ion-item *ngFor="let health of $any(mainStatus).health | keyvalue : asIsOrder">
<ion-spinner class="icon-spinner" color="warning" slot="start" *ngIf="['starting', 'loading'] | includes : $any(health.value).result"></ion-spinner>
<ion-icon slot="start" *ngIf="$any(health.value).result === 'success'" name="checkmark-outline" color="success"></ion-icon>
<ion-icon slot="start" *ngIf="$any(health.value).result === 'failure'" name="close" color="danger"></ion-icon>
<ion-icon slot="start" *ngIf="$any(health.value).result === 'disabled'" name="remove-outline" color="dark"></ion-icon>
<ion-label>
<p>{{ health.key }}</p>
<h2>{{ $any(health.value).result }}</h2>
<p *ngIf="$any(health.value).result === 'failure'"><ion-text color="danger">{{ $any(health.value).error }}</ion-text></p>
</ion-label>
<ion-item *ngFor="let health of healthChecks | keyvalue : asIsOrder">
<ng-container *ngIf="$any(health.value).result as result">
<ion-spinner class="icon-spinner" color="warning" slot="start" *ngIf="['starting', 'loading'] | includes : result"></ion-spinner>
<ion-icon slot="start" *ngIf="result === 'success'" name="checkmark-outline" color="success"></ion-icon>
<ion-icon slot="start" *ngIf="result === 'failure'" name="close" color="danger"></ion-icon>
<ion-icon slot="start" *ngIf="result === 'disabled'" name="remove-outline" color="dark"></ion-icon>
<ion-label>
<p>{{ health.key }}</p>
<h2>{{ result }}</h2>
<p *ngIf="result === 'failure'"><ion-text color="danger">{{ $any(health.value).error }}</ion-text></p>
</ion-label>
</ng-container>
</ion-item>
</ng-container>
</ng-container>

View File

@@ -8,8 +8,8 @@ import { wizardModal } from 'src/app/components/install-wizard/install-wizard.co
import { WizardBaker } from 'src/app/components/install-wizard/prebaked-wizards'
import { ConfigService } from 'src/app/services/config.service'
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
import { CurrentDependencyInfo, DependencyErrorConfigUnsatisfied, DependencyErrorType, MainStatus, PackageDataEntry, PackageMainStatus, PackageState } from 'src/app/services/patch-db/data-model'
import { FEStatus, PkgStatusRendering, renderPkgStatus } from 'src/app/services/pkg-status-rendering.service'
import { CurrentDependencyInfo, DependencyErrorConfigUnsatisfied, DependencyErrorType, HealthCheckResult, PackageDataEntry, PackageMainStatus, PackageState } from 'src/app/services/patch-db/data-model'
import { DependencyRendering, DependencyStatus, HealthRendering, HealthStatus, PrimaryRendering, PrimaryStatus, renderPkgStatus } from 'src/app/services/pkg-status-rendering.service'
import { ConnectionFailure, ConnectionService } from 'src/app/services/connection.service'
import { ErrorToastService } from 'src/app/services/error-toast.service'
import { AppConfigPage } from 'src/app/modals/app-config/app-config.page'
@@ -22,20 +22,29 @@ import { ProgressData } from 'src/app/pipes/install-state.pipe'
styleUrls: ['./app-show.page.scss'],
})
export class AppShowPage {
PackageState = PackageState
DependencyErrorType = DependencyErrorType
Math = Math
PS = PrimaryStatus
DS = DependencyStatus
HS = HealthStatus
PR = PrimaryRendering
DR = DependencyRendering
HR = HealthRendering
pkgId: string
pkg: PackageDataEntry
hideLAN: boolean
buttons: Button[] = []
FeStatus = FEStatus
PackageState = PackageState
DependencyErrorType = DependencyErrorType
currentDependencies: { [id: string]: CurrentDependencyInfo }
rendering: PkgStatusRendering
Math = Math
mainStatus: MainStatus
PackageMainStatus = PackageMainStatus
statuses: {
primary: PrimaryStatus
dependency: DependencyStatus
health: HealthStatus
} = { } as any
connectionFailure: boolean
loading = true
healthChecks: { [id: string]: HealthCheckResult }
installProgress: ProgressData
@ViewChild(IonContent) content: IonContent
@@ -176,8 +185,12 @@ export class AppShowPage {
this.currentDependencies[id] = value
}
})
this.mainStatus = { ...pkg.installed?.status.main }
this.rendering = renderPkgStatus(pkg.state, pkg.installed?.status)
if (pkg.installed?.status.main.status === PackageMainStatus.Running) {
this.healthChecks = { ...pkg.installed.status.main.health }
} else {
this.healthChecks = { }
}
this.statuses = renderPkgStatus(pkg)
}
private async installDep (depId: string): Promise<void> {
@@ -264,7 +277,6 @@ export class AppShowPage {
title: 'Instructions',
icon: 'list-outline',
color: 'danger',
disabled: [],
},
// config
{
@@ -272,7 +284,6 @@ export class AppShowPage {
title: 'Config',
icon: 'construct-outline',
color: 'danger',
disabled: [FEStatus.Installing, FEStatus.Updating, FEStatus.Removing, FEStatus.BackingUp, FEStatus.Restoring],
},
// properties
{
@@ -280,7 +291,6 @@ export class AppShowPage {
title: 'Properties',
icon: 'briefcase-outline',
color: 'danger',
disabled: [],
},
// interfaces
{
@@ -288,7 +298,6 @@ export class AppShowPage {
title: 'Interfaces',
icon: 'desktop-outline',
color: 'danger',
disabled: [],
},
// actions
{
@@ -296,7 +305,6 @@ export class AppShowPage {
title: 'Actions',
icon: 'flash-outline',
color: 'danger',
disabled: [],
},
// metrics
// {
@@ -304,8 +312,6 @@ export class AppShowPage {
// title: 'Monitor',
// icon: 'pulse-outline',
// color: 'danger',
// // @TODO make the disabled check better. Don't want to list every status here. Monitor should be disabled except is pkg is running.
// disabled: [FEStatus.Installing, FEStatus.Updating, FEStatus.Removing, FEStatus.BackingUp, FEStatus.Restoring],
// },
// logs
{
@@ -313,14 +319,12 @@ export class AppShowPage {
title: 'Logs',
icon: 'receipt-outline',
color: 'danger',
disabled: [],
},
{
action: () => this.donate(),
title: `Donate to ${this.pkg.manifest.title}`,
icon: 'logo-bitcoin',
color: 'danger',
disabled: [],
},
]
}
@@ -334,6 +338,5 @@ interface Button {
title: string
icon: string
color: string
disabled: FEStatus[]
action: Function
}

View File

@@ -1,69 +1,113 @@
import { HealthCheckResultLoading, MainStatusRunning, PackageMainStatus, PackageState, Status } from './patch-db/data-model'
import { isEmptyObject } from '../util/misc.util'
import { PackageDataEntry, InstalledPackageDataEntry, PackageMainStatus, PackageState, Status } from './patch-db/data-model'
export function renderPkgStatus (state: PackageState, status: Status): PkgStatusRendering {
switch (state) {
case PackageState.Installing: return { display: 'Installing', color: 'primary', showDots: true, feStatus: FEStatus.Installing }
case PackageState.Updating: return { display: 'Updating', color: 'primary', showDots: true, feStatus: FEStatus.Updating }
case PackageState.Removing: return { display: 'Removing', color: 'warning', showDots: true, feStatus: FEStatus.Removing }
case PackageState.Installed: return handleInstalledState(status)
}
}
export function renderPkgStatus (pkg: PackageDataEntry): {
primary: PrimaryStatus,
dependency: DependencyStatus | null,
health: HealthStatus | null
} {
let primary: PrimaryStatus
let dependency: DependencyStatus | null = null
let health: HealthStatus | null = null
function handleInstalledState (status: Status): PkgStatusRendering {
if (!status.configured) {
return { display: 'Needs Config', color: 'warning', showDots: false, feStatus: FEStatus.NeedsConfig }
}
if (Object.keys(status['dependency-errors']).length) {
return { display: 'Dependency Issue', color: 'warning', showDots: false, feStatus: FEStatus.DependencyIssue }
}
switch (status.main.status) {
case PackageMainStatus.Stopping: return { display: 'Stopping', color: 'dark', showDots: true, feStatus: FEStatus.Stopping }
case PackageMainStatus.Stopped: return { display: 'Stopped', color: 'dark', showDots: false, feStatus: FEStatus.Stopped }
case PackageMainStatus.BackingUp: return { display: 'Backing Up', color: 'warning', showDots: true, feStatus: FEStatus.BackingUp }
case PackageMainStatus.Restoring: return { display: 'Restoring', color: 'primary', showDots: true, feStatus: FEStatus.Restoring }
case PackageMainStatus.Running: return handleRunningState(status.main)
}
}
function handleRunningState (status: MainStatusRunning): PkgStatusRendering {
if (Object.values(status.health).some(h => h.result === 'failure')) {
return { display: 'Needs Attention', color: 'danger', showDots: false, feStatus: FEStatus.NeedsAttention }
} else if (Object.values(status.health).some(h => h.result === 'starting')) {
return { display: 'Starting', color: 'warning', showDots: true, feStatus: FEStatus.Starting }
} else if (Object.values(status.health).some(h => h.result === 'loading')) {
const firstLoading = Object.values(status.health).find(h => h.result === 'loading') as HealthCheckResultLoading
return { display: firstLoading.message, color: 'warning', showDots: true, feStatus: FEStatus.Loading }
if (pkg.state === PackageState.Installed) {
primary = PrimaryStatus[pkg.installed.status.main.status]
dependency = getDependencyStatus(pkg.installed)
health = getHealthStatus(pkg.installed.status)
} else {
return { display: 'Running', color: 'success', showDots: false, feStatus: FEStatus.Running }
primary = PrimaryStatus[pkg.state]
}
return { primary, dependency, health }
}
function getDependencyStatus (pkg: InstalledPackageDataEntry): DependencyStatus {
if (isEmptyObject(pkg['current-dependencies'])) return null
const pkgIds = Object.keys(pkg.status['dependency-errors'])
for (let pkgId of pkgIds) {
if (pkg.manifest.dependencies[pkgId].critical) {
return DependencyStatus.Critical
}
}
return pkgIds.length ? DependencyStatus.Issue : DependencyStatus.Satisfied
}
function getHealthStatus (status: Status): HealthStatus {
if (!status.configured) {
return HealthStatus.NeedsConfig
}
if (status.main.status === PackageMainStatus.Running) {
const values = Object.values(status.main.health)
if (values.some(h => h.result === 'failure')) {
return HealthStatus.Failure
} else if (values.some(h => h.result === 'starting')) {
return HealthStatus.Starting
} else if (values.some(h => h.result === 'loading')) {
return HealthStatus.Loading
} else {
return HealthStatus.Healthy
}
}
}
export interface PkgStatusRendering {
feStatus: FEStatus
export interface StatusRendering {
display: string
color: string
showDots: boolean
showDots?: boolean
}
// aggregate of all pkg statuses, except for Installed, which implies a "main" or "FE" status
export enum FEStatus {
// pkg
export enum PrimaryStatus {
// state
Installing = 'installing',
Updating = 'updating',
Removing = 'removing',
// main
// status
Running = 'running',
Stopping = 'stopping',
Stopped = 'stopped',
BackingUp = 'backing-up',
Restoring = 'restoring',
// FE
NeedsAttention = 'needs-attention',
Starting = 'starting',
Connecting = 'connecting',
DependencyIssue = 'dependency-issue',
NeedsConfig = 'needs-config',
Loading = 'loading',
}
export enum DependencyStatus {
Issue = 'issue',
Critical = 'critical',
Satisfied = 'satisfied',
}
export enum HealthStatus {
NeedsConfig = 'needs-config',
Failure = 'failure',
Starting = 'starting',
Loading = 'loading',
Healthy = 'healthy',
}
export const PrimaryRendering: { [key: string]: StatusRendering } = {
[PrimaryStatus.Installing]: { display: 'Installing', color: 'primary', showDots: true },
[PrimaryStatus.Updating]: { display: 'Updating', color: 'primary', showDots: true },
[PrimaryStatus.Removing]: { display: 'Removing', color: 'warning', showDots: true },
[PrimaryStatus.Stopping]: { display: 'Stopping', color: 'dark', showDots: true },
[PrimaryStatus.Stopped]: { display: 'Stopped', color: 'dark', showDots: false },
[PrimaryStatus.BackingUp]: { display: 'Backing Up', color: 'warning', showDots: true },
[PrimaryStatus.Restoring]: { display: 'Restoring', color: 'primary', showDots: true },
[PrimaryStatus.Running]: { display: 'Running', color: 'success', showDots: false },
}
export const DependencyRendering: { [key: string]: StatusRendering } = {
[DependencyStatus.Issue]: { display: 'Issue', color: 'warning' },
[DependencyStatus.Critical]: { display: 'Critical Issue', color: 'danger' },
[DependencyStatus.Satisfied]: { display: 'Satisfied', color: 'success' },
}
export const HealthRendering: { [key: string]: StatusRendering } = {
[HealthStatus.NeedsConfig]: { display: 'Needs Config', color: 'warning' },
[HealthStatus.Failure]: { display: 'Failure', color: 'danger' },
[HealthStatus.Starting]: { display: 'Starting', color: 'primary' },
[HealthStatus.Loading]: { display: 'Loading', color: 'primary' },
[HealthStatus.Healthy]: { display: 'Healthy', color: 'success' },
}

View File

@@ -86,8 +86,7 @@ export function isObject (val: any): boolean {
}
export function isEmptyObject (obj: object): boolean {
if (!obj) return true
return Object.keys(obj).length === 0 && obj.constructor === Object
return !Object.keys(obj).length
}
export function pauseFor (ms: number): Promise<void> {