mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 10:21:52 +00:00
show page done
This commit is contained in:
committed by
Aiden McClelland
parent
d7ecc0b6e7
commit
5467383e8d
@@ -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'
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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' },
|
||||
}
|
||||
|
||||
@@ -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> {
|
||||
|
||||
Reference in New Issue
Block a user