mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 12:11:56 +00:00
health page, manifest page, watch unauth, begin redesign
This commit is contained in:
committed by
Aiden McClelland
parent
409c550b5e
commit
21530ce63c
@@ -100,7 +100,7 @@ export class AppComponent {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
this.http.watch401$().subscribe(() => {
|
this.http.watchUnauth$().subscribe(() => {
|
||||||
this.authService.setUnverified()
|
this.authService.setUnverified()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ export interface Manifest {
|
|||||||
stop: string | null
|
stop: string | null
|
||||||
}
|
}
|
||||||
main: ActionImpl
|
main: ActionImpl
|
||||||
'health-check': ActionImpl
|
'health-checks': { [id: string]: ActionImpl & { critical: boolean } }
|
||||||
config: ConfigActions | null
|
config: ConfigActions | null
|
||||||
volumes: { [id: string]: Volume }
|
volumes: { [id: string]: Volume }
|
||||||
'min-os-version': string
|
'min-os-version': string
|
||||||
@@ -257,24 +257,31 @@ export enum PackageMainStatus {
|
|||||||
|
|
||||||
export type HealthCheckResult = HealthCheckResultWarmingUp | HealthCheckResultDisabled | HealthCheckResultSuccess | HealthCheckResultFailure
|
export type HealthCheckResult = HealthCheckResultWarmingUp | HealthCheckResultDisabled | HealthCheckResultSuccess | HealthCheckResultFailure
|
||||||
|
|
||||||
|
export enum HealthResult {
|
||||||
|
WarmingUp = 'warming-up',
|
||||||
|
Disabled = 'disabled',
|
||||||
|
Success = 'success',
|
||||||
|
Failure = 'failure',
|
||||||
|
}
|
||||||
|
|
||||||
export interface HealthCheckResultWarmingUp {
|
export interface HealthCheckResultWarmingUp {
|
||||||
time: string // UTC date string
|
time: string // UTC date string
|
||||||
result: 'warming-up'
|
result: HealthResult.WarmingUp
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HealthCheckResultDisabled {
|
export interface HealthCheckResultDisabled {
|
||||||
time: string // UTC date string
|
time: string // UTC date string
|
||||||
result: 'disabled'
|
result: HealthResult.Disabled
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HealthCheckResultSuccess {
|
export interface HealthCheckResultSuccess {
|
||||||
time: string // UTC date string
|
time: string // UTC date string
|
||||||
result: 'success'
|
result: HealthResult.Success
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HealthCheckResultFailure {
|
export interface HealthCheckResultFailure {
|
||||||
time: string // UTC date string
|
time: string // UTC date string
|
||||||
result: 'failure'
|
result: HealthResult.Failure
|
||||||
error: string
|
error: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,24 +9,19 @@
|
|||||||
|
|
||||||
<ion-content class="ion-padding-top">
|
<ion-content class="ion-padding-top">
|
||||||
<ng-container *ngIf="patch.watch$('package-data', pkgId, 'installed') | ngrxPush as installed">
|
<ng-container *ngIf="patch.watch$('package-data', pkgId, 'installed') | ngrxPush as installed">
|
||||||
<ng-container *ngIf="installed.manifest as manifest">
|
<ion-item-group>
|
||||||
|
<ion-item button *ngFor="let action of installed.manifest.actions | keyvalue: asIsOrder" (click)="handleAction(installed, action)" >
|
||||||
<ion-item *ngIf="manifest.actions | empty; else actions">
|
|
||||||
<ion-label class="ion-text-wrap">
|
<ion-label class="ion-text-wrap">
|
||||||
<p>No Actions for {{ manifest.title }} {{ manifest.versionInstalled }}.</p>
|
<h2><ion-text color="primary">{{ action.value.name }}</ion-text><ion-icon *ngIf="!(action.value['allowed-statuses'] | includes: installed.status.main.status)" color="danger" name="close-outline"></ion-icon></h2>
|
||||||
|
<p><ion-text color="dark">{{ action.value.description }}</ion-text></p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
<ion-item button (click)="uninstall(installed.manifest)" >
|
||||||
<ng-template #actions>
|
<ion-label class="ion-text-wrap">
|
||||||
<ion-item-group>
|
<h2><ion-text color="primary">Uninstall</ion-text></h2>
|
||||||
<ion-item button *ngFor="let action of manifest.actions | keyvalue: asIsOrder" (click)="handleAction(installed, action)" >
|
<p><ion-text color="dark">This will uninstall the service from your Embassy and delete all data permanently.</ion-text></p>
|
||||||
<ion-label class="ion-text-wrap">
|
</ion-label>
|
||||||
<h2><ion-text color="primary">{{ action.value.name }}</ion-text><ion-icon *ngIf="!(action.value['allowed-statuses'] | includes: installed.status.main.status)" color="danger" name="close-outline"></ion-icon></h2>
|
</ion-item>
|
||||||
<p><ion-text color="dark">{{ action.value.description }}</ion-text></p>
|
</ion-item-group>
|
||||||
</ion-label>
|
|
||||||
</ion-item>
|
|
||||||
</ion-item-group>
|
|
||||||
</ng-template>
|
|
||||||
</ng-container>
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
import { Component } from '@angular/core'
|
import { Component } from '@angular/core'
|
||||||
import { ActivatedRoute } from '@angular/router'
|
import { ActivatedRoute } from '@angular/router'
|
||||||
import { ApiService } from 'src/app/services/api/api.service'
|
import { ApiService } from 'src/app/services/api/api.service'
|
||||||
import { AlertController } from '@ionic/angular'
|
import { AlertController, ModalController, NavController } from '@ionic/angular'
|
||||||
import { LoaderService } from 'src/app/services/loader.service'
|
import { LoaderService } from 'src/app/services/loader.service'
|
||||||
import { HttpErrorResponse } from '@angular/common/http'
|
import { HttpErrorResponse } from '@angular/common/http'
|
||||||
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
|
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
|
||||||
import { Action, InstalledPackageDataEntry, PackageMainStatus } from 'src/app/models/patch-db/data-model'
|
import { Action, InstalledPackageDataEntry, Manifest, PackageMainStatus } from 'src/app/models/patch-db/data-model'
|
||||||
|
import { wizardModal } from 'src/app/components/install-wizard/install-wizard.component'
|
||||||
|
import { WizardBaker } from 'src/app/components/install-wizard/prebaked-wizards'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-actions',
|
selector: 'app-actions',
|
||||||
@@ -18,8 +20,11 @@ export class AppActionsPage {
|
|||||||
constructor (
|
constructor (
|
||||||
private readonly route: ActivatedRoute,
|
private readonly route: ActivatedRoute,
|
||||||
private readonly apiService: ApiService,
|
private readonly apiService: ApiService,
|
||||||
|
private readonly modalCtrl: ModalController,
|
||||||
private readonly alertCtrl: AlertController,
|
private readonly alertCtrl: AlertController,
|
||||||
private readonly loaderService: LoaderService,
|
private readonly loaderService: LoaderService,
|
||||||
|
private readonly wizardBaker: WizardBaker,
|
||||||
|
private readonly navCtrl: NavController,
|
||||||
public readonly patch: PatchDbModel,
|
public readonly patch: PatchDbModel,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
@@ -71,6 +76,22 @@ export class AppActionsPage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async uninstall (manifest: Manifest) {
|
||||||
|
const { id, title, version, alerts } = manifest
|
||||||
|
const data = await wizardModal(
|
||||||
|
this.modalCtrl,
|
||||||
|
this.wizardBaker.uninstall({
|
||||||
|
id,
|
||||||
|
title,
|
||||||
|
version,
|
||||||
|
uninstallAlert: alerts.uninstall,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
if (data.cancelled) return
|
||||||
|
return this.navCtrl.navigateRoot('/services/installed')
|
||||||
|
}
|
||||||
|
|
||||||
private async executeAction (pkgId: string, actionId: string) {
|
private async executeAction (pkgId: string, actionId: string) {
|
||||||
try {
|
try {
|
||||||
const res = await this.loaderService.displayDuringP(
|
const res = await this.loaderService.displayDuringP(
|
||||||
|
|||||||
@@ -10,172 +10,129 @@
|
|||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
<ion-content class="ion-padding-bottom">
|
<ion-content class="ion-padding-top">
|
||||||
|
|
||||||
<ion-item *ngIf="error" style="margin-bottom: 16px;">
|
<ion-item *ngIf="error" style="margin-bottom: 16px;">
|
||||||
<ion-text class="ion-text-wrap" color="danger">{{ error }}</ion-text>
|
<ion-text class="ion-text-wrap" color="danger">{{ error }}</ion-text>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<ng-container *ngrxLet="connectionService.monitor$() as connection">
|
<ng-container *ngrxLet="connectionService.monitor$() as connection">
|
||||||
<ng-container *ngIf="pkg | manifest as manifest">
|
<ng-container *ngIf="pkg | status : connection as status">
|
||||||
<ng-container *ngIf="pkg | status : connection as status">
|
<!-- top plate -->
|
||||||
<div class="top-plate">
|
<div class="top-plate">
|
||||||
<ion-item class="no-cushion-item" lines="none">
|
<ion-item class="no-cushion-item" lines="none">
|
||||||
<ion-label class="ion-text-wrap" style="
|
<ion-label class="ion-text-wrap" style="
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 80px auto;
|
grid-template-columns: 80px auto;
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
margin-top: 15px;"
|
margin-top: 15px;"
|
||||||
>
|
>
|
||||||
<ion-avatar style="justify-self: center; height: 55px; width: 55px" slot="start">
|
<ion-avatar style="justify-self: center; height: 55px; width: 55px" slot="start">
|
||||||
<img [src]="pkg['static-files'].icon" />
|
<img [src]="pkg['static-files'].icon" />
|
||||||
</ion-avatar>
|
</ion-avatar>
|
||||||
<div style="display: flex; flex-direction: column;">
|
<div style="display: flex; flex-direction: column;">
|
||||||
<ion-text style="font-family: 'Montserrat'; font-size: x-large; line-height: normal;" [class.less-large]="manifest.title.length > 20">
|
<ion-text style="font-family: 'Montserrat'; font-size: x-large; line-height: normal;" [class.less-large]="manifest.title.length > 20">
|
||||||
{{ manifest.title }}
|
{{ manifest.title }}
|
||||||
</ion-text>
|
</ion-text>
|
||||||
<ion-text style="margin-top: -5px; margin-left: 2px;">
|
<ion-text style="margin-top: -5px; margin-left: 2px;">
|
||||||
{{ manifest.version | displayEmver }}
|
{{ manifest.version | displayEmver }}
|
||||||
</ion-text>
|
</ion-text>
|
||||||
</div>
|
</div>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<ion-item class="no-cushion-item" lines=none style="margin-bottom: 10px;">
|
<ion-item class="no-cushion-item" lines="none" style="margin-bottom: 10px;">
|
||||||
<ion-label class="status-readout">
|
<ion-label class="status-readout">
|
||||||
<status size="bold-large" [pkg]="pkg" [connection]="connection"></status>
|
<status size="bold-large" [pkg]="pkg" [connection]="connection"></status>
|
||||||
<ion-button *ngIf="status === FeStatus.NeedsConfig" expand="block" fill="outline" [routerLink]="['config']">
|
<ion-button *ngIf="status === FeStatus.NeedsConfig" expand="block" fill="outline" [routerLink]="['config']">
|
||||||
Configure
|
Configure
|
||||||
</ion-button>
|
</ion-button>
|
||||||
<ion-button *ngIf="status === FeStatus.Running" expand="block" fill="outline" color="danger" (click)="stop()">
|
<ion-button *ngIf="[FeStatus.Running, FeStatus.StartingUp, FeStatus.NeedsAttention] | includes : status" expand="block" fill="outline" color="danger" (click)="stop()">
|
||||||
Stop
|
Stop
|
||||||
</ion-button>
|
</ion-button>
|
||||||
<ion-button *ngIf="status === FeStatus.DependencyIssue" expand="block" fill="outline" (click)="scrollToRequirements()">
|
<ion-button *ngIf="status === FeStatus.DependencyIssue" expand="block" fill="outline" (click)="scrollToRequirements()">
|
||||||
Fix
|
Fix
|
||||||
</ion-button>
|
</ion-button>
|
||||||
<ion-button *ngIf="status === FeStatus.Stopped" expand="block" fill="outline" color="success" (click)="tryStart()">
|
<ion-button *ngIf="status === FeStatus.Stopped" expand="block" fill="outline" color="success" (click)="tryStart()">
|
||||||
Start
|
Start
|
||||||
</ion-button>
|
</ion-button>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<ion-button size="small" *ngIf="pkg | hasUi" [disabled]="!(pkg | isLaunchable)" class="launch-button" expand="block" (click)="launchUiTab()">
|
<ion-button size="small" *ngIf="pkg | hasUi" [disabled]="!(pkg | isLaunchable)" class="launch-button" expand="block" (click)="launchUiTab()">
|
||||||
Launch Web Interface
|
Launch Web Interface
|
||||||
<ion-icon slot="end" name="rocket-outline"></ion-icon>
|
<ion-icon slot="end" name="rocket-outline"></ion-icon>
|
||||||
</ion-button>
|
</ion-button>
|
||||||
|
</div>
|
||||||
</div>
|
|
||||||
|
<ng-container *ngIf="!([FeStatus.Installing, FeStatus.Updating, FeStatus.Removing] | includes : status)">
|
||||||
<ng-container *ngIf="!([FeStatus.Installing, FeStatus.Updating, FeStatus.Removing] | includes : status)">
|
<ion-grid class="ion-text-center" style="margin: 0 6px;">
|
||||||
<ion-item-group class="ion-padding-bottom">
|
<ion-row>
|
||||||
<!-- interfaces -->
|
<ion-col *ngFor="let button of buttons" sizeMd="4" sizeSm="6" sizeXs="6">
|
||||||
<ion-item [routerLink]="['interfaces']">
|
<ion-button style="width: 100% !important; min-height: 120px; --background: #2f4858;" [disabled]="button.disabled | includes : status" (click)="button.action()">
|
||||||
<ion-icon slot="start" name="aperture-outline" color="primary"></ion-icon>
|
<div>
|
||||||
<ion-label><ion-text color="primary">Interfaces</ion-text></ion-label>
|
<ion-icon size="large" [name]="button.icon" style="color: #fbe6ea"></ion-icon>
|
||||||
</ion-item>
|
<br/><br/>
|
||||||
<!-- instructions -->
|
<ion-text style="color: #fbe6ea">{{ button.title }}</ion-text>
|
||||||
<ion-item [routerLink]="['instructions']">
|
</div>
|
||||||
<ion-icon slot="start" name="list-outline" color="primary"></ion-icon>
|
|
||||||
<ion-label><ion-text color="primary">Instructions</ion-text></ion-label>
|
|
||||||
</ion-item>
|
|
||||||
<!-- config -->
|
|
||||||
<ion-item [disabled]="[FeStatus.Installing, FeStatus.Updating, FeStatus.Removing, FeStatus.BackingUp, FeStatus.Restoring] | includes : status" [routerLink]="['config']">
|
|
||||||
<ion-icon slot="start" name="construct-outline" color="primary"></ion-icon>
|
|
||||||
<ion-label><ion-text color="primary">Config</ion-text></ion-label>
|
|
||||||
</ion-item>
|
|
||||||
<!-- properties -->
|
|
||||||
<ion-item [routerLink]="['properties']">
|
|
||||||
<ion-icon slot="start" name="information-circle-outline" color="primary"></ion-icon>
|
|
||||||
<ion-label><ion-text color="primary">Properties</ion-text></ion-label>
|
|
||||||
</ion-item>
|
|
||||||
<!-- actions -->
|
|
||||||
<ion-item [routerLink]="['actions']">
|
|
||||||
<ion-icon slot="start" name="flash-outline" color="primary"></ion-icon>
|
|
||||||
<ion-label><ion-text color="primary">Actions</ion-text></ion-label>
|
|
||||||
</ion-item>
|
|
||||||
<!-- logs -->
|
|
||||||
<ion-item [routerLink]="['logs']">
|
|
||||||
<ion-icon slot="start" name="newspaper-outline" color="primary"></ion-icon>
|
|
||||||
<ion-label><ion-text color="primary">Logs</ion-text></ion-label>
|
|
||||||
</ion-item>
|
|
||||||
<!-- restore -->
|
|
||||||
<ion-item button [disabled]="[FeStatus.Connecting, FeStatus.Installing, FeStatus.Updating, FeStatus.Stopping, FeStatus.Removing, FeStatus.BackingUp, FeStatus.Restoring] | includes : status" [routerLink]="['restore']">
|
|
||||||
<ion-icon slot="start" name="color-wand-outline" color="primary"></ion-icon>
|
|
||||||
<ion-label><ion-text color="primary">Restore from Backup</ion-text></ion-label>
|
|
||||||
</ion-item>
|
|
||||||
<!-- donate -->
|
|
||||||
<ion-item button (click)="donate(manifest)">
|
|
||||||
<ion-icon slot="start" name="shapes-outline" color="primary"></ion-icon>
|
|
||||||
<ion-label><ion-text color="primary">Donate</ion-text></ion-label>
|
|
||||||
</ion-item>
|
|
||||||
<!-- marketplace -->
|
|
||||||
<ion-item [routerLink]="['/services', 'marketplace', manifest.id]">
|
|
||||||
<ion-icon slot="start" name="storefront-outline" color="primary"></ion-icon>
|
|
||||||
<ion-label><ion-text color="primary">Marketplace Listing</ion-text></ion-label>
|
|
||||||
</ion-item>
|
|
||||||
|
|
||||||
<!-- dependencies -->
|
|
||||||
<ng-container *ngIf="!(manifest.dependencies | empty)">
|
|
||||||
<ion-item-divider id="dependencies">
|
|
||||||
Dependencies
|
|
||||||
<ion-button style="position: relative; right: 10px;" size="small" fill="clear" color="medium" (click)="presentPopover(depDefinition, $event)">
|
|
||||||
<ion-icon name="help-circle-outline"></ion-icon>
|
|
||||||
</ion-button>
|
</ion-button>
|
||||||
</ion-item-divider>
|
</ion-col>
|
||||||
|
</ion-row>
|
||||||
|
</ion-grid>
|
||||||
|
|
||||||
<div *ngFor="let dep of pkg.installed['current-dependencies'] | keyvalue">
|
<ion-item-group class="ion-padding-bottom">
|
||||||
<ion-item *ngrxLet="patch.watch$('package-data', dep.key) as localDep" class="dependency-item" lines="none">
|
<!-- dependencies -->
|
||||||
<ion-avatar slot="start" style="position: relative; height: 5vh; width: 5vh; margin: 0px;">
|
<ng-container *ngIf="!(pkg.installed['current-dependencies'] | empty)">
|
||||||
<div class="dep-badge" [class]="pkg.installed.status['dependency-errors'][dep.key] ? 'dep-issue' : 'dep-sat'"></div>
|
<ion-card id="dependencies" class="dep-card">
|
||||||
<img [src]="localDep ? localDep['static-files'].icon : pkg.installed.status['dependency-errors'][dep.key]?.icon" />
|
<ion-card-header>
|
||||||
</ion-avatar>
|
<ion-card-title>Dependencies</ion-card-title>
|
||||||
<ion-label class="ion-text-wrap" style="padding: 1vh; padding-left: 2vh">
|
</ion-card-header>
|
||||||
<h4 style="font-family: 'Montserrat'">{{ localDep ? (localDep | manifest).title : pkg.installed.status['dependency-errors'][dep.key]?.title }}</h4>
|
<ion-card-content>
|
||||||
<p style="font-size: small">{{ manifest.dependencies[dep.key].version | displayEmver }}</p>
|
<!-- A current-dependency is a subset of the manifest.dependencies that is currently required as determined by the service config. -->
|
||||||
<p style="padding-top: 2px; position: relative; font-style: italic; font-size: smaller"><ion-text [color]="pkg.installed.status['dependency-errors'][dep.key] ? 'warning' : 'success'">{{ pkg.installed.status['dependency-errors'][dep.key] ? pkg.installed.status['dependency-errors'][dep.key].type : 'satisfied' }}</ion-text></p>
|
<div *ngFor="let dep of pkg.installed['current-dependencies'] | keyvalue">
|
||||||
</ion-label>
|
<ion-item *ngrxLet="patch.watch$('package-data', dep.key) as localDep" class="dependency-item">
|
||||||
|
<ion-avatar slot="start" style="position: relative; height: 5vh; width: 5vh; margin: 0px;">
|
||||||
|
<div class="dep-badge" [class]="pkg.installed.status['dependency-errors'][dep.key] ? 'dep-issue' : 'dep-sat'"></div>
|
||||||
|
<img [src]="localDep ? localDep['static-files'].icon : pkg.installed.status['dependency-errors'][dep.key]?.icon" />
|
||||||
|
</ion-avatar>
|
||||||
|
<ion-label class="ion-text-wrap" style="padding: 1vh; padding-left: 2vh">
|
||||||
|
<h4 style="font-family: 'Montserrat'">{{ localDep ? (localDep | manifest).title : pkg.installed.status['dependency-errors'][dep.key]?.title }}</h4>
|
||||||
|
<p style="font-size: small">{{ manifest.dependencies[dep.key].version | displayEmver }}</p>
|
||||||
|
<p style="padding-top: 2px; position: relative; font-style: italic; font-size: smaller"><ion-text [color]="pkg.installed.status['dependency-errors'][dep.key] ? 'warning' : 'success'">{{ pkg.installed.status['dependency-errors'][dep.key] ? pkg.installed.status['dependency-errors'][dep.key].type : 'satisfied' }}</ion-text></p>
|
||||||
|
</ion-label>
|
||||||
|
|
||||||
<ion-button *ngIf="!pkg.installed.status['dependency-errors'][dep.key] || (pkg.installed.status['dependency-errors'][dep.key] && [DependencyErrorType.InterfaceHealthChecksFailed, DependencyErrorType.HealthChecksFailed] | includes : pkg.installed.status['dependency-errors'][dep.key].type)" slot="end" size="small" [routerLink]="['/services', 'installed', dep.key]" color="primary" fill="outline" style="font-size: x-small">
|
<ion-button *ngIf="!pkg.installed.status['dependency-errors'][dep.key] || (pkg.installed.status['dependency-errors'][dep.key] && [DependencyErrorType.InterfaceHealthChecksFailed, DependencyErrorType.HealthChecksFailed] | includes : pkg.installed.status['dependency-errors'][dep.key].type)" slot="end" size="small" [routerLink]="['/services', 'installed', dep.key]" color="primary" fill="outline" style="font-size: x-small">
|
||||||
View
|
View
|
||||||
</ion-button>
|
|
||||||
|
|
||||||
<ng-container *ngIf="pkg.installed.status['dependency-errors'][dep.key]">
|
|
||||||
<ion-button *ngIf="!localDep" slot="end" size="small" (click)="fixDep('install', dep.key)" color="primary" fill="outline" style="font-size: x-small">
|
|
||||||
Install
|
|
||||||
</ion-button>
|
</ion-button>
|
||||||
|
|
||||||
<ng-container *ngIf="localDep && localDep.state === PackageState.Installed">
|
<ng-container *ngIf="pkg.installed.status['dependency-errors'][dep.key]">
|
||||||
<ion-button *ngIf="pkg.installed.status['dependency-errors'][dep.key].type === DependencyErrorType.NotRunning" slot="end" size="small" [routerLink]="['/services', 'installed', dep.key]" color="primary" fill="outline" style="font-size: x-small">
|
<ion-button *ngIf="!localDep" slot="end" size="small" (click)="fixDep('install', dep.key)" color="primary" fill="outline" style="font-size: x-small">
|
||||||
Start
|
Install
|
||||||
</ion-button>
|
</ion-button>
|
||||||
<ion-button *ngIf="pkg.installed.status['dependency-errors'][dep.key].type === DependencyErrorType.IncorrectVersion" slot="end" size="small" (click)="fixDep('update', dep.key)" color="primary" fill="outline" style="font-size: x-small">
|
|
||||||
Update
|
|
||||||
</ion-button>
|
|
||||||
<ion-button *ngIf="pkg.installed.status['dependency-errors'][dep.key].type === DependencyErrorType.ConfigUnsatisfied" slot="end" size="small" (click)="fixDep('configure', dep.key)" color="primary" fill="outline" style="font-size: x-small">
|
|
||||||
Configure
|
|
||||||
</ion-button>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<div *ngIf="localDep && localDep.state !== PackageState.Installed" slot="end" class="spinner">
|
<ng-container *ngIf="localDep && localDep.state === PackageState.Installed">
|
||||||
<ion-spinner [color]="localDep.state === PackageState.Removing ? 'danger' : 'primary'" style="height: 3vh; width: 3vh" name="dots"></ion-spinner>
|
<ion-button *ngIf="pkg.installed.status['dependency-errors'][dep.key].type === DependencyErrorType.NotRunning" slot="end" size="small" [routerLink]="['/services', 'installed', dep.key]" color="primary" fill="outline" style="font-size: x-small">
|
||||||
</div>
|
Start
|
||||||
|
</ion-button>
|
||||||
|
<ion-button *ngIf="pkg.installed.status['dependency-errors'][dep.key].type === DependencyErrorType.IncorrectVersion" slot="end" size="small" (click)="fixDep('update', dep.key)" color="primary" fill="outline" style="font-size: x-small">
|
||||||
|
Update
|
||||||
|
</ion-button>
|
||||||
|
<ion-button *ngIf="pkg.installed.status['dependency-errors'][dep.key].type === DependencyErrorType.ConfigUnsatisfied" slot="end" size="small" (click)="fixDep('configure', dep.key)" color="primary" fill="outline" style="font-size: x-small">
|
||||||
|
Configure
|
||||||
|
</ion-button>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
</ng-container>
|
<div *ngIf="localDep && localDep.state !== PackageState.Installed" slot="end" class="spinner">
|
||||||
</ion-item>
|
<ion-spinner [color]="localDep.state === PackageState.Removing ? 'danger' : 'primary'" style="height: 3vh; width: 3vh" name="dots"></ion-spinner>
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
|
||||||
|
</ng-container>
|
||||||
<ion-item-divider></ion-item-divider>
|
</ion-item>
|
||||||
|
</div>
|
||||||
<ng-container *ngIf="!([FeStatus.Installing, FeStatus.Updating, FeStatus.BackingUp, FeStatus.Restoring] | includes : status)">
|
</ion-card-content>
|
||||||
<!-- uninstall -->
|
</ion-card>
|
||||||
<ion-item button (click)="uninstall()">
|
</ng-container>
|
||||||
<ion-icon slot="start" name="trash-outline" color="danger"></ion-icon>
|
</ion-item-group>
|
||||||
<ion-label><ion-text color="danger">Uninstall</ion-text></ion-label>
|
|
||||||
</ion-item>
|
|
||||||
</ng-container>
|
|
||||||
</ion-item-group>
|
|
||||||
</ng-container>
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
.top-plate {
|
.top-plate {
|
||||||
background: var(--ion-item-background);
|
background: var(--ion-item-background);
|
||||||
margin: 20px 10px;
|
margin: 0 16px;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-color: #373737;
|
border-color: #373737;
|
||||||
@@ -49,6 +49,12 @@
|
|||||||
margin: 12px 10px;
|
margin: 12px 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dep-card {
|
||||||
|
border-radius: 10px;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: #404040;
|
||||||
|
}
|
||||||
|
|
||||||
.dep-badge {
|
.dep-badge {
|
||||||
position: absolute; width: 2.5vh;
|
position: absolute; width: 2.5vh;
|
||||||
height: 2.5vh;
|
height: 2.5vh;
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import { Component, ViewChild } from '@angular/core'
|
import { Component, ViewChild } from '@angular/core'
|
||||||
import { AlertController, NavController, ModalController, IonContent, PopoverController } from '@ionic/angular'
|
import { AlertController, NavController, ModalController, IonContent, PopoverController } from '@ionic/angular'
|
||||||
import { ApiService } from 'src/app/services/api/api.service'
|
import { ApiService } from 'src/app/services/api/api.service'
|
||||||
import { ActivatedRoute, NavigationExtras } from '@angular/router'
|
import { ActivatedRoute, NavigationExtras, Router } from '@angular/router'
|
||||||
import { chill, isEmptyObject, pauseFor } from 'src/app/util/misc.util'
|
import { chill, isEmptyObject } from 'src/app/util/misc.util'
|
||||||
import { LoaderService } from 'src/app/services/loader.service'
|
import { LoaderService } from 'src/app/services/loader.service'
|
||||||
import { Observable, of, Subscription } from 'rxjs'
|
import { Observable, of, Subscription } from 'rxjs'
|
||||||
import { wizardModal } from 'src/app/components/install-wizard/install-wizard.component'
|
import { wizardModal } from 'src/app/components/install-wizard/install-wizard.component'
|
||||||
import { WizardBaker } from 'src/app/components/install-wizard/prebaked-wizards'
|
import { WizardBaker } from 'src/app/components/install-wizard/prebaked-wizards'
|
||||||
import { InformationPopoverComponent } from 'src/app/components/information-popover/information-popover.component'
|
import { InformationPopoverComponent } from 'src/app/components/information-popover/information-popover.component'
|
||||||
import { ConfigService } from 'src/app/services/config.service'
|
import { ConfigService, getManifest } from 'src/app/services/config.service'
|
||||||
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
|
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
|
||||||
import { DependencyErrorConfigUnsatisfied, DependencyErrorNotInstalled, DependencyErrorType, Manifest, PackageDataEntry, PackageState } from 'src/app/models/patch-db/data-model'
|
import { DependencyErrorConfigUnsatisfied, DependencyErrorNotInstalled, DependencyErrorType, Manifest, PackageDataEntry, PackageState } from 'src/app/models/patch-db/data-model'
|
||||||
import { FEStatus } from 'src/app/services/pkg-status-rendering.service'
|
import { FEStatus } from 'src/app/services/pkg-status-rendering.service'
|
||||||
@@ -26,13 +26,13 @@ export class AppInstalledShowPage {
|
|||||||
pkg: PackageDataEntry
|
pkg: PackageDataEntry
|
||||||
pkgSub: Subscription
|
pkgSub: Subscription
|
||||||
hideLAN: boolean
|
hideLAN: boolean
|
||||||
|
buttons: Button[] = []
|
||||||
|
manifest: Manifest = { } as Manifest
|
||||||
|
|
||||||
FeStatus = FEStatus
|
FeStatus = FEStatus
|
||||||
PackageState = PackageState
|
PackageState = PackageState
|
||||||
DependencyErrorType = DependencyErrorType
|
DependencyErrorType = DependencyErrorType
|
||||||
|
|
||||||
depDefinition = '<span style="font-style: italic">Service Dependencies</span> are other services that this service recommends or requires in order to run.'
|
|
||||||
|
|
||||||
@ViewChild(IonContent) content: IonContent
|
@ViewChild(IonContent) content: IonContent
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
@@ -51,7 +51,11 @@ export class AppInstalledShowPage {
|
|||||||
|
|
||||||
async ngOnInit () {
|
async ngOnInit () {
|
||||||
this.pkgId = this.route.snapshot.paramMap.get('pkgId')
|
this.pkgId = this.route.snapshot.paramMap.get('pkgId')
|
||||||
this.pkgSub = this.patch.watch$('package-data', this.pkgId).subscribe(pkg => this.pkg = pkg)
|
this.pkgSub = this.patch.watch$('package-data', this.pkgId).subscribe(pkg => {
|
||||||
|
this.pkg = pkg
|
||||||
|
this.manifest = getManifest(this.pkg)
|
||||||
|
})
|
||||||
|
this.setButtons()
|
||||||
}
|
}
|
||||||
|
|
||||||
async ngAfterViewInit () {
|
async ngAfterViewInit () {
|
||||||
@@ -102,30 +106,14 @@ export class AppInstalledShowPage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async uninstall () {
|
async donate (): Promise<void> {
|
||||||
const { id, title, version, alerts } = this.pkg.installed.manifest
|
const url = this.manifest['donation-url']
|
||||||
const data = await wizardModal(
|
|
||||||
this.modalCtrl,
|
|
||||||
this.wizardBaker.uninstall({
|
|
||||||
id,
|
|
||||||
title,
|
|
||||||
version,
|
|
||||||
uninstallAlert: alerts.uninstall,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
if (data.cancelled) return
|
|
||||||
return this.navCtrl.navigateRoot('/services/installed')
|
|
||||||
}
|
|
||||||
|
|
||||||
async donate (manifest: Manifest): Promise<void> {
|
|
||||||
const url = manifest['donation-url']
|
|
||||||
if (url) {
|
if (url) {
|
||||||
window.open(url, '_blank')
|
window.open(url, '_blank')
|
||||||
} else {
|
} else {
|
||||||
const alert = await this.alertCtrl.create({
|
const alert = await this.alertCtrl.create({
|
||||||
header: 'Not Accepting Donations',
|
header: 'Not Accepting Donations',
|
||||||
message: `The developers of ${manifest.title} have not provided a donation URL. Please contact them directly if you insist on giving them money.`,
|
message: `The developers of ${this.manifest.title} have not provided a donation URL. Please contact them directly if you insist on giving them money.`,
|
||||||
buttons: ['OK'],
|
buttons: ['OK'],
|
||||||
})
|
})
|
||||||
await alert.present()
|
await alert.present()
|
||||||
@@ -163,6 +151,10 @@ export class AppInstalledShowPage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
asIsOrder () {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
private async installDep (depId: string): Promise<void> {
|
private async installDep (depId: string): Promise<void> {
|
||||||
const version = this.pkg.installed.manifest.dependencies[depId].version
|
const version = this.pkg.installed.manifest.dependencies[depId].version
|
||||||
const dependentTitle = this.pkg.installed.manifest.title
|
const dependentTitle = this.pkg.installed.manifest.title
|
||||||
@@ -234,4 +226,94 @@ export class AppInstalledShowPage {
|
|||||||
this.error = e.message
|
this.error = e.message
|
||||||
return of()
|
return of()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setButtons (): void {
|
||||||
|
this.buttons = [
|
||||||
|
{
|
||||||
|
action: () => this.navCtrl.navigateForward(['metrics'], { relativeTo: this.route }),
|
||||||
|
title: 'Health',
|
||||||
|
icon: 'medkit-outline',
|
||||||
|
color: 'danger',
|
||||||
|
disabled: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: () => this.navCtrl.navigateForward(['instructions'], { relativeTo: this.route }),
|
||||||
|
title: 'Instructions',
|
||||||
|
icon: 'list-outline',
|
||||||
|
color: 'danger',
|
||||||
|
disabled: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: () => this.navCtrl.navigateForward(['config'], { relativeTo: this.route }),
|
||||||
|
title: 'Configure',
|
||||||
|
icon: 'construct-outline',
|
||||||
|
color: 'danger',
|
||||||
|
disabled: [FEStatus.Installing, FEStatus.Updating, FEStatus.Removing, FEStatus.BackingUp, FEStatus.Restoring],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: () => this.navCtrl.navigateForward(['properties'], { relativeTo: this.route }),
|
||||||
|
title: 'Values',
|
||||||
|
icon: 'briefcase-outline',
|
||||||
|
color: 'danger',
|
||||||
|
disabled: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: () => this.navCtrl.navigateForward(['interfaces'], { relativeTo: this.route }),
|
||||||
|
title: 'Interfaces',
|
||||||
|
icon: 'desktop-outline',
|
||||||
|
color: 'danger',
|
||||||
|
disabled: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: () => this.navCtrl.navigateForward(['actions'], { relativeTo: this.route }),
|
||||||
|
title: 'Actions',
|
||||||
|
icon: 'flash-outline',
|
||||||
|
color: 'danger',
|
||||||
|
disabled: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: () => this.navCtrl.navigateForward(['logs'], { relativeTo: this.route }),
|
||||||
|
title: 'Logs',
|
||||||
|
icon: 'receipt-outline',
|
||||||
|
color: 'danger',
|
||||||
|
disabled: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: () => this.navCtrl.navigateForward(['restore'], { relativeTo: this.route }),
|
||||||
|
title: 'Restore Backup',
|
||||||
|
icon: 'color-wand-outline',
|
||||||
|
color: 'danger',
|
||||||
|
disabled: [FEStatus.Connecting, FEStatus.Installing, FEStatus.Updating, FEStatus.Stopping, FEStatus.Removing, FEStatus.BackingUp, FEStatus.Restoring],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: () => this.navCtrl.navigateForward(['manifest'], { relativeTo: this.route }),
|
||||||
|
title: 'Package Manifest',
|
||||||
|
icon: 'finger-print-outline',
|
||||||
|
color: 'danger',
|
||||||
|
disabled: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: () => this.donate(),
|
||||||
|
title: 'Support Project',
|
||||||
|
icon: 'logo-bitcoin',
|
||||||
|
color: 'danger',
|
||||||
|
disabled: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: () => this.navCtrl.navigateForward(['/services', 'marketplace', this.pkgId], { relativeTo: this.route }),
|
||||||
|
title: 'Marketplace Listing',
|
||||||
|
icon: 'storefront-outline',
|
||||||
|
color: 'danger',
|
||||||
|
disabled: [],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Button {
|
||||||
|
title: string
|
||||||
|
icon: string
|
||||||
|
color: string
|
||||||
|
disabled: FEStatus[]
|
||||||
|
action: Function
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,11 +40,6 @@
|
|||||||
<ion-icon size="small" slot="icon-only" name="copy-outline"></ion-icon>
|
<ion-icon size="small" slot="icon-only" name="copy-outline"></ion-icon>
|
||||||
</ion-button>
|
</ion-button>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item button detail="true">
|
|
||||||
<ion-label class="ion-text-wrap">
|
|
||||||
Advanced
|
|
||||||
</ion-label>
|
|
||||||
</ion-item>
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ion-card-content>
|
</ion-card-content>
|
||||||
</ion-card>
|
</ion-card>
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { NgModule } from '@angular/core'
|
||||||
|
import { CommonModule } from '@angular/common'
|
||||||
|
import { Routes, RouterModule } from '@angular/router'
|
||||||
|
import { IonicModule } from '@ionic/angular'
|
||||||
|
import { AppManifestPage } from './app-manifest.page'
|
||||||
|
import { PwaBackComponentModule } from 'src/app/components/pwa-back-button/pwa-back.component.module'
|
||||||
|
import { SharingModule } from 'src/app/modules/sharing.module'
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: AppManifestPage,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
IonicModule,
|
||||||
|
RouterModule.forChild(routes),
|
||||||
|
PwaBackComponentModule,
|
||||||
|
SharingModule,
|
||||||
|
],
|
||||||
|
declarations: [AppManifestPage],
|
||||||
|
})
|
||||||
|
export class AppManifestPageModule { }
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
<ion-header>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-buttons slot="start">
|
||||||
|
<pwa-back-button></pwa-back-button>
|
||||||
|
</ion-buttons>
|
||||||
|
<ion-title>Manifest</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
|
||||||
|
<ion-content *ngIf="pkg">
|
||||||
|
<ion-item *ngIf="error" style="margin-bottom: 16px;">
|
||||||
|
<ion-text class="ion-text-wrap" color="danger">{{ error }}</ion-text>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item-group *ngIf="node | typeof === 'object'">
|
||||||
|
<div *ngFor="let prop of node | keyvalue : asIsOrder">
|
||||||
|
<!-- object/array -->
|
||||||
|
<ng-container *ngIf="['object', 'array'] | includes : (prop.value | typeof); else notObj">
|
||||||
|
<ion-item button detail="true" *ngIf="!(prop.value | empty)" (click)="goToNested(prop.key)">
|
||||||
|
<ion-label class="ion-text-wrap">
|
||||||
|
<h2>{{ prop.key }}</h2>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
</ng-container>
|
||||||
|
<!-- not object/array -->
|
||||||
|
<ng-template #notObj>
|
||||||
|
<ion-item *ngIf="prop.value">
|
||||||
|
<ion-label class="ion-text-wrap">
|
||||||
|
<h2>{{ prop.key }}</h2>
|
||||||
|
<p>{{ prop.value }}</p>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
</ng-template>
|
||||||
|
</div>
|
||||||
|
</ion-item-group>
|
||||||
|
<!-- Arrays -->
|
||||||
|
<ion-item-group *ngIf="node | typeof === 'array'">
|
||||||
|
<ion-item *ngFor="let prop of node">
|
||||||
|
<ion-label class="ion-text-wrap">
|
||||||
|
{{ prop }}
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
</ion-item-group>
|
||||||
|
</ion-content>
|
||||||
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
.metric-note {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
import { Component } from '@angular/core'
|
||||||
|
import { ActivatedRoute } from '@angular/router'
|
||||||
|
import { NavController } from '@ionic/angular'
|
||||||
|
import * as JsonPointer from 'json-pointer'
|
||||||
|
import { Subscription } from 'rxjs'
|
||||||
|
import { distinctUntilChanged } from 'rxjs/operators'
|
||||||
|
import { PackageDataEntry } from 'src/app/models/patch-db/data-model'
|
||||||
|
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
|
||||||
|
import { getManifest } from 'src/app/services/config.service'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-manifest',
|
||||||
|
templateUrl: './app-manifest.page.html',
|
||||||
|
styleUrls: ['./app-manifest.page.scss'],
|
||||||
|
})
|
||||||
|
export class AppManifestPage {
|
||||||
|
pkgId: string
|
||||||
|
pkg: PackageDataEntry
|
||||||
|
pointer: string
|
||||||
|
node: object
|
||||||
|
subs: Subscription[]
|
||||||
|
|
||||||
|
constructor (
|
||||||
|
private readonly route: ActivatedRoute,
|
||||||
|
private readonly patch: PatchDbModel,
|
||||||
|
private readonly navCtrl: NavController,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnInit () {
|
||||||
|
this.pkgId = this.route.snapshot.paramMap.get('pkgId')
|
||||||
|
|
||||||
|
this.subs = [
|
||||||
|
this.patch.watch$('package-data', this.pkgId)
|
||||||
|
.subscribe(pkg => {
|
||||||
|
this.pkg = pkg
|
||||||
|
this.setNode()
|
||||||
|
}),
|
||||||
|
this.route.queryParams
|
||||||
|
.pipe(distinctUntilChanged())
|
||||||
|
.subscribe(queryParams => {
|
||||||
|
this.pointer = queryParams['pointer']
|
||||||
|
this.setNode()
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
|
||||||
|
this.setNode()
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy () {
|
||||||
|
this.subs.forEach(sub => sub.unsubscribe())
|
||||||
|
}
|
||||||
|
|
||||||
|
setNode () {
|
||||||
|
this.node = JsonPointer.get(getManifest(this.pkg), this.pointer || '')
|
||||||
|
}
|
||||||
|
|
||||||
|
async goToNested (key: string): Promise<any> {
|
||||||
|
this.navCtrl.navigateForward(`/services/installed/${this.pkgId}/manifest`, {
|
||||||
|
queryParams: {
|
||||||
|
pointer: `${this.pointer || ''}/${key}`,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
asIsOrder (a: any, b: any) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { NgModule } from '@angular/core'
|
||||||
|
import { CommonModule } from '@angular/common'
|
||||||
|
import { Routes, RouterModule } from '@angular/router'
|
||||||
|
import { IonicModule } from '@ionic/angular'
|
||||||
|
import { AppMetricsPage } from './app-metrics.page'
|
||||||
|
import { PwaBackComponentModule } from 'src/app/components/pwa-back-button/pwa-back.component.module'
|
||||||
|
import { SharingModule } from 'src/app/modules/sharing.module'
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: AppMetricsPage,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
IonicModule,
|
||||||
|
RouterModule.forChild(routes),
|
||||||
|
PwaBackComponentModule,
|
||||||
|
SharingModule,
|
||||||
|
],
|
||||||
|
declarations: [AppMetricsPage],
|
||||||
|
})
|
||||||
|
export class AppMetricsPageModule { }
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
<ion-header>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-buttons slot="start">
|
||||||
|
<pwa-back-button></pwa-back-button>
|
||||||
|
</ion-buttons>
|
||||||
|
<ion-title>Monitor</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
|
||||||
|
<ion-content *ngIf="pkg">
|
||||||
|
<ion-item *ngIf="error" style="margin-bottom: 16px;">
|
||||||
|
<ion-text class="ion-text-wrap" color="danger">{{ error }}</ion-text>
|
||||||
|
</ion-item>
|
||||||
|
<ion-card>
|
||||||
|
<ion-card-header>
|
||||||
|
<ion-card-title>
|
||||||
|
<ion-icon style="vertical-align: middle; padding-right: 12px;" name="medkit-outline"></ion-icon>
|
||||||
|
<span style="vertical-align: middle;">Health Checks</span>
|
||||||
|
</ion-card-title>
|
||||||
|
</ion-card-header>
|
||||||
|
<ion-card-content>
|
||||||
|
<ion-item *ngIf="!(pkg.installed?.status.main.health | empty); else noHealth" color="light" style="margin: 10px;">
|
||||||
|
<ion-label>
|
||||||
|
<div *ngFor="let health of pkg.installed.status.main.health | keyvalue : asIsOrder" class="align" style="margin-left: 12px;">
|
||||||
|
<ion-icon *ngIf="health.value.result === 'success'" name="checkmark-outline" color="success"></ion-icon>
|
||||||
|
<ion-icon *ngIf="health.value.result === 'warming-up'" name="timer-outline" color="warning"></ion-icon>
|
||||||
|
<ion-icon *ngIf="health.value.result === 'failure'" name="close-outline" color="danger"></ion-icon>
|
||||||
|
<ion-icon *ngIf="health.value.result === 'disabled'" name="remove-outline" color="medium"></ion-icon>
|
||||||
|
<h2>{{ health.key }}</h2>
|
||||||
|
<p style="margin-left: 24px;" *ngIf="health.value.result === 'failure'"><ion-text color="danger">{{ health.value.error }}</ion-text></p>
|
||||||
|
</div>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
<ng-template #noHealth>
|
||||||
|
No health checks
|
||||||
|
</ng-template>
|
||||||
|
</ion-card-content>
|
||||||
|
</ion-card>
|
||||||
|
</ion-content>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
.metric-note {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
40
ui/src/app/pages/apps-routes/app-metrics/app-metrics.page.ts
Normal file
40
ui/src/app/pages/apps-routes/app-metrics/app-metrics.page.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import { Component } from '@angular/core'
|
||||||
|
import { ActivatedRoute } from '@angular/router'
|
||||||
|
import { Subscription } from 'rxjs'
|
||||||
|
import { PackageDataEntry } from 'src/app/models/patch-db/data-model'
|
||||||
|
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-metrics',
|
||||||
|
templateUrl: './app-metrics.page.html',
|
||||||
|
styleUrls: ['./app-metrics.page.scss'],
|
||||||
|
})
|
||||||
|
export class AppMetricsPage {
|
||||||
|
pkgId: string
|
||||||
|
pkg: PackageDataEntry
|
||||||
|
subs: Subscription[]
|
||||||
|
|
||||||
|
constructor (
|
||||||
|
private readonly route: ActivatedRoute,
|
||||||
|
private readonly patch: PatchDbModel,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnInit () {
|
||||||
|
this.pkgId = this.route.snapshot.paramMap.get('pkgId')
|
||||||
|
|
||||||
|
this.subs = [
|
||||||
|
this.patch.watch$('package-data', this.pkgId)
|
||||||
|
.subscribe(pkg => {
|
||||||
|
this.pkg = pkg
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy () {
|
||||||
|
this.subs.forEach(sub => sub.unsubscribe())
|
||||||
|
}
|
||||||
|
|
||||||
|
asIsOrder (a: any, b: any) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
<ion-buttons slot="start">
|
<ion-buttons slot="start">
|
||||||
<pwa-back-button></pwa-back-button>
|
<pwa-back-button></pwa-back-button>
|
||||||
</ion-buttons>
|
</ion-buttons>
|
||||||
<ion-title>Properties</ion-title>
|
<ion-title>Values</ion-title>
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
@@ -28,15 +28,15 @@
|
|||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<!-- no properties -->
|
<!-- no properties -->
|
||||||
<ion-item *ngIf="(hasProperties$ | ngrxPush) === false">
|
<ion-item *ngIf="properties | empty">
|
||||||
<ion-label class="ion-text-wrap">
|
<ion-label class="ion-text-wrap">
|
||||||
<p>No properties.</p>
|
<p>No values.</p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<!-- properties -->
|
<!-- properties -->
|
||||||
<ion-item-group *ngIf="(hasProperties$ | ngrxPush) === true">
|
<ion-item-group *ngIf="!(properties | empty)">
|
||||||
<div *ngFor="let prop of properties$ | ngrxPush | keyvalue: asIsOrder">
|
<div *ngFor="let prop of node | keyvalue: asIsOrder">
|
||||||
<!-- object -->
|
<!-- object -->
|
||||||
<ion-item button detail="true" *ngIf="prop.value.type === 'object'" (click)="goToNested(prop.key)">
|
<ion-item button detail="true" *ngIf="prop.value.type === 'object'" (click)="goToNested(prop.key)">
|
||||||
<ion-button *ngIf="prop.value.description" class="help-button" fill="clear" slot="start" (click)="presentDescription(prop, $event)">
|
<ion-button *ngIf="prop.value.description" class="help-button" fill="clear" slot="start" (click)="presentDescription(prop, $event)">
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
import { Component } from '@angular/core'
|
import { Component } from '@angular/core'
|
||||||
import { ActivatedRoute } from '@angular/router'
|
import { ActivatedRoute } from '@angular/router'
|
||||||
import { ApiService } from 'src/app/services/api/api.service'
|
import { ApiService } from 'src/app/services/api/api.service'
|
||||||
import { isEmptyObject, pauseFor } from 'src/app/util/misc.util'
|
import { Subscription } from 'rxjs'
|
||||||
import { BehaviorSubject, Subject } from 'rxjs'
|
|
||||||
import { copyToClipboard } from 'src/app/util/web.util'
|
import { copyToClipboard } from 'src/app/util/web.util'
|
||||||
import { AlertController, NavController, PopoverController, ToastController } from '@ionic/angular'
|
import { AlertController, NavController, PopoverController, ToastController } from '@ionic/angular'
|
||||||
import { PackageProperties } from 'src/app/util/properties.util'
|
import { PackageProperties } from 'src/app/util/properties.util'
|
||||||
import { QRComponent } from 'src/app/components/qr/qr.component'
|
import { QRComponent } from 'src/app/components/qr/qr.component'
|
||||||
import { PropertyStore } from './property-store'
|
import { PropertyStore } from './property-store'
|
||||||
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
|
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
|
||||||
import * as JSONpointer from 'json-pointer'
|
import * as JsonPointer from 'json-pointer'
|
||||||
import { FEStatus } from 'src/app/services/pkg-status-rendering.service'
|
import { FEStatus } from 'src/app/services/pkg-status-rendering.service'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -23,10 +22,11 @@ export class AppPropertiesPage {
|
|||||||
pkgId: string
|
pkgId: string
|
||||||
pointer: string
|
pointer: string
|
||||||
qrCode: string
|
qrCode: string
|
||||||
properties$ = new BehaviorSubject<PackageProperties>({ })
|
properties: PackageProperties
|
||||||
hasProperties$ = new BehaviorSubject<boolean>(null)
|
node: PackageProperties
|
||||||
unmasked: { [key: string]: boolean } = { }
|
unmasked: { [key: string]: boolean } = { }
|
||||||
FeStatus = FEStatus
|
FeStatus = FEStatus
|
||||||
|
subs: Subscription[]
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private readonly route: ActivatedRoute,
|
private readonly route: ActivatedRoute,
|
||||||
@@ -36,28 +36,27 @@ export class AppPropertiesPage {
|
|||||||
private readonly popoverCtrl: PopoverController,
|
private readonly popoverCtrl: PopoverController,
|
||||||
private readonly propertyStore: PropertyStore,
|
private readonly propertyStore: PropertyStore,
|
||||||
private readonly navCtrl: NavController,
|
private readonly navCtrl: NavController,
|
||||||
public patch: PatchDbModel,
|
public readonly patch: PatchDbModel,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
ngOnInit () {
|
async ngOnInit () {
|
||||||
this.pkgId = this.route.snapshot.paramMap.get('pkgId')
|
this.pkgId = this.route.snapshot.paramMap.get('pkgId')
|
||||||
this.pointer = this.route.queryParams['pointer']
|
|
||||||
|
|
||||||
this.getProperties().then(() => this.loading = false)
|
await this.getProperties()
|
||||||
|
|
||||||
this.propertyStore.watch$().subscribe(m => {
|
this.subs = [
|
||||||
const properties = JSONpointer.get(m, this.pointer || '')
|
this.route.queryParams.subscribe(queryParams => {
|
||||||
this.properties$.next(properties)
|
if (queryParams['pointer'] === this.pointer) return
|
||||||
})
|
this.pointer = queryParams['pointer']
|
||||||
this.properties$.subscribe(m => {
|
this.node = JsonPointer.get(this.properties, this.pointer || '')
|
||||||
this.hasProperties$.next(!isEmptyObject(m))
|
}),
|
||||||
})
|
]
|
||||||
this.route.queryParams.subscribe(queryParams => {
|
|
||||||
if (queryParams['pointer'] === this.pointer) return
|
this.loading = false
|
||||||
this.pointer = queryParams['pointer']
|
}
|
||||||
const properties = JSONpointer.get(this.propertyStore.properties$.getValue(), this.pointer || '')
|
|
||||||
this.properties$.next(properties)
|
ngOnDestroy () {
|
||||||
})
|
this.subs.forEach(sub => sub.unsubscribe())
|
||||||
}
|
}
|
||||||
|
|
||||||
async doRefresh (event: any) {
|
async doRefresh (event: any) {
|
||||||
@@ -118,8 +117,8 @@ export class AppPropertiesPage {
|
|||||||
|
|
||||||
private async getProperties (): Promise<void> {
|
private async getProperties (): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const properties = await this.apiService.getPackageProperties({ id: this.pkgId })
|
this.properties = await this.apiService.getPackageProperties({ id: this.pkgId })
|
||||||
this.propertyStore.update(properties)
|
this.node = JsonPointer.get(this.properties, this.pointer || '')
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
this.error = e.message
|
this.error = e.message
|
||||||
|
|||||||
@@ -39,6 +39,14 @@ const routes: Routes = [
|
|||||||
path: 'installed/:pkgId/logs',
|
path: 'installed/:pkgId/logs',
|
||||||
loadChildren: () => import('./app-logs/app-logs.module').then(m => m.AppLogsPageModule),
|
loadChildren: () => import('./app-logs/app-logs.module').then(m => m.AppLogsPageModule),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'installed/:pkgId/manifest',
|
||||||
|
loadChildren: () => import('./app-manifest/app-manifest.module').then(m => m.AppManifestPageModule),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'installed/:pkgId/metrics',
|
||||||
|
loadChildren: () => import('./app-metrics/app-metrics.module').then(m => m.AppMetricsPageModule),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'installed/:pkgId/properties',
|
path: 'installed/:pkgId/properties',
|
||||||
loadChildren: () => import('./app-properties/app-properties.module').then(m => m.AppPropertiesPageModule),
|
loadChildren: () => import('./app-properties/app-properties.module').then(m => m.AppPropertiesPageModule),
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Component } from '@angular/core'
|
import { Component } from '@angular/core'
|
||||||
import { ServerMetrics } from 'src/app/services/api/api-types'
|
import { Metrics } from 'src/app/services/api/api-types'
|
||||||
import { ApiService } from 'src/app/services/api/api.service'
|
import { ApiService } from 'src/app/services/api/api.service'
|
||||||
import { pauseFor } from 'src/app/util/misc.util'
|
import { pauseFor } from 'src/app/util/misc.util'
|
||||||
|
|
||||||
@@ -12,7 +12,7 @@ export class ServerMetricsPage {
|
|||||||
error = ''
|
error = ''
|
||||||
loading = true
|
loading = true
|
||||||
going = false
|
going = false
|
||||||
metrics: ServerMetrics = { }
|
metrics: Metrics = { }
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private readonly apiService: ApiService,
|
private readonly apiService: ApiService,
|
||||||
|
|||||||
@@ -12,60 +12,60 @@
|
|||||||
<ion-item-divider>Backups</ion-item-divider>
|
<ion-item-divider>Backups</ion-item-divider>
|
||||||
|
|
||||||
<ion-item [routerLink]="['backup']">
|
<ion-item [routerLink]="['backup']">
|
||||||
<ion-icon slot="start" name="save-outline" color="primary"></ion-icon>
|
<ion-icon slot="start" name="save-outline"></ion-icon>
|
||||||
<ion-label><ion-text color="primary">Create Backup</ion-text></ion-label>
|
<ion-label>Create Backup</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<ion-item-divider>Insights</ion-item-divider>
|
<ion-item-divider>Insights</ion-item-divider>
|
||||||
|
|
||||||
<ion-item-group>
|
<ion-item-group>
|
||||||
<ion-item [routerLink]="['specs']">
|
<ion-item [routerLink]="['specs']">
|
||||||
<ion-icon slot="start" name="information-circle-outline" color="primary"></ion-icon>
|
<ion-icon slot="start" name="information-circle-outline"></ion-icon>
|
||||||
<ion-label><ion-text color="primary">About</ion-text></ion-label>
|
<ion-label>About</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<ion-item [routerLink]="['metrics']">
|
<ion-item [routerLink]="['metrics']">
|
||||||
<ion-icon slot="start" name="pulse" color="primary"></ion-icon>
|
<ion-icon slot="start" name="pulse"></ion-icon>
|
||||||
<ion-label><ion-text color="primary">Monitor</ion-text></ion-label>
|
<ion-label>Monitor</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<ion-item [routerLink]="['logs']">
|
<ion-item [routerLink]="['logs']">
|
||||||
<ion-icon slot="start" name="newspaper-outline" color="primary"></ion-icon>
|
<ion-icon slot="start" name="newspaper-outline"></ion-icon>
|
||||||
<ion-label><ion-text color="primary">Logs</ion-text></ion-label>
|
<ion-label>Logs</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<ion-item-divider>Settings</ion-item-divider>
|
<ion-item-divider>Settings</ion-item-divider>
|
||||||
|
|
||||||
<ion-item lines="none" [routerLink]="['settings']">
|
<ion-item lines="none" [routerLink]="['settings']">
|
||||||
<ion-icon slot="start" name="cog-outline" color="primary"></ion-icon>
|
<ion-icon slot="start" name="cog-outline"></ion-icon>
|
||||||
<ion-label><ion-text color="primary">General</ion-text></ion-label>
|
<ion-label>General</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<ion-item [routerLink]="['lan']">
|
<ion-item [routerLink]="['lan']">
|
||||||
<ion-icon slot="start" name="home-outline" color="primary"></ion-icon>
|
<ion-icon slot="start" name="home-outline"></ion-icon>
|
||||||
<ion-label><ion-text color="primary">LAN</ion-text></ion-label>
|
<ion-label>LAN</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<ion-item [routerLink]="['wifi']">
|
<ion-item [routerLink]="['wifi']">
|
||||||
<ion-icon slot="start" name="wifi" color="primary"></ion-icon>
|
<ion-icon slot="start" name="wifi"></ion-icon>
|
||||||
<ion-label><ion-text color="primary">WiFi</ion-text></ion-label>
|
<ion-label>WiFi</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<ion-item lines="none" [routerLink]="['developer']">
|
<ion-item lines="none" [routerLink]="['developer']">
|
||||||
<ion-icon slot="start" name="terminal-outline" color="primary"></ion-icon>
|
<ion-icon slot="start" name="terminal-outline"></ion-icon>
|
||||||
<ion-label><ion-text color="primary">Developer Options</ion-text></ion-label>
|
<ion-label>Developer Options</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<ion-item-divider>Power</ion-item-divider>
|
<ion-item-divider>Power</ion-item-divider>
|
||||||
|
|
||||||
<ion-item button (click)="presentAlertRestart()">
|
<ion-item button (click)="presentAlertRestart()">
|
||||||
<ion-icon slot="start" name="reload-outline" color="primary"></ion-icon>
|
<ion-icon slot="start" name="reload-outline"></ion-icon>
|
||||||
<ion-label><ion-text color="primary">Restart</ion-text></ion-label>
|
<ion-label>Restart</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<ion-item button lines="none" (click)="presentAlertShutdown()">
|
<ion-item button lines="none" (click)="presentAlertShutdown()">
|
||||||
<ion-icon slot="start" name="power" color="primary"></ion-icon>
|
<ion-icon slot="start" name="power"></ion-icon>
|
||||||
<ion-label><ion-text color="primary">Shutdown</ion-text></ion-label>
|
<ion-label>Shutdown</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ion-item-group>
|
</ion-item-group>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
@@ -5,7 +5,8 @@ import { isEmptyObject } from '../util/misc.util'
|
|||||||
name: 'empty',
|
name: 'empty',
|
||||||
})
|
})
|
||||||
export class EmptyPipe implements PipeTransform {
|
export class EmptyPipe implements PipeTransform {
|
||||||
transform (obj: { }): boolean {
|
transform (val: object | [] = { }): boolean {
|
||||||
return isEmptyObject(obj)
|
if (Array.isArray(val)) return !val.length
|
||||||
|
return isEmptyObject(val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,12 @@ import { Pipe, PipeTransform } from '@angular/core'
|
|||||||
})
|
})
|
||||||
export class TypeofPipe implements PipeTransform {
|
export class TypeofPipe implements PipeTransform {
|
||||||
transform (value: any): any {
|
transform (value: any): any {
|
||||||
|
if (value === null) {
|
||||||
|
return 'null'
|
||||||
|
} else if (Array.isArray(value)) {
|
||||||
|
return 'array'
|
||||||
|
}
|
||||||
|
|
||||||
return typeof value
|
return typeof value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -28,7 +28,7 @@ export module RR {
|
|||||||
export type GetServerLogsRes = Log[]
|
export type GetServerLogsRes = Log[]
|
||||||
|
|
||||||
export type GetServerMetricsReq = { } // server.metrics
|
export type GetServerMetricsReq = { } // server.metrics
|
||||||
export type GetServerMetricsRes = ServerMetrics
|
export type GetServerMetricsRes = Metrics
|
||||||
|
|
||||||
export type UpdateServerReq = WithExpire<{ }> // server.update
|
export type UpdateServerReq = WithExpire<{ }> // server.update
|
||||||
export type UpdateServerRes = WithRevision<null>
|
export type UpdateServerRes = WithRevision<null>
|
||||||
@@ -219,7 +219,7 @@ export interface ActionResponse {
|
|||||||
qr: boolean
|
qr: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ServerMetrics {
|
export interface Metrics {
|
||||||
[key: string]: {
|
[key: string]: {
|
||||||
[key: string]: {
|
[key: string]: {
|
||||||
value: string | number | null
|
value: string | number | null
|
||||||
|
|||||||
@@ -67,17 +67,7 @@ export module Mock {
|
|||||||
inject: false,
|
inject: false,
|
||||||
'shm-size': '',
|
'shm-size': '',
|
||||||
},
|
},
|
||||||
'health-check': {
|
'health-checks': { },
|
||||||
type: 'docker',
|
|
||||||
image: '',
|
|
||||||
system: true,
|
|
||||||
entrypoint: '',
|
|
||||||
args: [''],
|
|
||||||
mounts: { },
|
|
||||||
'io-format': DockerIoFormat.Yaml,
|
|
||||||
inject: false,
|
|
||||||
'shm-size': '',
|
|
||||||
},
|
|
||||||
config: null,
|
config: null,
|
||||||
volumes: { },
|
volumes: { },
|
||||||
'min-os-version': '0.2.12',
|
'min-os-version': '0.2.12',
|
||||||
@@ -198,17 +188,7 @@ export module Mock {
|
|||||||
inject: false,
|
inject: false,
|
||||||
'shm-size': '',
|
'shm-size': '',
|
||||||
},
|
},
|
||||||
'health-check': {
|
'health-checks': { },
|
||||||
type: 'docker',
|
|
||||||
image: '',
|
|
||||||
system: true,
|
|
||||||
entrypoint: '',
|
|
||||||
args: [''],
|
|
||||||
mounts: { },
|
|
||||||
'io-format': DockerIoFormat.Yaml,
|
|
||||||
inject: false,
|
|
||||||
'shm-size': '',
|
|
||||||
},
|
|
||||||
config: null,
|
config: null,
|
||||||
volumes: { },
|
volumes: { },
|
||||||
'min-os-version': '0.2.12',
|
'min-os-version': '0.2.12',
|
||||||
@@ -353,17 +333,7 @@ export module Mock {
|
|||||||
inject: false,
|
inject: false,
|
||||||
'shm-size': '',
|
'shm-size': '',
|
||||||
},
|
},
|
||||||
'health-check': {
|
'health-checks': { },
|
||||||
type: 'docker',
|
|
||||||
image: '',
|
|
||||||
system: true,
|
|
||||||
entrypoint: '',
|
|
||||||
args: [''],
|
|
||||||
mounts: { },
|
|
||||||
'io-format': DockerIoFormat.Yaml,
|
|
||||||
inject: false,
|
|
||||||
'shm-size': '',
|
|
||||||
},
|
|
||||||
config: null,
|
config: null,
|
||||||
volumes: { },
|
volumes: { },
|
||||||
'min-os-version': '0.2.12',
|
'min-os-version': '0.2.12',
|
||||||
@@ -991,7 +961,7 @@ export module Mock {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'Another Property': {
|
'Another Value': {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
description: 'Some more information about the service.',
|
description: 'Some more information about the service.',
|
||||||
copyable: false,
|
copyable: false,
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export class HttpService {
|
|||||||
this.fullUrl = `${window.location.protocol}//${window.location.hostname}:${port}/${url}/${version}`
|
this.fullUrl = `${window.location.protocol}//${window.location.hostname}:${port}/${url}/${version}`
|
||||||
}
|
}
|
||||||
|
|
||||||
watch401$ (): Observable<{ }> {
|
watchUnauth$ (): Observable<{ }> {
|
||||||
return this.unauthorizedApiResponse$.asObservable()
|
return this.unauthorizedApiResponse$.asObservable()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,7 +36,10 @@ export class HttpService {
|
|||||||
|
|
||||||
const res = await this.httpRequest<RPCResponse<T>>(httpOpts)
|
const res = await this.httpRequest<RPCResponse<T>>(httpOpts)
|
||||||
|
|
||||||
if (isRpcError(res)) throw new RpcError(res.error)
|
if (isRpcError(res)) {
|
||||||
|
if (res.error.code === 34) this.unauthorizedApiResponse$.next(true)
|
||||||
|
throw new RpcError(res.error)
|
||||||
|
}
|
||||||
|
|
||||||
if (isRpcSuccess(res)) return res.result
|
if (isRpcSuccess(res)) return res.result
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { PackageDataEntry, PackageMainStatus, PackageState, Status } from '../models/patch-db/data-model'
|
import { HealthCheckResultWarmingUp, MainStatus, MainStatusRunning, PackageDataEntry, PackageMainStatus, PackageState, Status } from '../models/patch-db/data-model'
|
||||||
import { ConnectionState } from './connection.service'
|
import { ConnectionState } from './connection.service'
|
||||||
|
|
||||||
export function renderPkgStatus (pkg: PackageDataEntry, connection: ConnectionState): PkgStatusRendering {
|
export function renderPkgStatus (pkg: PackageDataEntry, connection: ConnectionState): PkgStatusRendering {
|
||||||
@@ -19,16 +19,26 @@ function handleInstalledState (status: Status): PkgStatusRendering {
|
|||||||
return { display: 'Needs Config', color: 'warning', showDots: false, feStatus: FEStatus.NeedsConfig }
|
return { display: 'Needs Config', color: 'warning', showDots: false, feStatus: FEStatus.NeedsConfig }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Object.values(status['dependency-errors']).length) {
|
if (Object.keys(status['dependency-errors']).length) {
|
||||||
return { display: 'Dependency Issue', color: 'warning', showDots: false, feStatus: FEStatus.DependencyIssue }
|
return { display: 'Dependency Issue', color: 'warning', showDots: false, feStatus: FEStatus.DependencyIssue }
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (status.main.status) {
|
switch (status.main.status) {
|
||||||
case PackageMainStatus.Running: return { display: 'Running', color: 'success', showDots: false, feStatus: FEStatus.Running }
|
|
||||||
case PackageMainStatus.Stopping: return { display: 'Stopping', color: 'dark', showDots: true, feStatus: FEStatus.Stopping }
|
case PackageMainStatus.Stopping: return { display: 'Stopping', color: 'dark', showDots: true, feStatus: FEStatus.Stopping }
|
||||||
case PackageMainStatus.Stopped: return { display: 'Stopped', color: 'medium', showDots: false, feStatus: FEStatus.Stopped }
|
case PackageMainStatus.Stopped: return { display: 'Stopped', color: 'medium', showDots: false, feStatus: FEStatus.Stopped }
|
||||||
case PackageMainStatus.BackingUp: return { display: 'Backing Up', color: 'warning', showDots: true, feStatus: FEStatus.BackingUp }
|
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.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 === 'warming-up')) {
|
||||||
|
return { display: 'Starting Up', color: 'warning', showDots: true, feStatus: FEStatus.StartingUp }
|
||||||
|
} else {
|
||||||
|
return { display: 'Running', color: 'success', showDots: false, feStatus: FEStatus.Running }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,6 +62,8 @@ export enum FEStatus {
|
|||||||
BackingUp = 'backing-up',
|
BackingUp = 'backing-up',
|
||||||
Restoring = 'restoring',
|
Restoring = 'restoring',
|
||||||
// FE
|
// FE
|
||||||
|
NeedsAttention = 'needs-attention',
|
||||||
|
StartingUp = 'starting-up',
|
||||||
Connecting = 'connecting',
|
Connecting = 'connecting',
|
||||||
DependencyIssue = 'dependency-issue',
|
DependencyIssue = 'dependency-issue',
|
||||||
NeedsConfig = 'needs-config',
|
NeedsConfig = 'needs-config',
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
},
|
},
|
||||||
"mocks": {
|
"mocks": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"connection": "poll",
|
"connection": "ws",
|
||||||
"rpcPort": "5959",
|
"rpcPort": "5959",
|
||||||
"wsPort": "5960",
|
"wsPort": "5960",
|
||||||
"maskAs": "tor",
|
"maskAs": "tor",
|
||||||
|
|||||||
Reference in New Issue
Block a user