mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 18:31:52 +00:00
action inputs
This commit is contained in:
committed by
Aiden McClelland
parent
2086367407
commit
b0d59ed80f
1
ui/package-lock.json
generated
1
ui/package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
"lint": "ng lint",
|
||||
"e2e": "ng e2e"
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/common": "^11.0.0",
|
||||
"@angular/core": "^11.0.0",
|
||||
|
||||
@@ -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=">
|
||||
|
||||
@@ -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 { }
|
||||
@@ -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>
|
||||
55
ui/src/app/modals/app-action-input/app-action-input.page.ts
Normal file
55
ui/src/app/modals/app-action-input/app-action-input.page.ts
Normal 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()
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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],
|
||||
})
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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],
|
||||
})
|
||||
|
||||
@@ -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>
|
||||
@@ -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],
|
||||
})
|
||||
|
||||
@@ -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;">
|
||||
|
||||
@@ -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/>
|
||||
|
||||
@@ -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: [],
|
||||
|
||||
@@ -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;">
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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'
|
||||
|
||||
Reference in New Issue
Block a user