-
+
+
+
+
+
+
+
+
+
+
+
{{ pkg.value.entry.state | titlecase }}...{{ pkg.value.installProgress.totalProgress }}%
{{ pkg.value.entry.manifest.title }}
diff --git a/ui/src/app/pages/apps-routes/app-list/app-list.page.scss b/ui/src/app/pages/apps-routes/app-list/app-list.page.scss
index fca704000..a33eca105 100644
--- a/ui/src/app/pages/apps-routes/app-list/app-list.page.scss
+++ b/ui/src/app/pages/apps-routes/app-list/app-list.page.scss
@@ -1,5 +1,6 @@
.installed-card {
- margin: 2px;
+ margin: 4px;
+ padding: 4px;
background: linear-gradient(37deg, #333333, #131313);
border-radius: 10px;
text-align: center;
@@ -83,3 +84,17 @@
font-weight: bold;
margin: 0;
}
+
+.status-grid {
+ --ion-grid-padding: 0;
+ ion-row {
+ min-height: 22px;
+ }
+ ion-col {
+ --ion-grid-column-padding: 0
+ }
+ ion-icon {
+ font-size: calc(12px + .4vw);
+ padding-top: 4px;
+ }
+}
diff --git a/ui/src/app/pages/apps-routes/app-list/app-list.page.ts b/ui/src/app/pages/apps-routes/app-list/app-list.page.ts
index 868b05d01..c06eb2f98 100644
--- a/ui/src/app/pages/apps-routes/app-list/app-list.page.ts
+++ b/ui/src/app/pages/apps-routes/app-list/app-list.page.ts
@@ -4,7 +4,7 @@ import { ConnectionFailure, ConnectionService } from 'src/app/services/connectio
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
import { PackageDataEntry, PackageState } from 'src/app/services/patch-db/data-model'
import { Subscription } from 'rxjs'
-import { PrimaryRendering, renderPkgStatus, StatusRendering } from 'src/app/services/pkg-status-rendering.service'
+import { DependencyStatus, HealthStatus, PrimaryRendering, renderPkgStatus, StatusRendering } from 'src/app/services/pkg-status-rendering.service'
import { filter } from 'rxjs/operators'
import { isEmptyObject } from 'src/app/util/misc.util'
import { PackageLoadingService, ProgressData } from 'src/app/services/package-loading.service'
@@ -15,19 +15,11 @@ import { PackageLoadingService, ProgressData } from 'src/app/services/package-lo
styleUrls: ['./app-list.page.scss'],
})
export class AppListPage {
+ PackageState = PackageState
+
subs: Subscription[] = []
connectionFailure: boolean
- pkgs: { [id: string]: {
- entry: PackageDataEntry
- bulb: {
- class: string
- img: string
- }
- statusRendering: StatusRendering | null
- sub: Subscription | null
- installProgress: ProgressData
- }} = { }
- PackageState = PackageState
+ pkgs: { [id: string]: PkgInfo } = { }
loading = true
constructor (
@@ -70,17 +62,19 @@ export class AppListPage {
class: 'bulb-off',
img: 'assets/img/off-bulb.png',
},
- statusRendering: PrimaryRendering[renderPkgStatus(pkgs[id]).primary],
- sub: null,
+ primaryRendering: PrimaryRendering[renderPkgStatus(pkgs[id]).primary],
installProgress: !isEmptyObject(pkgs[id]['install-progress']) ? this.installPackageService.transform(pkgs[id]['install-progress']) : undefined,
+ error: false,
+ sub: null,
}
// subscribe to pkg
this.pkgs[id].sub = this.patch.watch$('package-data', id).subscribe(pkg => {
if (!pkg) return
let bulbClass = 'bulb-on'
let img = ''
- const statusRendering = PrimaryRendering[renderPkgStatus(pkg).primary]
- switch (statusRendering.color) {
+ const statuses = renderPkgStatus(pkg)
+ const primaryRendering = PrimaryRendering[statuses.primary]
+ switch (primaryRendering.color) {
case 'danger':
img = 'assets/img/danger-bulb.png'
break
@@ -101,7 +95,8 @@ export class AppListPage {
class: bulbClass,
img,
}
- this.pkgs[id].statusRendering = statusRendering
+ this.pkgs[id].primaryRendering = primaryRendering
+ this.pkgs[id].error = [HealthStatus.NeedsConfig, HealthStatus.Failure].includes(statuses.health) || [DependencyStatus.Issue, DependencyStatus.Critical].includes(statuses.dependency)
})
})
}),
@@ -128,3 +123,15 @@ export class AppListPage {
return 0
}
}
+
+interface PkgInfo {
+ entry: PackageDataEntry
+ bulb: {
+ class: string
+ img: string
+ }
+ primaryRendering: StatusRendering
+ installProgress: ProgressData
+ error: boolean
+ sub: Subscription | null
+}
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 6ceafe5ea..bdbef3273 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
@@ -65,13 +65,47 @@ export class AppShowPage {
async ngOnInit () {
this.pkgId = this.route.snapshot.paramMap.get('pkgId')
- this.setValues(this.patch.data['package-data'])
+ // this.setServiceValues(this.patch.data['package-data'])
this.subs = [
// 1
- this.patch.watch$('package-data')
- .subscribe(pkgs => {
- this.setValues(pkgs)
+ this.patch.watch$('package-data', this.pkgId)
+ .subscribe(pkg => {
+ this.pkg = pkg
+ this.installProgress = !isEmptyObject(pkg['install-progress']) ? this.packageLoadingService.transform(pkg['install-progress']) : undefined
+ this.statuses = renderPkgStatus(pkg)
+
+ // health
+ if (this.pkg.installed?.status.main.status === PackageMainStatus.Running) {
+ this.healthChecks = { ...this.pkg.installed.status.main.health }
+ } else {
+ this.healthChecks = { }
+ }
+
+ // dependencies
+ if (!pkg.installed) {
+ this.dependencies = { }
+ } else {
+ const currentDeps = pkg.installed['current-dependencies']
+ Object.keys(currentDeps).forEach(key => {
+ const manifestDep = pkg.manifest.dependencies[key]
+ if (!this.dependencies[key] && manifestDep) {
+ this.dependencies[key] = { } as any
+ this.dependencies[key].sub = this.patch.watch$('package-data', key)
+ .subscribe(localDep => {
+ this.setDepValues(key, manifestDep.version, localDep)
+ })
+ }
+ })
+
+ // unsub to deleted
+ Object.keys(this.dependencies).forEach(key => {
+ if (!currentDeps[key]) {
+ this.dependencies[key].sub.unsubscribe()
+ delete this.dependencies[key]
+ }
+ })
+ }
}),
// 2
this.connectionService.watchFailure$()
@@ -88,6 +122,9 @@ export class AppShowPage {
ngOnDestroy () {
this.subs.forEach(sub => sub.unsubscribe())
+ Object.values(this.dependencies).forEach(dep => {
+ dep.sub.unsubscribe()
+ })
}
launchUi (): void {
@@ -166,87 +203,63 @@ export class AppShowPage {
await modal.present()
}
- private setValues (pkgs: { [id: string]: PackageDataEntry }): void {
- this.pkg = pkgs[this.pkgId]
- this.installProgress = !isEmptyObject(this.pkg['install-progress']) ? this.packageLoadingService.transform(this.pkg['install-progress']) : undefined
- this.statuses = renderPkgStatus(this.pkg)
+ private setDepValues (id: string, version: string, localDep: PackageDataEntry | undefined): void {
+ let errorText = ''
+ let spinnerColor = ''
+ let actionText = 'View'
+ let action: () => any = () => this.navCtrl.navigateForward(`/services/${id}`)
- if (!this.pkg.installed) {
- this.dependencies = { }
- this.healthChecks = { }
- } else {
- // ** dependencies
- Object.keys(this.pkg.installed['current-dependencies'] || { })
- .forEach(id => {
- // we can safely ignore any current dependencies that are not defined in the service manifest
- const manifestDep = this.pkg.manifest.dependencies[id]
- if (manifestDep) {
- let errorText = ''
- let spinnerColor = ''
- let actionText = 'View'
- let action: () => any = () => this.navCtrl.navigateForward(`/services/${id}`)
+ const error = this.pkg.installed.status['dependency-errors'][id] || null
- const error = this.pkg.installed.status['dependency-errors'][id] || null
+ if (error) {
+ // health checks failed
+ if ([DependencyErrorType.InterfaceHealthChecksFailed, DependencyErrorType.HealthChecksFailed].includes(error.type)) {
+ errorText = 'Health Check Failed'
+ // not fully installed (same as !localDep?.installed)
+ } else if (error.type === DependencyErrorType.NotInstalled) {
+ if (localDep) {
+ errorText = localDep.state // 'Installing' | 'Removing'
+ } else {
+ errorText = 'Not Installed'
+ actionText = 'Install'
+ action = () => this.fixDep('install', id)
+ }
+ // incorrect version
+ } else if (error.type === DependencyErrorType.IncorrectVersion) {
+ if (localDep) {
+ errorText = localDep.state // 'Updating' | 'Removing'
+ } else {
+ errorText = 'Incorrect Version'
+ actionText = 'Update'
+ action = () => this.fixDep('update', id)
+ }
+ // not running
+ } else if (error.type === DependencyErrorType.NotRunning) {
+ errorText = 'Not Running'
+ actionText = 'Start'
+ // config unsatisfied
+ } else if (error.type === DependencyErrorType.ConfigUnsatisfied) {
+ errorText = 'Config Not Satisfied'
+ actionText = 'Auto Config'
+ action = () => this.fixDep('configure', id)
+ }
- if (error) {
- const localDep = pkgs[id]
- // health checks failed
- if ([DependencyErrorType.InterfaceHealthChecksFailed, DependencyErrorType.HealthChecksFailed].includes(error.type)) {
- errorText = 'Health Check Failed'
- // not fully installed (same as !localDep?.installed)
- } else if (error.type === DependencyErrorType.NotInstalled) {
- if (localDep) {
- errorText = localDep.state // 'Installing' | 'Removing'
- } else {
- errorText = 'Not Installed'
- actionText = 'Install'
- action = () => this.fixDep('install', id)
- }
- // incorrect version
- } else if (error.type === DependencyErrorType.IncorrectVersion) {
- if (localDep) {
- errorText = localDep.state // 'Updating' | 'Removing'
- } else {
- errorText = 'Incorrect Version'
- actionText = 'Update'
- action = () => this.fixDep('update', id)
- }
- // not running
- } else if (error.type === DependencyErrorType.NotRunning) {
- errorText = 'Not Running'
- actionText = 'Start'
- // config unsatisfied
- } else if (error.type === DependencyErrorType.ConfigUnsatisfied) {
- errorText = 'Config Not Satisfied'
- actionText = 'Auto Config'
- action = () => this.fixDep('configure', id)
- }
-
- if (localDep && localDep.state !== PackageState.Installed) {
- spinnerColor = localDep.state === PackageState.Removing ? 'danger' : 'primary'
- }
- }
-
- if (!this.dependencies[id]) this.dependencies[id] = { } as any
-
- const depInfo = this.pkg.installed['dependency-info'][id]
-
- this.dependencies[id].title = depInfo.manifest.title
- this.dependencies[id].icon = depInfo.icon
- this.dependencies[id].version = manifestDep.version
- this.dependencies[id].errorText = errorText
- this.dependencies[id].actionText = actionText
- this.dependencies[id].spinnerColor = spinnerColor
- this.dependencies[id].action = action
- }
- })
- // ** health
- if (this.pkg.installed.status.main.status === PackageMainStatus.Running) {
- this.healthChecks = { ...this.pkg.installed.status.main.health }
- } else {
- this.healthChecks = { }
+ if (localDep && localDep.state !== PackageState.Installed) {
+ spinnerColor = localDep.state === PackageState.Removing ? 'danger' : 'primary'
}
}
+
+ if (!this.dependencies[id]) this.dependencies[id] = { } as any
+
+ const depInfo = this.pkg.installed['dependency-info'][id]
+
+ this.dependencies[id].title = depInfo.manifest.title
+ this.dependencies[id].icon = depInfo.icon
+ this.dependencies[id].version = version
+ this.dependencies[id].errorText = errorText
+ this.dependencies[id].actionText = actionText
+ this.dependencies[id].spinnerColor = spinnerColor
+ this.dependencies[id].action = action
}
private async installDep (depId: string): Promise