diff --git a/ui/src/app/pages/apps-routes/app-properties/app-properties.page.html b/ui/src/app/pages/apps-routes/app-properties/app-properties.page.html index 37744ce7f..640a4e304 100644 --- a/ui/src/app/pages/apps-routes/app-properties/app-properties.page.html +++ b/ui/src/app/pages/apps-routes/app-properties/app-properties.page.html @@ -4,7 +4,7 @@ Properties - + Refresh @@ -56,7 +56,7 @@ - + diff --git a/ui/src/app/pages/apps-routes/app-properties/app-properties.page.ts b/ui/src/app/pages/apps-routes/app-properties/app-properties.page.ts index 70de05dcf..a852cf6be 100644 --- a/ui/src/app/pages/apps-routes/app-properties/app-properties.page.ts +++ b/ui/src/app/pages/apps-routes/app-properties/app-properties.page.ts @@ -3,7 +3,7 @@ import { ActivatedRoute } from '@angular/router' import { ApiService } from 'src/app/services/api/embassy-api.service' import { Subscription } from 'rxjs' import { copyToClipboard } from 'src/app/util/web.util' -import { AlertController, IonContent, NavController, PopoverController, ToastController } from '@ionic/angular' +import { AlertController, IonContent, ModalController, NavController, ToastController } from '@ionic/angular' import { PackageProperties } from 'src/app/util/properties.util' import { QRComponent } from 'src/app/components/qr/qr.component' import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' @@ -34,7 +34,7 @@ export class AppPropertiesPage { private readonly errToast: ErrorToastService, private readonly alertCtrl: AlertController, private readonly toastCtrl: ToastController, - private readonly popoverCtrl: PopoverController, + private readonly modalCtrl: ModalController, private readonly navCtrl: NavController, private readonly patch: PatchDbService, ) { } @@ -100,16 +100,15 @@ export class AppPropertiesPage { await toast.present() } - async showQR (text: string, ev: any): Promise { - const popover = await this.popoverCtrl.create({ + async showQR (text: string): Promise { + const modal = await this.modalCtrl.create({ component: QRComponent, - cssClass: 'qr-popover', - event: ev, componentProps: { text, }, + cssClass: 'qr-modal', }) - return await popover.present() + await modal.present() } toggleMask (key: string) { 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 8649318cb..13e7daf85 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 @@ -19,19 +19,11 @@ - + Status - - Dependencies: - - - - Health: - - @@ -44,15 +36,11 @@ Stop - - Fix - - + Start - @@ -87,55 +75,32 @@ - + + + Dependencies + + + + + + +

{{ dep.value.title }}

+

{{ dep.value.version | displayEmver }}

+

{{ dep.value.errorText || 'satisfied' }}

+
+ + + {{ dep.value.actionText }} + + +
+
Menu {{ button.title }} - - - - Dependencies - - - - - - -

{{ pkg.installed['dependency-info'][dep.key].manifest.title }}

-

{{ pkg.manifest.dependencies[dep.key].version | displayEmver }}

-

{{ pkg.installed.status['dependency-errors'][dep.key] ? pkg.installed.status['dependency-errors'][dep.key].type : 'satisfied' }}

-
- - - View - - - - - Install - - - - - Start - - - Update - - - Configure - - - -
- -
-
-
-
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 36e727d2d..6ceafe5ea 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, 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 { DependencyErrorConfigUnsatisfied, DependencyErrorType, HealthCheckResult, PackageDataEntry, PackageMainStatus, PackageState } from 'src/app/services/patch-db/data-model' +import { DependencyStatus, 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' @@ -27,16 +27,14 @@ export class AppShowPage { Math = Math PS = PrimaryStatus DS = DependencyStatus - HS = HealthStatus PR = PrimaryRendering - DR = DependencyRendering - HR = HealthRendering pkgId: string pkg: PackageDataEntry hideLAN: boolean buttons: Button[] = [] - currentDependencies: { [id: string]: CurrentDependencyInfo } + // currentDependencies: { [id: string]: CurrentDependencyInfo } + dependencies: { [id: string]: DependencyInfo } = { } statuses: { primary: PrimaryStatus dependency: DependencyStatus @@ -61,19 +59,19 @@ export class AppShowPage { private readonly wizardBaker: WizardBaker, private readonly config: ConfigService, private readonly packageLoadingService: PackageLoadingService, - public readonly patch: PatchDbService, - public readonly connectionService: ConnectionService, + private readonly patch: PatchDbService, + private readonly connectionService: ConnectionService, ) { } async ngOnInit () { this.pkgId = this.route.snapshot.paramMap.get('pkgId') - this.setValues(this.patch.data['package-data'][this.pkgId]) + this.setValues(this.patch.data['package-data']) this.subs = [ // 1 - this.patch.watch$('package-data', this.pkgId) - .subscribe(pkg => { - this.setValues(pkg) + this.patch.watch$('package-data') + .subscribe(pkgs => { + this.setValues(pkgs) }), // 2 this.connectionService.watchFailure$() @@ -148,13 +146,6 @@ export class AppShowPage { } } - scrollToRequirements () { - const el = document.getElementById('dependencies') - if (!el) return - let y = el.offsetTop - return this.content.scrollToPoint(0, y, 1000) - } - async fixDep (action: 'install' | 'update' | 'configure', id: string): Promise { switch (action) { case 'install': @@ -175,22 +166,87 @@ export class AppShowPage { await modal.present() } - private setValues (pkg: PackageDataEntry): void { - this.pkg = pkg - this.installProgress = !isEmptyObject(pkg['install-progress']) ? this.packageLoadingService.transform(pkg['install-progress']) : undefined - // we can safely ignore any current dependencies that are not defined in the service manifest - this.currentDependencies = { } - Object.entries(pkg.installed?.['current-dependencies'] || { }).forEach(([id, value]) => { - if (pkg.manifest.dependencies[id]) { - this.currentDependencies[id] = value - } - }) - if (pkg.installed?.status.main.status === PackageMainStatus.Running) { - this.healthChecks = { ...pkg.installed.status.main.health } - } else { + 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) + + 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 + + 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 = { } + } } - this.statuses = renderPkgStatus(pkg) } private async installDep (depId: string): Promise { @@ -334,6 +390,16 @@ export class AppShowPage { } } +interface DependencyInfo { + title: string + icon: string + version: string + errorText: string + spinnerColor: string + actionText: string + action: () => any +} + interface Button { title: string icon: string diff --git a/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show.page.html b/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show.page.html index 5c13449b4..6d7fe0c50 100644 --- a/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show.page.html +++ b/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show.page.html @@ -3,7 +3,7 @@ - Listing + Marketplace Listing diff --git a/ui/src/app/services/pkg-status-rendering.service.ts b/ui/src/app/services/pkg-status-rendering.service.ts index 81ffac2ec..4238dab7d 100644 --- a/ui/src/app/services/pkg-status-rendering.service.ts +++ b/ui/src/app/services/pkg-status-rendering.service.ts @@ -6,13 +6,14 @@ export function renderPkgStatus (pkg: PackageDataEntry): { dependency: DependencyStatus | null, health: HealthStatus | null } { + console.log('PKGPKG', pkg) let primary: PrimaryStatus let dependency: DependencyStatus | null = null let health: HealthStatus | null = null if (pkg.state === PackageState.Installed) { primary = pkg.installed.status.main.status as string as PrimaryStatus - dependency = getDependencyStatus(pkg.installed) + dependency = getDependencyStatus(pkg) health = getHealthStatus(pkg.installed.status) } else { primary = pkg.state as string as PrimaryStatus @@ -21,11 +22,11 @@ export function renderPkgStatus (pkg: PackageDataEntry): { return { primary, dependency, health } } -function getDependencyStatus (pkg: InstalledPackageDataEntry): DependencyStatus { - console.log('pkg', pkg) - if (isEmptyObject(pkg['current-dependencies'])) return null +function getDependencyStatus (pkg: PackageDataEntry): DependencyStatus { + const installed = pkg.installed + if (isEmptyObject(installed['current-dependencies'])) return null - const pkgIds = Object.keys(pkg.status['dependency-errors']) + const pkgIds = Object.keys(installed.status['dependency-errors']) for (let pkgId of pkgIds) { if (pkg.manifest.dependencies[pkgId].critical) { @@ -92,8 +93,8 @@ 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.Stopping]: { display: 'Stopping', color: 'dark-shade', showDots: true }, + [PrimaryStatus.Stopped]: { display: 'Stopped', color: 'dark-shade', 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 }, diff --git a/ui/src/global.scss b/ui/src/global.scss index 3b9aba558..70a01c772 100644 --- a/ui/src/global.scss +++ b/ui/src/global.scss @@ -174,6 +174,15 @@ ion-button { box-shadow: 0 0 70px 70px black; } +.qr-modal { + .modal-wrapper { + width: 400px !important; + height: 400px !important; + top: unset !important; + left: unset !important; + } +} + .modal-wrapper { position: absolute; height: 90% !important; @@ -236,11 +245,6 @@ ion-slides { } } -.qr-popover { - --width: auto; - --background: transparent !important; -} - ion-item-divider { text-transform: uppercase; --padding-top: 24px;