diff --git a/ui/src/app/components/status/status.component.ts b/ui/src/app/components/status/status.component.ts index f9c447490..eb0cd0897 100644 --- a/ui/src/app/components/status/status.component.ts +++ b/ui/src/app/components/status/status.component.ts @@ -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' diff --git a/ui/src/app/pages/apps-routes/app-show/app-show.page.html b/ui/src/app/pages/apps-routes/app-show/app-show.page.html index 25a4b4587..8649318cb 100644 --- a/ui/src/app/pages/apps-routes/app-show/app-show.page.html +++ b/ui/src/app/pages/apps-routes/app-show/app-show.page.html @@ -23,23 +23,31 @@ Status - + + + Dependencies: + + + + Health: + + - - - Open UI - - - + + + + Open UI + + Configure - + Stop - + Fix - + Start @@ -48,12 +56,12 @@ - + - + Health Checks - + @@ -64,16 +72,18 @@ - - - - - - -

{{ health.key }}

-

{{ $any(health.value).result }}

-

{{ $any(health.value).error }}

-
+ + + + + + + +

{{ health.key }}

+

{{ result }}

+

{{ $any(health.value).error }}

+
+
diff --git a/ui/src/app/pages/apps-routes/app-show/app-show.page.ts b/ui/src/app/pages/apps-routes/app-show/app-show.page.ts index d6d1c9cf1..36e727d2d 100644 --- a/ui/src/app/pages/apps-routes/app-show/app-show.page.ts +++ b/ui/src/app/pages/apps-routes/app-show/app-show.page.ts @@ -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 { @@ -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 } diff --git a/ui/src/app/services/pkg-status-rendering.service.ts b/ui/src/app/services/pkg-status-rendering.service.ts index d5ffaf1da..4408985b2 100644 --- a/ui/src/app/services/pkg-status-rendering.service.ts +++ b/ui/src/app/services/pkg-status-rendering.service.ts @@ -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' }, } diff --git a/ui/src/app/util/misc.util.ts b/ui/src/app/util/misc.util.ts index 11ed0765f..fcb466867 100644 --- a/ui/src/app/util/misc.util.ts +++ b/ui/src/app/util/misc.util.ts @@ -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 {