action inputs

This commit is contained in:
Matt Hill
2021-07-13 10:18:08 -06:00
committed by Aiden McClelland
parent 2086367407
commit b0d59ed80f
23 changed files with 160 additions and 46 deletions

1
ui/package-lock.json generated
View File

@@ -17,7 +17,6 @@
"@ionic/angular": "^5.6.0",
"@ionic/storage": "^3.0.0",
"@ionic/storage-angular": "^3.0.0",
"@ngrx/component": "^11.1.1",
"@start9labs/emver": "^0.1.4",
"ajv": "^6.12.6",
"angularx-qrcode": "^11.0.0",

View File

@@ -13,7 +13,6 @@
"lint": "ng lint",
"e2e": "ng e2e"
},
"private": true,
"dependencies": {
"@angular/common": "^11.0.0",
"@angular/core": "^11.0.0",

View File

@@ -36,8 +36,8 @@
<!-- 3rd party components -->
<qrcode qrdata="hello"></qrcode>
<img src="assets/img/running-bulb.png"/>
<img src="assets/img/issue-bulb.png"/>
<img src="assets/img/success-bulb.png"/>
<img src="assets/img/danger-bulb.png"/>
<img src="assets/img/warning-bulb.png"/>
<img src="assets/img/off-bulb.png"/>
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=">

View File

@@ -0,0 +1,19 @@
import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common'
import { IonicModule } from '@ionic/angular'
import { AppActionInputPage } from './app-action-input.page'
import { ObjectConfigComponentModule } from 'src/app/components/object-config/object-config.component.module'
import { ConfigHeaderComponentModule } from 'src/app/components/config-header/config-header.component.module'
@NgModule({
declarations: [AppActionInputPage],
imports: [
CommonModule,
IonicModule,
ObjectConfigComponentModule,
ConfigHeaderComponentModule,
],
entryComponents: [AppActionInputPage],
exports: [AppActionInputPage],
})
export class AppActionInputPageModule { }

View File

@@ -0,0 +1,27 @@
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-button (click)="dismiss()">
<ion-icon name="close-outline"></ion-icon>
</ion-button>
</ion-buttons>
<ion-title>{{ action.name }}</ion-title>
<ion-buttons slot="end">
<ion-button [disabled]="error" (click)="save()">
Save
</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content>
<config-header [spec]="spec" [error]="error"></config-header>
<!-- object -->
<ion-item-group>
<ion-item-divider></ion-item-divider>
<object-config [cursor]="cursor" (onEdit)="handleObjectEdit()"></object-config>
</ion-item-group>
</ion-content>

View File

@@ -0,0 +1,55 @@
import { Component, Input } from '@angular/core'
import { LoadingController, ModalController } from '@ionic/angular'
import { ConfigCursor } from 'src/app/pkg-config/config-cursor'
import { ValueSpecObject } from 'src/app/pkg-config/config-types'
import { LoaderService } from 'src/app/services/loader.service'
import { Action } from 'src/app/services/patch-db/data-model'
@Component({
selector: 'app-action-input',
templateUrl: './app-action-input.page.html',
styleUrls: ['./app-action-input.page.scss'],
})
export class AppActionInputPage {
@Input() action: Action
@Input() cursor: ConfigCursor<'object'>
@Input() execute: () => Promise<void>
spec: ValueSpecObject
value: object
error: string
constructor (
private readonly modalCtrl: ModalController,
private readonly loadingCtrl: LoadingController,
private loaderService: LoaderService,
) { }
ngOnInit () {
this.spec = this.cursor.spec()
this.value = this.cursor.config()
this.error = this.cursor.checkInvalid()
}
async dismiss (): Promise<void> {
this.modalCtrl.dismiss()
}
async save (): Promise<void> {
this.loaderService.of({
spinner: 'lines',
message: 'Executing action',
cssClass: 'loader-ontop-of-all',
}).displayDuringAsync(async () => {
try {
await this.execute()
this.modalCtrl.dismiss()
} catch (e) {
this.error = e.message
}
})
}
handleObjectEdit (): void {
this.error = this.cursor.checkInvalid()
}
}

View File

@@ -57,7 +57,7 @@ export class AppConfigValuePage {
if ((!!this.saveFn && this.edited) || (!this.saveFn && this.error)) {
await this.presentAlert()
} else {
await this.modalCtrl.dismiss()
await this.modalCtrl.dismiss(this.value)
}
}

View File

@@ -10,7 +10,7 @@
</ion-header>
<ion-content>
<text-spinner *ngIf="loading; else loaded" [text]="'fetching ' + title"></text-spinner>
<text-spinner *ngIf="loading; else loaded" [text]="'Loading ' + title | titlecase"></text-spinner>
<ng-template #loaded>
<ion-item *ngIf="error" style="margin-bottom: 16px;">
<ion-text class="ion-text-wrap" color="danger">{{ error }}</ion-text>

View File

@@ -6,6 +6,7 @@ import { AppActionsPage } from './app-actions.page'
import { PwaBackComponentModule } from 'src/app/components/pwa-back-button/pwa-back.component.module'
import { QRComponentModule } from 'src/app/components/qr/qr.component.module'
import { SharingModule } from 'src/app/modules/sharing.module'
import { AppActionInputPageModule } from 'src/app/modals/app-action-input/app-action-input.module'
const routes: Routes = [
{
@@ -22,6 +23,7 @@ const routes: Routes = [
PwaBackComponentModule,
QRComponentModule,
SharingModule,
AppActionInputPageModule,
],
declarations: [AppActionsPage],
})

View File

@@ -9,6 +9,9 @@ import { Action, Manifest, PackageDataEntry, PackageMainStatus } from 'src/app/s
import { wizardModal } from 'src/app/components/install-wizard/install-wizard.component'
import { WizardBaker } from 'src/app/components/install-wizard/prebaked-wizards'
import { Subscription } from 'rxjs'
import { AppConfigObjectPage } from 'src/app/modals/app-config-object/app-config-object.page'
import { ConfigCursor } from 'src/app/pkg-config/config-cursor'
import { AppActionInputPage } from 'src/app/modals/app-action-input/app-action-input.page'
@Component({
selector: 'app-actions',
@@ -46,23 +49,36 @@ export class AppActionsPage {
async handleAction (pkg: PackageDataEntry, action: { key: string, value: Action }) {
if ((action.value['allowed-statuses'] as PackageMainStatus[]).includes(pkg.installed.status.main.status)) {
const alert = await this.alertCtrl.create({
header: 'Confirm',
message: `Are you sure you want to execute action "${action.value.name}"? ${action.value.warning || ''}`,
buttons: [
{
text: 'Cancel',
role: 'cancel',
const inputSpec = action.value['input-spec']
if (inputSpec) {
const modal = await this.modalCtrl.create({
component: AppActionInputPage,
componentProps: {
action: action.value,
cursor: new ConfigCursor(inputSpec, { }),
execute: () => this.executeAction(pkg.manifest.id, action.key),
},
{
text: 'Execute',
handler: () => {
this.executeAction(pkg.manifest.id, action.key)
})
await modal.present()
} else {
const alert = await this.alertCtrl.create({
header: 'Confirm',
message: `Are you sure you want to execute action "${action.value.name}"? ${action.value.warning || ''}`,
buttons: [
{
text: 'Cancel',
role: 'cancel',
},
},
],
})
await alert.present()
{
text: 'Execute',
handler: () => {
this.executeAction(pkg.manifest.id, action.key)
},
},
],
})
await alert.present()
}
} else {
const statuses = [...action.value['allowedStatuses']]
const last = statuses.pop()

View File

@@ -5,6 +5,7 @@ import { IonicModule } from '@ionic/angular'
import { AppInstructionsPage } from './app-instructions.page'
import { PwaBackComponentModule } from 'src/app/components/pwa-back-button/pwa-back.component.module'
import { SharingModule } from 'src/app/modules/sharing.module'
import { TextSpinnerComponentModule } from 'src/app/components/text-spinner/text-spinner.component.module'
const routes: Routes = [
{
@@ -20,6 +21,7 @@ const routes: Routes = [
RouterModule.forChild(routes),
PwaBackComponentModule,
SharingModule,
TextSpinnerComponentModule,
],
declarations: [
AppInstructionsPage,

View File

@@ -8,7 +8,7 @@
</ion-header>
<ion-content class="ion-padding">
<ion-spinner *ngIf="loading; else loaded" class="center" name="lines" color="warning"></ion-spinner>
<text-spinner *ngIf="loading; else loaded" text="Loading Instructions"></text-spinner>
<ng-template #loaded>
<ion-item *ngIf="error" style="margin-bottom: 16px;">

View File

@@ -36,7 +36,7 @@
<img [class]="serviceInfo[pkg.key].bulbInfo.class" [src]="serviceInfo[pkg.key].bulbInfo.img"/>
<ion-card-header>
<status *ngIf="connected" [rendering]="serviceInfo[pkg.key].rendering" size="calc(8px + .4vw)" weight="bold"></status>
<status [rendering]="serviceInfo[pkg.key].rendering" size="calc(8px + .4vw)" weight="bold"></status>
<ion-card-title>{{ pkg.value.manifest.title }}</ion-card-title>
</ion-card-header>
</ion-card>

View File

@@ -4,6 +4,7 @@ import { Routes, RouterModule } from '@angular/router'
import { IonicModule } from '@ionic/angular'
import { AppLogsPage } from './app-logs.page'
import { PwaBackComponentModule } from 'src/app/components/pwa-back-button/pwa-back.component.module'
import { TextSpinnerComponentModule } from 'src/app/components/text-spinner/text-spinner.component.module'
const routes: Routes = [
{
@@ -18,6 +19,7 @@ const routes: Routes = [
IonicModule,
RouterModule.forChild(routes),
PwaBackComponentModule,
TextSpinnerComponentModule,
],
declarations: [AppLogsPage],
})

View File

@@ -18,7 +18,7 @@
<ion-text class="ion-text-wrap" color="danger">{{ error }}</ion-text>
</ion-item>
<ion-spinner *ngIf="!logs" class="center" name="lines" color="warning"></ion-spinner>
<text-spinner *ngIf="!logs" text="Loading Logs"></text-spinner>
<p style="white-space: pre-line;">{{ logs }}</p>
<div style="white-space: pre-line;">{{ logs }}</div>
</ion-content>

View File

@@ -6,6 +6,7 @@ import { AppPropertiesPage } from './app-properties.page'
import { PwaBackComponentModule } from 'src/app/components/pwa-back-button/pwa-back.component.module'
import { QRComponentModule } from 'src/app/components/qr/qr.component.module'
import { SharingModule } from 'src/app/modules/sharing.module'
import { TextSpinnerComponentModule } from 'src/app/components/text-spinner/text-spinner.component.module'
const routes: Routes = [
{
@@ -22,6 +23,7 @@ const routes: Routes = [
PwaBackComponentModule,
QRComponentModule,
SharingModule,
TextSpinnerComponentModule,
],
declarations: [AppPropertiesPage],
})

View File

@@ -13,7 +13,7 @@
</ion-header>
<ion-content class="ion-padding-top">
<ion-spinner *ngIf="loading; else loaded" class="center" name="lines" color="warning"></ion-spinner>
<text-spinner *ngIf="loading; else loaded" text="Loading Properties"></text-spinner>
<ng-template #loaded>
<ion-item *ngIf="error" style="margin-bottom: 16px;">

View File

@@ -31,17 +31,17 @@
</ion-item>
<div class="status-readout">
<status *ngIf="connected" size="large" weight="500" [rendering]="rendering"></status>
<ion-button *ngIf="(pkgId | status | async) === FeStatus.NeedsConfig" expand="block" [routerLink]="['config']">
<status size="large" weight="500" [rendering]="rendering"></status>
<ion-button *ngIf="rendering.feStatus === FeStatus.NeedsConfig" expand="block" [routerLink]="['config']">
Configure
</ion-button>
<ion-button *ngIf="[FeStatus.Running, FeStatus.StartingUp, FeStatus.NeedsAttention] | includes : (pkgId | status | async)" expand="block" color="danger" (click)="stop()">
<ion-button *ngIf="[FeStatus.Running, FeStatus.StartingUp, FeStatus.NeedsAttention] | includes : rendering.feStatus" expand="block" color="danger" (click)="stop()">
Stop
</ion-button>
<ion-button *ngIf="(pkgId | status | async) === FeStatus.DependencyIssue" expand="block" (click)="scrollToRequirements()">
<ion-button *ngIf="rendering.feStatus === FeStatus.DependencyIssue" expand="block" (click)="scrollToRequirements()">
Fix
</ion-button>
<ion-button *ngIf="(pkgId | status | async) === FeStatus.Stopped" expand="block" color="success" (click)="tryStart()">
<ion-button *ngIf="rendering.feStatus === FeStatus.Stopped" expand="block" color="success" (click)="tryStart()">
Start
</ion-button>
</div>
@@ -52,11 +52,11 @@
</ion-button>
</div>
<ng-container *ngIf="!([FeStatus.Installing, FeStatus.Updating, FeStatus.Removing] | includes : pkg.status)">
<ng-container *ngIf="!([FeStatus.Installing, FeStatus.Updating, FeStatus.Removing] | includes : rendering.feStatus)">
<ion-grid class="ion-text-center" style="margin: 0 6px;">
<ion-row>
<ion-col *ngFor="let button of buttons" sizeMd="4" sizeSm="6" sizeXs="6">
<ion-button style="width: 100%; min-height: 120px;" color="light" [disabled]="button.disabled | includes : pkg.status" (click)="button.action()">
<ion-button style="width: 100%; min-height: 120px;" color="light" [disabled]="button.disabled | includes : rendering.feStatus" (click)="button.action()">
<div>
<ion-icon size="large" [name]="button.icon"></ion-icon>
<br/><br/>

View File

@@ -7,9 +7,9 @@ import { LoaderService } from 'src/app/services/loader.service'
import { combineLatest, Observable, of, Subscription } from 'rxjs'
import { wizardModal } from 'src/app/components/install-wizard/install-wizard.component'
import { WizardBaker } from 'src/app/components/install-wizard/prebaked-wizards'
import { ConfigService, getManifest } from 'src/app/services/config.service'
import { ConfigService } from 'src/app/services/config.service'
import { PatchDbModel } from 'src/app/services/patch-db/patch-db.service'
import { DependencyErrorConfigUnsatisfied, DependencyErrorNotInstalled, DependencyErrorType, Manifest, PackageDataEntry, PackageState } from 'src/app/services/patch-db/data-model'
import { DependencyErrorConfigUnsatisfied, DependencyErrorNotInstalled, DependencyErrorType, PackageDataEntry, PackageState } from 'src/app/services/patch-db/data-model'
import { FEStatus, PkgStatusRendering, renderPkgStatus } from 'src/app/services/pkg-status-rendering.service'
import { ConnectionService } from 'src/app/services/connection.service'
@@ -279,7 +279,7 @@ export class AppShowPage {
},
{
action: () => this.navCtrl.navigateForward(['manifest'], { relativeTo: this.route }),
title: 'Package Manifest',
title: 'Package Details',
icon: 'finger-print-outline',
color: 'danger',
disabled: [],

View File

@@ -12,7 +12,7 @@
<ion-content class="ion-padding">
<text-spinner *ngIf="!marketplaceService.pkgs[pkgId]" [text]="'fetching package'"></text-spinner>
<text-spinner *ngIf="!marketplaceService.pkgs[pkgId]" text="Loading Package"></text-spinner>
<ng-container *ngIf="marketplaceService.pkgs[pkgId] as pkg">
<ion-item *ngIf="error" style="margin-bottom: 16px;">

View File

@@ -70,14 +70,6 @@ export function markAsLoadingDuring$<T> ($trigger$: Subject<boolean>, o: Observa
)
}
export function markAsLoadingDuringP<T> ($trigger$: Subject<boolean>, p: Promise<T>): Promise<T> {
return markAsLoadingDuring$($trigger$, from(p)).toPromise()
}
export function markAsLoadingDuringAsync<T> ($trigger$: Subject<boolean>, thunk: () => Promise<T>): Promise<T> {
return markAsLoadingDuringP($trigger$, fromAsyncP(thunk))
}
const defaultOptions: () => LoadingOptions = () => ({
spinner: 'lines',

View File

@@ -5,7 +5,6 @@ import { WizardBaker } from '../components/install-wizard/prebaked-wizards'
import { OSWelcomePage } from '../modals/os-welcome/os-welcome.page'
import { displayEmver } from '../pipes/emver.pipe'
import { ApiService } from './api/api.service'
import { RR } from './api/api-types'
import { ConfigService } from './config.service'
import { Emver } from './emver.service'
import { OsUpdateService } from './os-update.service'