mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 20:14:49 +00:00
Feature/shared refactor (#1176)
* refactor: move most of the shared entities to @start8labs/shared library
This commit is contained in:
@@ -1,8 +1,15 @@
|
||||
import { NgModule } from '@angular/core'
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { IonicModule } from '@ionic/angular'
|
||||
import { BackupDrivesComponent, BackupDrivesHeaderComponent, BackupDrivesStatusComponent } from './backup-drives.component'
|
||||
import { SharingModule } from '../../modules/sharing.module'
|
||||
import {
|
||||
BackupDrivesComponent,
|
||||
BackupDrivesHeaderComponent,
|
||||
BackupDrivesStatusComponent,
|
||||
} from './backup-drives.component'
|
||||
import {
|
||||
UnitConversionPipesModule,
|
||||
TextSpinnerComponentModule,
|
||||
} from '@start9labs/shared'
|
||||
import { GenericFormPageModule } from 'src/app/modals/generic-form/generic-form.module'
|
||||
|
||||
@NgModule({
|
||||
@@ -14,7 +21,8 @@ import { GenericFormPageModule } from 'src/app/modals/generic-form/generic-form.
|
||||
imports: [
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
SharingModule,
|
||||
UnitConversionPipesModule,
|
||||
TextSpinnerComponentModule,
|
||||
GenericFormPageModule,
|
||||
],
|
||||
exports: [
|
||||
@@ -23,4 +31,4 @@ import { GenericFormPageModule } from 'src/app/modals/generic-form/generic-form.
|
||||
BackupDrivesStatusComponent,
|
||||
],
|
||||
})
|
||||
export class BackupDrivesComponentModule { }
|
||||
export class BackupDrivesComponentModule {}
|
||||
|
||||
@@ -1,12 +1,21 @@
|
||||
import { Component, EventEmitter, Input, Output } from '@angular/core'
|
||||
import { BackupService } from './backup.service'
|
||||
import { CifsBackupTarget, DiskBackupTarget, RR } from 'src/app/services/api/api.types'
|
||||
import { ActionSheetController, AlertController, LoadingController, ModalController } from '@ionic/angular'
|
||||
import {
|
||||
CifsBackupTarget,
|
||||
DiskBackupTarget,
|
||||
RR,
|
||||
} from 'src/app/services/api/api.types'
|
||||
import {
|
||||
ActionSheetController,
|
||||
AlertController,
|
||||
LoadingController,
|
||||
ModalController,
|
||||
} from '@ionic/angular'
|
||||
import { GenericFormPage } from 'src/app/modals/generic-form/generic-form.page'
|
||||
import { ConfigSpec } from 'src/app/pkg-config/config-types'
|
||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { ErrorToastService } from 'src/app/services/error-toast.service'
|
||||
import { MappedBackupTarget } from 'src/app/util/misc.util'
|
||||
import { MappedBackupTarget } from 'src/app/types/mapped-backup-target'
|
||||
|
||||
@Component({
|
||||
selector: 'backup-drives',
|
||||
@@ -15,10 +24,12 @@ import { MappedBackupTarget } from 'src/app/util/misc.util'
|
||||
})
|
||||
export class BackupDrivesComponent {
|
||||
@Input() type: 'create' | 'restore'
|
||||
@Output() onSelect: EventEmitter<MappedBackupTarget<CifsBackupTarget | DiskBackupTarget>> = new EventEmitter()
|
||||
@Output() onSelect: EventEmitter<
|
||||
MappedBackupTarget<CifsBackupTarget | DiskBackupTarget>
|
||||
> = new EventEmitter()
|
||||
loadingText: string
|
||||
|
||||
constructor (
|
||||
constructor(
|
||||
private readonly loadingCtrl: LoadingController,
|
||||
private readonly actionCtrl: ActionSheetController,
|
||||
private readonly alertCtrl: AlertController,
|
||||
@@ -26,22 +37,30 @@ export class BackupDrivesComponent {
|
||||
private readonly embassyApi: ApiService,
|
||||
private readonly errToast: ErrorToastService,
|
||||
public readonly backupService: BackupService,
|
||||
) { }
|
||||
) {}
|
||||
|
||||
ngOnInit () {
|
||||
this.loadingText = this.type === 'create' ? 'Fetching Backup Targets' : 'Fetching Backup Sources'
|
||||
ngOnInit() {
|
||||
this.loadingText =
|
||||
this.type === 'create'
|
||||
? 'Fetching Backup Targets'
|
||||
: 'Fetching Backup Sources'
|
||||
this.backupService.getBackupTargets()
|
||||
}
|
||||
|
||||
select (target: MappedBackupTarget<CifsBackupTarget | DiskBackupTarget>): void {
|
||||
select(
|
||||
target: MappedBackupTarget<CifsBackupTarget | DiskBackupTarget>,
|
||||
): void {
|
||||
if (target.entry.type === 'cifs' && !target.entry.mountable) {
|
||||
const message = 'Unable to connect to shared folder. Ensure (1) target computer is connected to LAN, (2) target folder is being shared, and (3) hostname, path, and credentials are accurate.'
|
||||
const message =
|
||||
'Unable to connect to shared folder. Ensure (1) target computer is connected to LAN, (2) target folder is being shared, and (3) hostname, path, and credentials are accurate.'
|
||||
this.presentAlertError(message)
|
||||
return
|
||||
}
|
||||
|
||||
if (this.type === 'restore' && !target.hasValidBackup) {
|
||||
const message = `${target.entry.type === 'cifs' ? 'Shared folder' : 'Drive partition'} does not contain a valid Embassy backup.`
|
||||
const message = `${
|
||||
target.entry.type === 'cifs' ? 'Shared folder' : 'Drive partition'
|
||||
} does not contain a valid Embassy backup.`
|
||||
this.presentAlertError(message)
|
||||
return
|
||||
}
|
||||
@@ -49,7 +68,7 @@ export class BackupDrivesComponent {
|
||||
this.onSelect.emit(target)
|
||||
}
|
||||
|
||||
async presentModalAddCifs (): Promise<void> {
|
||||
async presentModalAddCifs(): Promise<void> {
|
||||
const modal = await this.modalCtrl.create({
|
||||
component: GenericFormPage,
|
||||
componentProps: {
|
||||
@@ -69,7 +88,10 @@ export class BackupDrivesComponent {
|
||||
await modal.present()
|
||||
}
|
||||
|
||||
async presentActionCifs (target: MappedBackupTarget<CifsBackupTarget>, index: number): Promise<void> {
|
||||
async presentActionCifs(
|
||||
target: MappedBackupTarget<CifsBackupTarget>,
|
||||
index: number,
|
||||
): Promise<void> {
|
||||
const entry = target.entry as CifsBackupTarget
|
||||
|
||||
const action = await this.actionCtrl.create({
|
||||
@@ -93,8 +115,12 @@ export class BackupDrivesComponent {
|
||||
},
|
||||
},
|
||||
{
|
||||
text: this.type === 'create' ? 'Create Backup' : 'Restore From Backup',
|
||||
icon: this.type === 'create' ? 'cloud-upload-outline' : 'cloud-download-outline',
|
||||
text:
|
||||
this.type === 'create' ? 'Create Backup' : 'Restore From Backup',
|
||||
icon:
|
||||
this.type === 'create'
|
||||
? 'cloud-upload-outline'
|
||||
: 'cloud-download-outline',
|
||||
handler: () => {
|
||||
this.select(target)
|
||||
},
|
||||
@@ -105,7 +131,7 @@ export class BackupDrivesComponent {
|
||||
await action.present()
|
||||
}
|
||||
|
||||
private async presentAlertError (message: string): Promise<void> {
|
||||
private async presentAlertError(message: string): Promise<void> {
|
||||
const alert = await this.alertCtrl.create({
|
||||
header: 'Error',
|
||||
message,
|
||||
@@ -114,7 +140,7 @@ export class BackupDrivesComponent {
|
||||
await alert.present()
|
||||
}
|
||||
|
||||
private async addCifs (value: RR.AddBackupTargetReq): Promise<boolean> {
|
||||
private async addCifs(value: RR.AddBackupTargetReq): Promise<boolean> {
|
||||
const loader = await this.loadingCtrl.create({
|
||||
spinner: 'lines',
|
||||
message: 'Testing connectivity to shared folder...',
|
||||
@@ -139,7 +165,11 @@ export class BackupDrivesComponent {
|
||||
}
|
||||
}
|
||||
|
||||
private async presentModalEditCifs (id: string, entry: CifsBackupTarget, index: number): Promise<void> {
|
||||
private async presentModalEditCifs(
|
||||
id: string,
|
||||
entry: CifsBackupTarget,
|
||||
index: number,
|
||||
): Promise<void> {
|
||||
const { hostname, path, username } = entry
|
||||
|
||||
const modal = await this.modalCtrl.create({
|
||||
@@ -166,7 +196,10 @@ export class BackupDrivesComponent {
|
||||
await modal.present()
|
||||
}
|
||||
|
||||
private async editCifs (value: RR.UpdateBackupTargetReq, index: number): Promise<void> {
|
||||
private async editCifs(
|
||||
value: RR.UpdateBackupTargetReq,
|
||||
index: number,
|
||||
): Promise<void> {
|
||||
const loader = await this.loadingCtrl.create({
|
||||
spinner: 'lines',
|
||||
message: 'Testing connectivity to shared folder...',
|
||||
@@ -185,7 +218,7 @@ export class BackupDrivesComponent {
|
||||
}
|
||||
}
|
||||
|
||||
private async deleteCifs (id: string, index: number): Promise<void> {
|
||||
private async deleteCifs(id: string, index: number): Promise<void> {
|
||||
const loader = await this.loadingCtrl.create({
|
||||
spinner: 'lines',
|
||||
message: 'Removing...',
|
||||
@@ -204,7 +237,6 @@ export class BackupDrivesComponent {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'backup-drives-header',
|
||||
templateUrl: './backup-drives-header.component.html',
|
||||
@@ -214,16 +246,13 @@ export class BackupDrivesHeaderComponent {
|
||||
@Input() title: string
|
||||
@Output() onClose: EventEmitter<void> = new EventEmitter()
|
||||
|
||||
constructor (
|
||||
public readonly backupService: BackupService,
|
||||
) { }
|
||||
constructor(public readonly backupService: BackupService) {}
|
||||
|
||||
refresh () {
|
||||
refresh() {
|
||||
this.backupService.getBackupTargets()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'backup-drives-status',
|
||||
templateUrl: './backup-drives-status.component.html',
|
||||
@@ -238,7 +267,8 @@ const CifsSpec: ConfigSpec = {
|
||||
hostname: {
|
||||
type: 'string',
|
||||
name: 'Hostname',
|
||||
description: 'The hostname of your target device on the Local Area Network.',
|
||||
description:
|
||||
'The hostname of your target device on the Local Area Network.',
|
||||
placeholder: `e.g. 'My Computer' OR 'my-computer.local'`,
|
||||
pattern: '^[a-zA-Z0-9._-]+( [a-zA-Z0-9]+)*$',
|
||||
'pattern-description': `Must be a valid hostname. e.g. 'My Computer' OR 'my-computer.local'`,
|
||||
@@ -249,7 +279,8 @@ const CifsSpec: ConfigSpec = {
|
||||
path: {
|
||||
type: 'string',
|
||||
name: 'Path',
|
||||
description: 'The directory path to the shared folder on your target device.',
|
||||
description:
|
||||
'The directory path to the shared folder on your target device.',
|
||||
placeholder: 'e.g. /Desktop/my-folder',
|
||||
nullable: false,
|
||||
masked: false,
|
||||
|
||||
@@ -2,9 +2,13 @@ import { Injectable } from '@angular/core'
|
||||
import { IonicSafeString } from '@ionic/core'
|
||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { getErrorMessage } from 'src/app/services/error-toast.service'
|
||||
import { BackupTarget, CifsBackupTarget, DiskBackupTarget } from 'src/app/services/api/api.types'
|
||||
import { Emver } from 'src/app/services/emver.service'
|
||||
import { MappedBackupTarget } from 'src/app/util/misc.util'
|
||||
import {
|
||||
BackupTarget,
|
||||
CifsBackupTarget,
|
||||
DiskBackupTarget,
|
||||
} from 'src/app/services/api/api.types'
|
||||
import { MappedBackupTarget } from 'src/app/types/mapped-backup-target'
|
||||
import { Emver } from '@start9labs/shared'
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
@@ -15,36 +19,36 @@ export class BackupService {
|
||||
loading = true
|
||||
loadingError: string | IonicSafeString
|
||||
|
||||
constructor (
|
||||
constructor(
|
||||
private readonly embassyApi: ApiService,
|
||||
private readonly emver: Emver,
|
||||
) { }
|
||||
) {}
|
||||
|
||||
async getBackupTargets (): Promise<void> {
|
||||
async getBackupTargets(): Promise<void> {
|
||||
this.loading = true
|
||||
|
||||
try {
|
||||
const targets = await this.embassyApi.getBackupTargets({ })
|
||||
const targets = await this.embassyApi.getBackupTargets({})
|
||||
// cifs
|
||||
this.cifs = Object.entries(targets)
|
||||
.filter(([_, target]) => target.type === 'cifs')
|
||||
.map(([id, cifs]) => {
|
||||
return {
|
||||
id,
|
||||
hasValidBackup: this.hasValidBackup(cifs),
|
||||
entry: cifs as CifsBackupTarget,
|
||||
}
|
||||
})
|
||||
.filter(([_, target]) => target.type === 'cifs')
|
||||
.map(([id, cifs]) => {
|
||||
return {
|
||||
id,
|
||||
hasValidBackup: this.hasValidBackup(cifs),
|
||||
entry: cifs as CifsBackupTarget,
|
||||
}
|
||||
})
|
||||
// drives
|
||||
this.drives = Object.entries(targets)
|
||||
.filter(([_, target]) => target.type === 'disk')
|
||||
.map(([id, drive]) => {
|
||||
return {
|
||||
id,
|
||||
hasValidBackup: this.hasValidBackup(drive),
|
||||
entry: drive as DiskBackupTarget,
|
||||
}
|
||||
})
|
||||
.filter(([_, target]) => target.type === 'disk')
|
||||
.map(([id, drive]) => {
|
||||
return {
|
||||
id,
|
||||
hasValidBackup: this.hasValidBackup(drive),
|
||||
entry: drive as DiskBackupTarget,
|
||||
}
|
||||
})
|
||||
} catch (e) {
|
||||
this.loadingError = getErrorMessage(e)
|
||||
} finally {
|
||||
@@ -52,7 +56,9 @@ export class BackupService {
|
||||
}
|
||||
}
|
||||
|
||||
hasValidBackup (target: BackupTarget): boolean {
|
||||
return [0, 1].includes(this.emver.compare(target['embassy-os']?.version, '0.3.0'))
|
||||
hasValidBackup(target: BackupTarget): boolean {
|
||||
return [0, 1].includes(
|
||||
this.emver.compare(target['embassy-os']?.version, '0.3.0'),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
<div style="position: relative; margin-right: 1vh;">
|
||||
<ion-badge mode="md" class="md-badge" *ngIf="unreadCount && !sidebarOpen" color="danger">{{ unreadCount }}</ion-badge>
|
||||
<div class="wrapper">
|
||||
<ion-badge
|
||||
*ngIf="unreadCount && !sidebarOpen"
|
||||
mode="md"
|
||||
class="md-badge"
|
||||
color="danger"
|
||||
>
|
||||
{{ unreadCount }}
|
||||
</ion-badge>
|
||||
<ion-menu-button color="dark"></ion-menu-button>
|
||||
</div>
|
||||
|
||||
@@ -1,18 +1,11 @@
|
||||
import { NgModule } from '@angular/core'
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { BadgeMenuComponent } from './badge-menu.component'
|
||||
import { IonicModule } from '@ionic/angular'
|
||||
import { SharingModule } from 'src/app/modules/sharing.module'
|
||||
import { BadgeMenuComponent } from './badge-menu.component'
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
BadgeMenuComponent,
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
SharingModule,
|
||||
],
|
||||
declarations: [BadgeMenuComponent],
|
||||
imports: [CommonModule, IonicModule],
|
||||
exports: [BadgeMenuComponent],
|
||||
})
|
||||
export class BadgeMenuComponentModule { }
|
||||
export class BadgeMenuComponentModule {}
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
.wrapper {
|
||||
position: relative;
|
||||
margin-right: 1vh;
|
||||
}
|
||||
|
||||
.md-badge {
|
||||
background-color: var(--ion-color-danger);
|
||||
position: absolute;
|
||||
|
||||
@@ -1,29 +1,25 @@
|
||||
import { NgModule } from '@angular/core'
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { FormObjectComponent, FormLabelComponent, FormErrorComponent } from './form-object.component'
|
||||
import {
|
||||
FormObjectComponent,
|
||||
FormLabelComponent,
|
||||
FormErrorComponent,
|
||||
} from './form-object.component'
|
||||
import { IonicModule } from '@ionic/angular'
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||
import { SharingModule } from 'src/app/modules/sharing.module'
|
||||
import { SharedPipesModule } from '@start9labs/shared'
|
||||
import { EnumListPageModule } from 'src/app/modals/enum-list/enum-list.module'
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
FormObjectComponent,
|
||||
FormLabelComponent,
|
||||
FormErrorComponent,
|
||||
],
|
||||
declarations: [FormObjectComponent, FormLabelComponent, FormErrorComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
SharingModule,
|
||||
SharedPipesModule,
|
||||
EnumListPageModule,
|
||||
],
|
||||
exports: [
|
||||
FormObjectComponent,
|
||||
FormLabelComponent,
|
||||
FormErrorComponent,
|
||||
],
|
||||
exports: [FormObjectComponent, FormLabelComponent, FormErrorComponent],
|
||||
})
|
||||
export class FormObjectComponentModule { }
|
||||
export class FormObjectComponentModule {}
|
||||
|
||||
@@ -1,11 +1,28 @@
|
||||
import { Component, Input, Output, EventEmitter } from '@angular/core'
|
||||
import { AbstractFormGroupDirective, FormArray, FormGroup } from '@angular/forms'
|
||||
import { AlertButton, AlertController, IonicSafeString, ModalController } from '@ionic/angular'
|
||||
import { ConfigSpec, ListValueSpecOf, ValueSpec, ValueSpecBoolean, ValueSpecList, ValueSpecListOf, ValueSpecUnion } from 'src/app/pkg-config/config-types'
|
||||
import {
|
||||
AbstractFormGroupDirective,
|
||||
FormArray,
|
||||
FormGroup,
|
||||
} from '@angular/forms'
|
||||
import {
|
||||
AlertButton,
|
||||
AlertController,
|
||||
IonicSafeString,
|
||||
ModalController,
|
||||
} from '@ionic/angular'
|
||||
import {
|
||||
ConfigSpec,
|
||||
ListValueSpecOf,
|
||||
ValueSpec,
|
||||
ValueSpecBoolean,
|
||||
ValueSpecList,
|
||||
ValueSpecListOf,
|
||||
ValueSpecUnion,
|
||||
} from 'src/app/pkg-config/config-types'
|
||||
import { FormService } from 'src/app/services/form.service'
|
||||
import { Range } from 'src/app/pkg-config/config-utilities'
|
||||
import { EnumListPage } from 'src/app/modals/enum-list/enum-list.page'
|
||||
import { pauseFor } from 'src/app/util/misc.util'
|
||||
import { pauseFor } from '@start9labs/shared'
|
||||
import { v4 } from 'uuid'
|
||||
const Mustache = require('mustache')
|
||||
|
||||
@@ -22,31 +39,38 @@ export class FormObjectComponent {
|
||||
@Input() showEdited: boolean = false
|
||||
@Output() onInputChange = new EventEmitter<void>()
|
||||
@Output() onExpand = new EventEmitter<void>()
|
||||
warningAck: { [key: string]: boolean } = { }
|
||||
unmasked: { [key: string]: boolean } = { }
|
||||
objectDisplay: { [key: string]: { expanded: boolean, height: string } } = { }
|
||||
objectListDisplay: { [key: string]: { expanded: boolean, height: string, displayAs: string }[] } = { }
|
||||
warningAck: { [key: string]: boolean } = {}
|
||||
unmasked: { [key: string]: boolean } = {}
|
||||
objectDisplay: { [key: string]: { expanded: boolean; height: string } } = {}
|
||||
objectListDisplay: {
|
||||
[key: string]: { expanded: boolean; height: string; displayAs: string }[]
|
||||
} = {}
|
||||
private objectId = v4()
|
||||
|
||||
Object = Object
|
||||
|
||||
constructor (
|
||||
constructor(
|
||||
private readonly alertCtrl: AlertController,
|
||||
private readonly modalCtrl: ModalController,
|
||||
private readonly formService: FormService,
|
||||
) { }
|
||||
) {}
|
||||
|
||||
ngOnInit () {
|
||||
ngOnInit() {
|
||||
Object.keys(this.objectSpec).forEach(key => {
|
||||
const spec = this.objectSpec[key]
|
||||
|
||||
if (spec.type === 'list' && ['object', 'union'].includes(spec.subtype)) {
|
||||
this.objectListDisplay[key] = [];
|
||||
(this.formGroup.get(key).value as any[]).forEach((obj, index) => {
|
||||
const displayAs = (spec.spec as ListValueSpecOf<'object'>)['display-as']
|
||||
this.objectListDisplay[key] = []
|
||||
this.formGroup.get(key).value.forEach((obj, index) => {
|
||||
const displayAs = (spec.spec as ListValueSpecOf<'object'>)[
|
||||
'display-as'
|
||||
]
|
||||
this.objectListDisplay[key][index] = {
|
||||
expanded: false,
|
||||
height: '0px',
|
||||
displayAs: displayAs ? (Mustache as any).render(displayAs, obj) : '',
|
||||
displayAs: displayAs
|
||||
? (Mustache as any).render(displayAs, obj)
|
||||
: '',
|
||||
}
|
||||
})
|
||||
} else if (['object', 'union'].includes(spec.type)) {
|
||||
@@ -58,11 +82,11 @@ export class FormObjectComponent {
|
||||
})
|
||||
}
|
||||
|
||||
getEnumListDisplay (arr: string[], spec: ListValueSpecOf<'enum'>): string {
|
||||
getEnumListDisplay(arr: string[], spec: ListValueSpecOf<'enum'>): string {
|
||||
return arr.map((v: string) => spec['value-names'][v]).join(', ')
|
||||
}
|
||||
|
||||
updateUnion (e: any): void {
|
||||
updateUnion(e: any): void {
|
||||
const primary = this.unionSpec.tag.id
|
||||
|
||||
Object.keys(this.formGroup.controls).forEach(control => {
|
||||
@@ -70,26 +94,31 @@ export class FormObjectComponent {
|
||||
this.formGroup.removeControl(control)
|
||||
})
|
||||
|
||||
const unionGroup = this.formService.getUnionObject(this.unionSpec as ValueSpecUnion, e.detail.value)
|
||||
const unionGroup = this.formService.getUnionObject(
|
||||
this.unionSpec as ValueSpecUnion,
|
||||
e.detail.value,
|
||||
)
|
||||
|
||||
Object.keys(unionGroup.controls).forEach(control => {
|
||||
if (control === primary) return
|
||||
this.formGroup.addControl(control, unionGroup.controls[control])
|
||||
})
|
||||
|
||||
Object.entries(this.unionSpec.variants[e.detail.value]).forEach(([key, value]) => {
|
||||
if (['object', 'union'].includes(value.type)) {
|
||||
this.objectDisplay[key] = {
|
||||
expanded: false,
|
||||
height: '0px',
|
||||
Object.entries(this.unionSpec.variants[e.detail.value]).forEach(
|
||||
([key, value]) => {
|
||||
if (['object', 'union'].includes(value.type)) {
|
||||
this.objectDisplay[key] = {
|
||||
expanded: false,
|
||||
height: '0px',
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
this.onExpand.emit()
|
||||
}
|
||||
|
||||
resize (key: string, i?: number): void {
|
||||
resize(key: string, i?: number): void {
|
||||
setTimeout(() => {
|
||||
if (i !== undefined) {
|
||||
this.objectListDisplay[key][i].height = this.getDocSize(key, i)
|
||||
@@ -100,11 +129,11 @@ export class FormObjectComponent {
|
||||
}, 250) // 250 to match transition-duration, defined in html
|
||||
}
|
||||
|
||||
addListItemWrapper (key: string, spec: ValueSpec) {
|
||||
addListItemWrapper(key: string, spec: ValueSpec) {
|
||||
this.presentAlertChangeWarning(key, spec, () => this.addListItem(key))
|
||||
}
|
||||
|
||||
addListItem (key: string, markDirty = true, val?: string): void {
|
||||
addListItem(key: string, markDirty = true, val?: string): void {
|
||||
const arr = this.formGroup.get(key) as FormArray
|
||||
if (markDirty) arr.markAsDirty()
|
||||
const listSpec = this.objectSpec[key] as ValueSpecList
|
||||
@@ -112,7 +141,9 @@ export class FormObjectComponent {
|
||||
newItem.markAllAsTouched()
|
||||
arr.insert(0, newItem)
|
||||
if (['object', 'union'].includes(listSpec.subtype)) {
|
||||
const displayAs = (listSpec.spec as ListValueSpecOf<'object'>)['display-as']
|
||||
const displayAs = (listSpec.spec as ListValueSpecOf<'object'>)[
|
||||
'display-as'
|
||||
]
|
||||
this.objectListDisplay[key].unshift({
|
||||
height: '0px',
|
||||
expanded: true,
|
||||
@@ -125,30 +156,39 @@ export class FormObjectComponent {
|
||||
}
|
||||
}
|
||||
|
||||
toggleExpandObject (key: string) {
|
||||
toggleExpandObject(key: string) {
|
||||
this.objectDisplay[key].expanded = !this.objectDisplay[key].expanded
|
||||
this.objectDisplay[key].height = this.objectDisplay[key].expanded ? this.getDocSize(key) : '0px'
|
||||
this.objectDisplay[key].height = this.objectDisplay[key].expanded
|
||||
? this.getDocSize(key)
|
||||
: '0px'
|
||||
this.onExpand.emit()
|
||||
}
|
||||
|
||||
toggleExpandListObject (key: string, i: number) {
|
||||
this.objectListDisplay[key][i].expanded = !this.objectListDisplay[key][i].expanded
|
||||
this.objectListDisplay[key][i].height = this.objectListDisplay[key][i].expanded ? this.getDocSize(key, i) : '0px'
|
||||
toggleExpandListObject(key: string, i: number) {
|
||||
this.objectListDisplay[key][i].expanded =
|
||||
!this.objectListDisplay[key][i].expanded
|
||||
this.objectListDisplay[key][i].height = this.objectListDisplay[key][i]
|
||||
.expanded
|
||||
? this.getDocSize(key, i)
|
||||
: '0px'
|
||||
}
|
||||
|
||||
updateLabel (key: string, i: number, displayAs: string) {
|
||||
this.objectListDisplay[key][i].displayAs = displayAs ? Mustache.render(displayAs, this.formGroup.get(key).value[i]) : ''
|
||||
updateLabel(key: string, i: number, displayAs: string) {
|
||||
this.objectListDisplay[key][i].displayAs = displayAs
|
||||
? Mustache.render(displayAs, this.formGroup.get(key).value[i])
|
||||
: ''
|
||||
}
|
||||
|
||||
getWarningText (text: string): IonicSafeString {
|
||||
if (text) return new IonicSafeString(`<ion-text color="warning">${text}</ion-text>`)
|
||||
getWarningText(text: string): IonicSafeString {
|
||||
if (text)
|
||||
return new IonicSafeString(`<ion-text color="warning">${text}</ion-text>`)
|
||||
}
|
||||
|
||||
handleInputChange () {
|
||||
handleInputChange() {
|
||||
this.onInputChange.emit()
|
||||
}
|
||||
|
||||
handleBooleanChange (key: string, spec: ValueSpecBoolean) {
|
||||
handleBooleanChange(key: string, spec: ValueSpecBoolean) {
|
||||
if (spec.warning) {
|
||||
const current = this.formGroup.get(key).value
|
||||
const cancelFn = () => this.formGroup.get(key).setValue(!current)
|
||||
@@ -156,7 +196,11 @@ export class FormObjectComponent {
|
||||
}
|
||||
}
|
||||
|
||||
async presentModalEnumList (key: string, spec: ValueSpecListOf<'enum'>, current: string[]) {
|
||||
async presentModalEnumList(
|
||||
key: string,
|
||||
spec: ValueSpecListOf<'enum'>,
|
||||
current: string[],
|
||||
) {
|
||||
const modal = await this.modalCtrl.create({
|
||||
componentProps: {
|
||||
key,
|
||||
@@ -175,7 +219,12 @@ export class FormObjectComponent {
|
||||
await modal.present()
|
||||
}
|
||||
|
||||
async presentAlertChangeWarning (key: string, spec: ValueSpec, okFn?: Function, cancelFn?: Function) {
|
||||
async presentAlertChangeWarning(
|
||||
key: string,
|
||||
spec: ValueSpec,
|
||||
okFn?: Function,
|
||||
cancelFn?: Function,
|
||||
) {
|
||||
if (!spec.warning || this.warningAck[key]) return okFn ? okFn() : null
|
||||
this.warningAck[key] = true
|
||||
|
||||
@@ -207,7 +256,7 @@ export class FormObjectComponent {
|
||||
await alert.present()
|
||||
}
|
||||
|
||||
async presentAlertDelete (key: string, index: number) {
|
||||
async presentAlertDelete(key: string, index: number) {
|
||||
const alert = await this.alertCtrl.create({
|
||||
header: 'Confirm',
|
||||
message: 'Are you sure you want to delete this entry?',
|
||||
@@ -228,17 +277,19 @@ export class FormObjectComponent {
|
||||
await alert.present()
|
||||
}
|
||||
|
||||
private deleteListItem (key: string, index: number, markDirty = true): void {
|
||||
if (this.objectListDisplay[key]) this.objectListDisplay[key][index].height = '0px'
|
||||
private deleteListItem(key: string, index: number, markDirty = true): void {
|
||||
if (this.objectListDisplay[key])
|
||||
this.objectListDisplay[key][index].height = '0px'
|
||||
const arr = this.formGroup.get(key) as FormArray
|
||||
if (markDirty) arr.markAsDirty()
|
||||
pauseFor(250).then(() => {
|
||||
if (this.objectListDisplay[key]) this.objectListDisplay[key].splice(index, 1)
|
||||
if (this.objectListDisplay[key])
|
||||
this.objectListDisplay[key].splice(index, 1)
|
||||
arr.removeAt(index)
|
||||
})
|
||||
}
|
||||
|
||||
private updateEnumList (key: string, current: string[], updated: string[]) {
|
||||
private updateEnumList(key: string, current: string[], updated: string[]) {
|
||||
this.formGroup.get(key).markAsDirty()
|
||||
|
||||
for (let i = current.length - 1; i >= 0; i--) {
|
||||
@@ -254,17 +305,16 @@ export class FormObjectComponent {
|
||||
})
|
||||
}
|
||||
|
||||
private getDocSize (key: string, index = 0) {
|
||||
private getDocSize(key: string, index = 0) {
|
||||
const element = document.getElementById(this.getElementId(key, index))
|
||||
return `${element.scrollHeight}px`
|
||||
}
|
||||
|
||||
getElementId (key: string, index = 0): string {
|
||||
getElementId(key: string, index = 0): string {
|
||||
return `${key}-${index}-${this.objectId}`
|
||||
}
|
||||
|
||||
async presentUnionTagDescription (name: string, description: string) {
|
||||
|
||||
async presentUnionTagDescription(name: string, description: string) {
|
||||
const alert = await this.alertCtrl.create({
|
||||
header: name,
|
||||
message: description,
|
||||
@@ -272,7 +322,7 @@ export class FormObjectComponent {
|
||||
await alert.present()
|
||||
}
|
||||
|
||||
asIsOrder () {
|
||||
asIsOrder() {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
@@ -293,11 +343,9 @@ export class FormLabelComponent {
|
||||
Range = Range
|
||||
@Input() data: HeaderData
|
||||
|
||||
constructor (
|
||||
private readonly alertCtrl: AlertController,
|
||||
) { }
|
||||
constructor(private readonly alertCtrl: AlertController) {}
|
||||
|
||||
async presentAlertDescription () {
|
||||
async presentAlertDescription() {
|
||||
const { name, description } = this.data.spec
|
||||
|
||||
const alert = await this.alertCtrl.create({
|
||||
@@ -308,7 +356,6 @@ export class FormLabelComponent {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'form-error',
|
||||
templateUrl: './form-error.component.html',
|
||||
|
||||
@@ -3,18 +3,16 @@ import { CommonModule } from '@angular/common'
|
||||
import { AlertComponent } from './alert.component'
|
||||
import { IonicModule } from '@ionic/angular'
|
||||
import { RouterModule } from '@angular/router'
|
||||
import { SharingModule } from 'src/app/modules/sharing.module'
|
||||
import { MarkdownPipeModule } from '@start9labs/shared'
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AlertComponent,
|
||||
],
|
||||
declarations: [AlertComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
RouterModule.forChild([]),
|
||||
SharingModule,
|
||||
MarkdownPipeModule,
|
||||
],
|
||||
exports: [AlertComponent],
|
||||
})
|
||||
export class AlertComponentModule { }
|
||||
export class AlertComponentModule {}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Component, Input } from '@angular/core'
|
||||
import { BehaviorSubject, from, Subject } from 'rxjs'
|
||||
import { takeUntil } from 'rxjs/operators'
|
||||
import { capitalizeFirstLetter } from 'src/app/util/misc.util'
|
||||
import { capitalizeFirstLetter } from '@start9labs/shared'
|
||||
import { markAsLoadingDuring$ } from '../loadable'
|
||||
import { WizardAction } from '../wizard-types'
|
||||
|
||||
@@ -30,16 +30,21 @@ export class CompleteComponent {
|
||||
|
||||
message: string
|
||||
|
||||
load () {
|
||||
markAsLoadingDuring$(this.loading$, from(this.params.executeAction())).pipe(takeUntil(this.cancel$)).subscribe(
|
||||
{
|
||||
error: e => this.transitions.error(new Error(`${this.params.action} failed: ${e.message || e}`)),
|
||||
load() {
|
||||
markAsLoadingDuring$(this.loading$, from(this.params.executeAction()))
|
||||
.pipe(takeUntil(this.cancel$))
|
||||
.subscribe({
|
||||
error: e =>
|
||||
this.transitions.error(
|
||||
new Error(`${this.params.action} failed: ${e.message || e}`),
|
||||
),
|
||||
complete: () => this.transitions.final(),
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
ngOnInit () {
|
||||
this.message = `${capitalizeFirstLetter(this.params.verb)} ${this.params.title}...`
|
||||
ngOnInit() {
|
||||
this.message = `${capitalizeFirstLetter(this.params.verb)} ${
|
||||
this.params.title
|
||||
}...`
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,18 +3,16 @@ import { CommonModule } from '@angular/common'
|
||||
import { DependentsComponent } from './dependents.component'
|
||||
import { IonicModule } from '@ionic/angular'
|
||||
import { RouterModule } from '@angular/router'
|
||||
import { SharingModule } from 'src/app/modules/sharing.module'
|
||||
import { SharedPipesModule } from '@start9labs/shared'
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
DependentsComponent,
|
||||
],
|
||||
declarations: [DependentsComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
RouterModule.forChild([]),
|
||||
SharingModule,
|
||||
SharedPipesModule,
|
||||
],
|
||||
exports: [DependentsComponent],
|
||||
})
|
||||
export class DependentsComponentModule { }
|
||||
export class DependentsComponentModule {}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { BehaviorSubject, from, Subject } from 'rxjs'
|
||||
import { takeUntil, tap } from 'rxjs/operators'
|
||||
import { Breakages } from 'src/app/services/api/api.types'
|
||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||
import { capitalizeFirstLetter, isEmptyObject } from 'src/app/util/misc.util'
|
||||
import { capitalizeFirstLetter, isEmptyObject } from '@start9labs/shared'
|
||||
import { markAsLoadingDuring$ } from '../loadable'
|
||||
import { WizardAction } from '../wizard-types'
|
||||
|
||||
@@ -14,9 +14,9 @@ import { WizardAction } from '../wizard-types'
|
||||
})
|
||||
export class DependentsComponent {
|
||||
@Input() params: {
|
||||
title: string,
|
||||
action: WizardAction, //Are you sure you want to *uninstall*...,
|
||||
verb: string, // *Uninstalling* will cause problems...
|
||||
title: string
|
||||
action: WizardAction //Are you sure you want to *uninstall*...,
|
||||
verb: string // *Uninstalling* will cause problems...
|
||||
fetchBreakages: () => Promise<Breakages>
|
||||
}
|
||||
@Input() transitions: {
|
||||
@@ -32,27 +32,37 @@ export class DependentsComponent {
|
||||
loading$ = new BehaviorSubject(false)
|
||||
cancel$ = new Subject<void>()
|
||||
|
||||
constructor (
|
||||
public readonly patch: PatchDbService,
|
||||
) { }
|
||||
constructor(public readonly patch: PatchDbService) {}
|
||||
|
||||
load () {
|
||||
load() {
|
||||
markAsLoadingDuring$(this.loading$, from(this.params.fetchBreakages()))
|
||||
.pipe(
|
||||
takeUntil(this.cancel$),
|
||||
tap(breakages => this.dependentBreakages = breakages),
|
||||
)
|
||||
.subscribe(
|
||||
{
|
||||
.pipe(
|
||||
takeUntil(this.cancel$),
|
||||
tap(breakages => (this.dependentBreakages = breakages)),
|
||||
)
|
||||
.subscribe({
|
||||
complete: () => {
|
||||
if (this.dependentBreakages && !isEmptyObject(this.dependentBreakages)) {
|
||||
this.dependentViolation = `${capitalizeFirstLetter(this.params.verb)} ${this.params.title} will prohibit the following services from functioning properly and may cause them to stop if they are currently running.`
|
||||
if (
|
||||
this.dependentBreakages &&
|
||||
!isEmptyObject(this.dependentBreakages)
|
||||
) {
|
||||
this.dependentViolation = `${capitalizeFirstLetter(
|
||||
this.params.verb,
|
||||
)} ${
|
||||
this.params.title
|
||||
} will prohibit the following services from functioning properly and may cause them to stop if they are currently running.`
|
||||
} else {
|
||||
this.transitions.next()
|
||||
}
|
||||
},
|
||||
error: (e: Error) => this.transitions.error(new Error(`Fetching dependent service information failed: ${e.message || e}`)),
|
||||
},
|
||||
)
|
||||
error: (e: Error) =>
|
||||
this.transitions.error(
|
||||
new Error(
|
||||
`Fetching dependent service information failed: ${
|
||||
e.message || e
|
||||
}`,
|
||||
),
|
||||
),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,21 +3,19 @@ import { CommonModule } from '@angular/common'
|
||||
import { InstallWizardComponent } from './install-wizard.component'
|
||||
import { IonicModule } from '@ionic/angular'
|
||||
import { RouterModule } from '@angular/router'
|
||||
import { SharingModule } from 'src/app/modules/sharing.module'
|
||||
import { EmverPipesModule } from '@start9labs/shared'
|
||||
import { DependentsComponentModule } from './dependents/dependents.component.module'
|
||||
import { CompleteComponentModule } from './complete/complete.component.module'
|
||||
import { NotesComponentModule } from './notes/notes.component.module'
|
||||
import { AlertComponentModule } from './alert/alert.component.module'
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
InstallWizardComponent,
|
||||
],
|
||||
declarations: [InstallWizardComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
RouterModule.forChild([]),
|
||||
SharingModule,
|
||||
EmverPipesModule,
|
||||
DependentsComponentModule,
|
||||
CompleteComponentModule,
|
||||
NotesComponentModule,
|
||||
@@ -25,4 +23,4 @@ import { AlertComponentModule } from './alert/alert.component.module'
|
||||
],
|
||||
exports: [InstallWizardComponent],
|
||||
})
|
||||
export class InstallWizardComponentModule { }
|
||||
export class InstallWizardComponentModule {}
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
import { Component, Input, NgZone, QueryList, ViewChild, ViewChildren } from '@angular/core'
|
||||
import {
|
||||
Component,
|
||||
Input,
|
||||
NgZone,
|
||||
QueryList,
|
||||
ViewChild,
|
||||
ViewChildren,
|
||||
} from '@angular/core'
|
||||
import { IonContent, IonSlides, ModalController } from '@ionic/angular'
|
||||
import { capitalizeFirstLetter, pauseFor } from 'src/app/util/misc.util'
|
||||
import { capitalizeFirstLetter, pauseFor } from '@start9labs/shared'
|
||||
import { CompleteComponent } from './complete/complete.component'
|
||||
import { DependentsComponent } from './dependents/dependents.component'
|
||||
import { AlertComponent } from './alert/alert.component'
|
||||
@@ -30,54 +37,70 @@ export class InstallWizardComponent {
|
||||
//a slide component gives us hook into a slide. Allows us to call load when slide comes into view
|
||||
@ViewChildren('components')
|
||||
slideComponentsQL: QueryList<Loadable>
|
||||
get slideComponents (): Loadable[] { return this.slideComponentsQL.toArray() }
|
||||
get slideComponents(): Loadable[] {
|
||||
return this.slideComponentsQL.toArray()
|
||||
}
|
||||
|
||||
private slideIndex = 0
|
||||
get currentSlide (): Loadable {
|
||||
get currentSlide(): Loadable {
|
||||
return this.slideComponents[this.slideIndex]
|
||||
}
|
||||
get currentBottomBar (): SlideDefinition['bottomBar'] {
|
||||
get currentBottomBar(): SlideDefinition['bottomBar'] {
|
||||
return this.params.slideDefinitions[this.slideIndex].bottomBar
|
||||
}
|
||||
|
||||
initializing = true
|
||||
error = ''
|
||||
|
||||
constructor (
|
||||
constructor(
|
||||
private readonly modalController: ModalController,
|
||||
private readonly zone: NgZone,
|
||||
) { }
|
||||
) {}
|
||||
|
||||
ngAfterViewInit () {
|
||||
ngAfterViewInit() {
|
||||
this.currentSlide.load()
|
||||
this.slideContainer.update()
|
||||
this.slideContainer.lockSwipes(true)
|
||||
}
|
||||
|
||||
ionViewDidEnter () {
|
||||
ionViewDidEnter() {
|
||||
this.initializing = false
|
||||
}
|
||||
|
||||
// process bottom bar buttons
|
||||
private transition = (info: { next: any } | { error: Error } | { cancelled: true } | { final: true }) => {
|
||||
const i = info as { next?: any, error?: Error, cancelled?: true, final?: true }
|
||||
private transition = (
|
||||
info:
|
||||
| { next: any }
|
||||
| { error: Error }
|
||||
| { cancelled: true }
|
||||
| { final: true },
|
||||
) => {
|
||||
const i = info as {
|
||||
next?: any
|
||||
error?: Error
|
||||
cancelled?: true
|
||||
final?: true
|
||||
}
|
||||
if (i.cancelled) this.currentSlide.cancel$.next()
|
||||
if (i.final || i.cancelled) return this.modalController.dismiss(i)
|
||||
if (i.error) return this.error = capitalizeFirstLetter(i.error.message)
|
||||
if (i.error) return (this.error = capitalizeFirstLetter(i.error.message))
|
||||
|
||||
this.moveToNextSlide(i.next)
|
||||
}
|
||||
|
||||
// bottom bar button callbacks. Pass this into components if they need to trigger slide transitions independent of the bottom bar clicks
|
||||
transitions = {
|
||||
next: (prevResult: any) => this.transition({ next: prevResult || this.currentSlide.result }),
|
||||
next: (prevResult: any) =>
|
||||
this.transition({ next: prevResult || this.currentSlide.result }),
|
||||
cancel: () => this.transition({ cancelled: true }),
|
||||
final: () => this.transition({ final: true }),
|
||||
error: (e: Error) => this.transition({ error: e }),
|
||||
}
|
||||
|
||||
private async moveToNextSlide (prevResult?: any) {
|
||||
if (this.slideComponents[this.slideIndex + 1] === undefined) { return this.transition({ final: true }) }
|
||||
private async moveToNextSlide(prevResult?: any) {
|
||||
if (this.slideComponents[this.slideIndex + 1] === undefined) {
|
||||
return this.transition({ final: true })
|
||||
}
|
||||
this.zone.run(async () => {
|
||||
this.slideComponents[this.slideIndex + 1].load(prevResult)
|
||||
await pauseFor(50) // give the load ^ opportunity to propogate into slide before sliding it into view
|
||||
@@ -89,7 +112,7 @@ export class InstallWizardComponent {
|
||||
})
|
||||
}
|
||||
|
||||
async callTransition (transition: Function) {
|
||||
async callTransition(transition: Function) {
|
||||
this.transitioning = true
|
||||
await transition()
|
||||
this.transitioning = false
|
||||
@@ -98,14 +121,14 @@ export class InstallWizardComponent {
|
||||
|
||||
export interface SlideDefinition {
|
||||
slide:
|
||||
{ selector: 'dependents', params: DependentsComponent['params'] } |
|
||||
{ selector: 'complete', params: CompleteComponent['params'] } |
|
||||
{ selector: 'alert', params: AlertComponent['params'] } |
|
||||
{ selector: 'notes', params: NotesComponent['params'] }
|
||||
| { selector: 'dependents'; params: DependentsComponent['params'] }
|
||||
| { selector: 'complete'; params: CompleteComponent['params'] }
|
||||
| { selector: 'alert'; params: AlertComponent['params'] }
|
||||
| { selector: 'notes'; params: NotesComponent['params'] }
|
||||
bottomBar: {
|
||||
cancel: {
|
||||
// indicates the existence of a cancel button, and whether to have text or an icon 'x' by default.
|
||||
afterLoading?: { text?: string },
|
||||
afterLoading?: { text?: string }
|
||||
whileLoading?: { text?: string }
|
||||
}
|
||||
// indicates the existence of next or finish buttons (should only have one)
|
||||
@@ -114,11 +137,16 @@ export interface SlideDefinition {
|
||||
}
|
||||
}
|
||||
|
||||
export type TopbarParams = { action: WizardAction, title: string, version: string }
|
||||
export type TopbarParams = {
|
||||
action: WizardAction
|
||||
title: string
|
||||
version: string
|
||||
}
|
||||
|
||||
export async function wizardModal (
|
||||
modalController: ModalController, params: InstallWizardComponent['params'],
|
||||
): Promise<{ cancelled?: true, final?: true, modal: HTMLIonModalElement }> {
|
||||
export async function wizardModal(
|
||||
modalController: ModalController,
|
||||
params: InstallWizardComponent['params'],
|
||||
): Promise<{ cancelled?: true; final?: true; modal: HTMLIonModalElement }> {
|
||||
const modal = await modalController.create({
|
||||
backdropDismiss: false,
|
||||
cssClass: 'wizard-modal',
|
||||
|
||||
@@ -3,18 +3,16 @@ import { CommonModule } from '@angular/common'
|
||||
import { NotesComponent } from './notes.component'
|
||||
import { IonicModule } from '@ionic/angular'
|
||||
import { RouterModule } from '@angular/router'
|
||||
import { SharingModule } from 'src/app/modules/sharing.module'
|
||||
import { MarkdownPipeModule } from '@start9labs/shared'
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
NotesComponent,
|
||||
],
|
||||
declarations: [NotesComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
RouterModule.forChild([]),
|
||||
SharingModule,
|
||||
MarkdownPipeModule,
|
||||
],
|
||||
exports: [NotesComponent],
|
||||
})
|
||||
export class NotesComponentModule { }
|
||||
export class NotesComponentModule {}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
||||
import { Breakages } from 'src/app/services/api/api.types'
|
||||
import { exists } from 'src/app/util/misc.util'
|
||||
import { exists } from '@start9labs/shared'
|
||||
import { ApiService } from '../../services/api/embassy-api.service'
|
||||
import {
|
||||
InstallWizardComponent,
|
||||
|
||||
@@ -2,15 +2,11 @@ import { NgModule } from '@angular/core'
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { IonicModule } from '@ionic/angular'
|
||||
import { LogsPage } from './logs.page'
|
||||
import { SharingModule } from 'src/app/modules/sharing.module'
|
||||
import { TextSpinnerComponentModule } from '@start9labs/shared'
|
||||
|
||||
@NgModule({
|
||||
declarations: [LogsPage],
|
||||
imports: [
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
SharingModule,
|
||||
],
|
||||
imports: [CommonModule, IonicModule, TextSpinnerComponentModule],
|
||||
exports: [LogsPage],
|
||||
})
|
||||
export class LogsPageModule { }
|
||||
export class LogsPageModule {}
|
||||
|
||||
@@ -1,13 +1,21 @@
|
||||
<p
|
||||
[style.color]="disconnected ? 'gray' : 'var(--ion-color-' + rendering.color + ')'"
|
||||
[style.color]="
|
||||
disconnected ? 'gray' : 'var(--ion-color-' + rendering.color + ')'
|
||||
"
|
||||
[style.font-size]="size"
|
||||
[style.font-style]="style"
|
||||
[style.font-weight]="weight"
|
||||
>
|
||||
<span *ngIf= "!installProgress">
|
||||
<span *ngIf="!installProgress">
|
||||
{{ disconnected ? 'Unknown' : rendering.display }}
|
||||
<span *ngIf="rendering.showDots" class="loading-dots"></span>
|
||||
<span *ngIf="rendering.display === PR[PS.Stopping].display && (sigtermTimeout | durationToSeconds) > 30">This may take a while.</span>
|
||||
<span
|
||||
*ngIf="
|
||||
rendering.display === PR[PS.Stopping].display &&
|
||||
(sigtermTimeout | durationToSeconds) > 30
|
||||
"
|
||||
>This may take a while.</span
|
||||
>
|
||||
</span>
|
||||
<span *ngIf="installProgress">
|
||||
<span *ngIf="installProgress < 99">
|
||||
@@ -19,5 +27,4 @@
|
||||
<span class="loading-dots"></span>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
</p>
|
||||
|
||||
@@ -1,18 +1,12 @@
|
||||
import { NgModule } from '@angular/core'
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { StatusComponent } from './status.component'
|
||||
import { IonicModule } from '@ionic/angular'
|
||||
import { SharingModule } from 'src/app/modules/sharing.module'
|
||||
import { UnitConversionPipesModule } from '@start9labs/shared'
|
||||
import { StatusComponent } from './status.component'
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
StatusComponent,
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
SharingModule,
|
||||
],
|
||||
declarations: [StatusComponent],
|
||||
imports: [CommonModule, IonicModule, UnitConversionPipesModule],
|
||||
exports: [StatusComponent],
|
||||
})
|
||||
export class StatusComponentModule { }
|
||||
export class StatusComponentModule {}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { Component, Input } from '@angular/core'
|
||||
import { PrimaryRendering, PrimaryStatus, StatusRendering } from 'src/app/services/pkg-status-rendering.service'
|
||||
import {
|
||||
PrimaryRendering,
|
||||
PrimaryStatus,
|
||||
StatusRendering,
|
||||
} from 'src/app/services/pkg-status-rendering.service'
|
||||
|
||||
@Component({
|
||||
selector: 'status',
|
||||
@@ -18,4 +22,3 @@ export class StatusComponent {
|
||||
@Input() installProgress?: number
|
||||
@Input() sigtermTimeout?: string
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
<ion-grid style="height: 100%;">
|
||||
<ion-row class="ion-align-items-center ion-text-center" style="height: 100%;">
|
||||
<ion-col>
|
||||
<ion-spinner name="lines" color="warning"></ion-spinner>
|
||||
<p>{{ text }}</p>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
</ion-grid>
|
||||
@@ -1,18 +0,0 @@
|
||||
import { NgModule } from '@angular/core'
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { TextSpinnerComponent } from './text-spinner.component'
|
||||
import { IonicModule } from '@ionic/angular'
|
||||
import { RouterModule } from '@angular/router'
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
TextSpinnerComponent,
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
RouterModule.forChild([]),
|
||||
],
|
||||
exports: [TextSpinnerComponent],
|
||||
})
|
||||
export class TextSpinnerComponentModule { }
|
||||
@@ -1,10 +0,0 @@
|
||||
import { Component, Input } from '@angular/core'
|
||||
|
||||
@Component({
|
||||
selector: 'text-spinner',
|
||||
templateUrl: './text-spinner.component.html',
|
||||
styleUrls: ['./text-spinner.component.scss'],
|
||||
})
|
||||
export class TextSpinnerComponent {
|
||||
@Input() text: string
|
||||
}
|
||||
Reference in New Issue
Block a user