Remove app wiz and dry calls (#1541)

* no more app wiz or dry calls

* change spinner type

* better display for update available

* reintroduce dep breakages for update/downgrade and style alerts everywhere

* only show install alert on first install

Co-authored-by: Matt Hill <matthill@Matt-M1.local>
Co-authored-by: Matt Hill <matthill@Matt-M1.start9.dev>
This commit is contained in:
Matt Hill
2022-06-16 13:30:23 -06:00
committed by Lucy C
parent 37304a9d92
commit 0ac5b34f2d
57 changed files with 669 additions and 521 deletions

View File

@@ -91,7 +91,6 @@ export class HomePage {
async restart(): Promise<void> {
const loader = await this.loadingCtrl.create({
spinner: 'lines',
cssClass: 'loader',
})
await loader.present()
@@ -108,7 +107,6 @@ export class HomePage {
async forgetDrive(): Promise<void> {
const loader = await this.loadingCtrl.create({
spinner: 'lines',
cssClass: 'loader',
})
await loader.present()
@@ -126,7 +124,6 @@ export class HomePage {
async repairDrive(): Promise<void> {
const loader = await this.loadingCtrl.create({
spinner: 'lines',
cssClass: 'loader',
})
await loader.present()
@@ -144,10 +141,9 @@ export class HomePage {
async presentAlertRepairDisk() {
const alert = await this.alertCtrl.create({
header: 'RepairDisk',
message: new IonicSafeString(
`<ion-text color="warning">Warning:</ion-text> This action will attempt to preform a disk repair operation and system reboot. No data will be deleted. This action should only be executed if directed by a Start9 support specialist. We recommend backing up your device before preforming this action. If anything happens to the device during the reboot (between the bep and chime), such as loosing power, a power surge, unplugging the drive, or unplugging the Embassy, the filesystem *will* be in an unrecoverable state. Please proceed with caution.`,
),
header: 'Warning',
message:
'This action will attempt to preform a disk repair operation and system reboot. No data will be deleted. This action should only be executed if directed by a Start9 support specialist. We recommend backing up your device before preforming this action. If anything happens to the device during the reboot (between the bep and chime), such as loosing power, a power surge, unplugging the drive, or unplugging the Embassy, the filesystem *will* be in an unrecoverable state. Please proceed with caution.',
buttons: [
{
text: 'Cancel',
@@ -167,6 +163,7 @@ export class HomePage {
cssClass: 'enter-click',
},
],
cssClass: 'alert-warning-message',
})
await alert.present()
}

View File

@@ -3,8 +3,6 @@ import { MarketplacePkg } from '../types/marketplace-pkg'
import { Marketplace } from '../types/marketplace'
export abstract class AbstractMarketplaceService {
abstract install(id: string, version?: string): Observable<unknown>
abstract getMarketplace(): Observable<Marketplace>
abstract getReleaseNotes(id: string): Observable<Record<string, string>>

View File

@@ -1,5 +1,4 @@
import { Url } from '@start9labs/shared'
import { Dependency } from './dependency'
export interface MarketplaceManifest<T = unknown> {
@@ -22,6 +21,7 @@ export interface MarketplaceManifest<T = unknown> {
uninstall: string | null
restore: string | null
start: string | null
stop: string | null
}
dependencies: Record<string, Dependency<T>>
}

View File

@@ -38,7 +38,6 @@ export class CifsModal {
async submit(): Promise<void> {
const loader = await this.loadingCtrl.create({
spinner: 'lines',
message: 'Connecting to shared folder...',
cssClass: 'loader',
})

View File

@@ -100,6 +100,7 @@ export class EmbassyPage {
this.presentModalPassword(drive)
}
},
cssClass: 'enter-click',
},
],
})

View File

@@ -1,12 +1,10 @@
import { Injectable } from '@angular/core'
import { ModalController } from '@ionic/angular'
import { Storage } from '@ionic/storage-angular'
import { Observable, of } from 'rxjs'
import { filter, share, switchMap, take, tap } from 'rxjs/operators'
import { isEmptyObject } from '@start9labs/shared'
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
import { AuthService } from 'src/app/services/auth.service'
import { DataModel, UIData } from 'src/app/services/patch-db/data-model'
import { EOSService } from 'src/app/services/eos.service'
import { OSWelcomePage } from 'src/app/modals/os-welcome/os-welcome.page'
@@ -39,9 +37,7 @@ export class PatchDataService extends Observable<DataModel | null> {
constructor(
private readonly patchMonitor: PatchMonitorService,
private readonly authService: AuthService,
private readonly patch: PatchDbService,
private readonly storage: Storage,
private readonly eosService: EOSService,
private readonly config: ConfigService,
private readonly modalCtrl: ModalController,

View File

@@ -46,7 +46,6 @@ export class UpdateToastService extends Observable<unknown> {
}
LOADER: LoadingOptions = {
spinner: 'lines',
message: 'Restarting...',
}

View File

@@ -26,7 +26,7 @@
{{ page.title }}
</ion-label>
<ion-icon
*ngIf="page.url === '/embassy' && (eosService.updateAvailable$ | async)"
*ngIf="page.url === '/embassy' && (eosService.showUpdate$ | async)"
color="success"
size="small"
name="rocket-outline"

View File

@@ -1,6 +1,5 @@
import { ChangeDetectionStrategy, Component } from '@angular/core'
import { AlertController } from '@ionic/angular'
import { ConfigService } from '../../services/config.service'
import { LocalStorageService } from '../../services/local-storage.service'
import { EOSService } from '../../services/eos.service'
@@ -76,8 +75,8 @@ export class MenuComponent {
},
{
text: 'Logout',
cssClass: 'enter-click',
handler: () => this.logout(),
cssClass: 'enter-click',
},
],
})

View File

@@ -33,11 +33,6 @@
*ngIf="slide.selector === 'alert'"
[params]="slide.params"
></alert>
<notes
#components
*ngIf="slide.selector === 'notes'"
[params]="slide.params"
></notes>
<dependents
#components
*ngIf="slide.selector === 'dependents'"

View File

@@ -6,7 +6,6 @@ import { RouterModule } from '@angular/router'
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'
import { SwiperModule } from 'swiper/angular'
@@ -19,7 +18,6 @@ import { SwiperModule } from 'swiper/angular'
EmverPipesModule,
DependentsComponentModule,
CompleteComponentModule,
NotesComponentModule,
AlertComponentModule,
SwiperModule,
],

View File

@@ -9,7 +9,6 @@ import { IonContent, ModalController } from '@ionic/angular'
import { CompleteComponent } from './complete/complete.component'
import { DependentsComponent } from './dependents/dependents.component'
import { AlertComponent } from './alert/alert.component'
import { NotesComponent } from './notes/notes.component'
import { WizardAction } from './wizard-types'
import SwiperCore, { Swiper } from 'swiper'
import { IonicSlides } from '@ionic/angular'
@@ -87,7 +86,6 @@ export class AppWizardComponent {
export type SlideDefinition =
| { selector: 'alert'; params: AlertComponent['params'] }
| { selector: 'notes'; params: NotesComponent['params'] }
| { selector: 'dependents'; params: DependentsComponent['params'] }
| { selector: 'complete'; params: CompleteComponent['params'] }

View File

@@ -1,14 +0,0 @@
<div class="ion-text-left">
<h1>Release Notes</h1>
<br />
<div *ngFor="let v of params.versions">
<h4>
<b>
{{ v.version }}
</b>
</h4>
<div class="underline" style="margin: unset"></div>
<div [innerHTML]="v.notes | markdown"></div>
<br />
</div>
</div>

View File

@@ -1,18 +0,0 @@
import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common'
import { NotesComponent } from './notes.component'
import { IonicModule } from '@ionic/angular'
import { RouterModule } from '@angular/router'
import { MarkdownPipeModule } from '@start9labs/shared'
@NgModule({
declarations: [NotesComponent],
imports: [
CommonModule,
IonicModule,
RouterModule.forChild([]),
MarkdownPipeModule,
],
exports: [NotesComponent],
})
export class NotesComponentModule {}

View File

@@ -1,22 +0,0 @@
import { Component, Input } from '@angular/core'
import { BaseSlide } from '../wizard-types'
@Component({
selector: 'notes',
templateUrl: './notes.component.html',
styleUrls: ['../app-wizard.component.scss'],
})
export class NotesComponent implements BaseSlide {
@Input() params: {
versions: { version: string; notes: string }[]
headline: string
}
loading = false
async load() {}
asIsOrder() {
return 0
}
}

View File

@@ -34,14 +34,6 @@ export class WizardDefs {
},
}
: undefined,
{
selector: 'dependents',
params: {
verb: 'updating',
title,
Fn: () => this.embassyApi.dryUpdatePackage({ id, version }),
},
},
{
selector: 'complete',
params: {
@@ -67,54 +59,6 @@ export class WizardDefs {
}
}
updateOS(values: {
version: string
releaseNotes: { [version: string]: string }
headline: string
}): AppWizardComponent['params'] {
const { version, releaseNotes, headline } = values
const versions = Object.keys(releaseNotes)
.sort()
.reverse()
.map(version => {
return {
version,
notes: releaseNotes[version],
}
})
const title = 'EmbassyOS'
const slides: SlideDefinition[] = [
{
selector: 'notes',
params: {
versions,
headline,
},
},
{
selector: 'complete',
params: {
verb: 'beginning update for',
title,
Fn: () =>
this.embassyApi.updateServer({
'marketplace-url': this.config.marketplace.url,
}),
},
},
]
return {
action: 'update',
title,
version,
slides: slides.filter(exists),
submitBtn: 'Begin Update',
}
}
downgrade(values: {
id: string
title: string
@@ -132,14 +76,6 @@ export class WizardDefs {
},
}
: undefined,
{
selector: 'dependents',
params: {
verb: 'downgrading',
title,
Fn: () => this.embassyApi.dryUpdatePackage({ id, version }),
},
},
{
selector: 'complete',
params: {
@@ -180,14 +116,6 @@ export class WizardDefs {
message: uninstallAlert || defaultUninstallWarning(title),
},
},
{
selector: 'dependents',
params: {
verb: 'uninstalling',
title,
Fn: () => this.embassyApi.dryUninstallPackage({ id }),
},
},
{
selector: 'complete',
params: {
@@ -210,14 +138,6 @@ export class WizardDefs {
const { title, id } = values
const slides: SlideDefinition[] = [
{
selector: 'dependents',
params: {
verb: 'stopping',
title,
Fn: () => this.embassyApi.dryStopPackage({ id }),
},
},
{
selector: 'complete',
params: {

View File

@@ -142,7 +142,6 @@ export class BackupDrivesComponent {
private async addCifs(value: RR.AddBackupTargetReq): Promise<boolean> {
const loader = await this.loadingCtrl.create({
spinner: 'lines',
message: 'Testing connectivity to shared folder...',
})
await loader.present()
@@ -200,7 +199,6 @@ export class BackupDrivesComponent {
index: number,
): Promise<void> {
const loader = await this.loadingCtrl.create({
spinner: 'lines',
message: 'Testing connectivity to shared folder...',
})
await loader.present()
@@ -217,7 +215,6 @@ export class BackupDrivesComponent {
private async deleteCifs(id: string, index: number): Promise<void> {
const loader = await this.loadingCtrl.create({
spinner: 'lines',
message: 'Removing...',
})
await loader.present()

View File

@@ -110,7 +110,7 @@
fill="solid"
color="primary"
[disabled]="saving"
(click)="save()"
(click)="tryConfigure()"
class="enter-click btn-128"
[class.no-click]="saving"
>

View File

@@ -10,10 +10,10 @@ import { ApiService } from 'src/app/services/api/embassy-api.service'
import {
ErrorToastService,
getErrorMessage,
isEmptyObject,
isObject,
} from '@start9labs/shared'
import { DependentInfo } from 'src/app/types/dependent-info'
import { wizardModal } from 'src/app/components/app-wizard/app-wizard.component'
import { WizardDefs } from 'src/app/components/app-wizard/wizard-defs'
import { ConfigSpec } from 'src/app/pkg-config/config-types'
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
@@ -24,6 +24,8 @@ import {
FormService,
} from 'src/app/services/form.service'
import { compare, Operation, getValueByPointer } from 'fast-json-patch'
import { hasCurrentDeps } from 'src/app/util/has-deps'
import { Breakages } from 'src/app/services/api/api.types'
@Component({
selector: 'app-config',
@@ -35,7 +37,7 @@ export class AppConfigPage {
@Input() pkgId: string
@Input() dependentInfo?: DependentInfo
diff: string[] // only if dependent info
pkg?: PackageDataEntry
pkg: PackageDataEntry
loadingText: string | undefined
configSpec: ConfigSpec
configForm: FormGroup
@@ -45,7 +47,6 @@ export class AppConfigPage {
loadingError: string | IonicSafeString
constructor(
private readonly wizards: WizardDefs,
private readonly embassyApi: ApiService,
private readonly errToast: ErrorToastService,
private readonly loadingCtrl: LoadingController,
@@ -127,7 +128,7 @@ export class AppConfigPage {
}
}
async save() {
async tryConfigure() {
convertValuesRecursive(this.configSpec, this.configForm)
if (this.configForm.invalid) {
@@ -137,48 +138,104 @@ export class AppConfigPage {
return
}
const hasDependents = !!Object.keys(
this.pkg?.installed?.['current-dependents'] || {},
).filter(depId => depId !== this.pkgId).length
this.saving = true
const config = this.configForm.value
if (!hasDependents) {
const loader = await this.loadingCtrl.create({
spinner: 'lines',
message: `Saving config. This could take a while...`,
})
await loader.present()
this.saving = true
try {
await this.embassyApi.setPackageConfig({
id: this.pkgId,
config,
})
this.modalCtrl.dismiss()
} catch (e: any) {
this.errToast.present(e)
} finally {
this.saving = false
loader.dismiss()
}
if (hasCurrentDeps(this.pkg)) {
this.dryConfigure()
} else {
const success = await wizardModal(
this.modalCtrl,
this.wizards.configure({
manifest: this.pkg!.manifest,
config,
}),
)
if (success) {
this.modalCtrl.dismiss()
}
this.configure()
}
}
private async dryConfigure() {
const loader = await this.loadingCtrl.create({
message: 'Checking dependent services...',
})
await loader.present()
try {
const breakages = await this.embassyApi.drySetPackageConfig({
id: this.pkgId,
config: this.configForm.value,
})
if (isEmptyObject(breakages)) {
this.configure(loader)
} else {
await loader.dismiss()
const proceed = await this.presentAlertBreakages(breakages)
if (proceed) {
this.configure()
} else {
this.saving = false
}
}
} catch (e: any) {
this.errToast.present(e)
this.saving = false
}
}
private async configure(loader?: HTMLIonLoadingElement) {
const message = 'Saving...'
if (loader) {
loader.message = message
} else {
loader = await this.loadingCtrl.create({ message })
await loader.present()
}
try {
await this.embassyApi.setPackageConfig({
id: this.pkgId,
config: this.configForm.value,
})
this.modalCtrl.dismiss()
} catch (e: any) {
this.errToast.present(e)
} finally {
this.saving = false
loader.dismiss()
}
}
private async presentAlertBreakages(breakages: Breakages): Promise<boolean> {
let message: string | IonicSafeString =
'As a result of this change, the following services will no longer work properly and may crash:<ul>'
const localPkgs = this.patch.getData()['package-data']
const bullets = Object.keys(breakages).map(id => {
const title = localPkgs[id].manifest.title
return `<li><b>${title}</b></li>`
})
message = new IonicSafeString(`${message}${bullets}</ul>`)
return new Promise(async resolve => {
const alert = await this.alertCtrl.create({
header: 'Warning',
message,
buttons: [
{
text: 'Cancel',
role: 'cancel',
handler: () => {
resolve(false)
},
},
{
text: 'Continue',
handler: () => {
resolve(true)
},
cssClass: 'enter-click',
},
],
cssClass: 'alert-warning-message',
})
await alert.present()
})
}
private getDiff(patch: Operation[]): string[] {
return patch.map(op => {
let message: string

View File

@@ -68,7 +68,6 @@ export class AppRecoverSelectPage {
.map(option => option.id)
const loader = await this.loadingCtrl.create({
spinner: 'lines',
message: 'Initializing...',
})
await loader.present()

View File

@@ -0,0 +1,43 @@
<ion-header>
<ion-toolbar>
<div style="padding: 10px 0">
<ion-title style="font-size: 32px"
>EmbassyOS {{ versions[0].version }}</ion-title
>
<div class="underline"></div>
<ion-title>
<i>Release Notes</i>
</ion-title>
</div>
<ion-buttons slot="end">
<ion-button (click)="dismiss()">
<ion-icon slot="icon-only" name="close"></ion-icon>
</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content>
<div style="padding: 36px" class="ion-text-left">
<ng-container *ngFor="let v of versions">
<h4><b>{{ v.version }}</b></h4>
<div class="underline" style="margin: unset"></div>
<div [innerHTML]="v.notes | markdown"></div>
</ng-container>
</div>
</ion-content>
<ion-footer>
<ion-toolbar>
<ion-buttons slot="end" class="ion-padding-end">
<ion-button
fill="solid"
color="primary"
(click)="updateEOS()"
class="enter-click btn-128"
>
Begin Update
</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-footer>

View File

@@ -0,0 +1,12 @@
import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common'
import { IonicModule } from '@ionic/angular'
import { OSUpdatePage } from './os-update.page'
import { MarkdownPipeModule } from '@start9labs/shared'
@NgModule({
declarations: [OSUpdatePage],
imports: [CommonModule, IonicModule, MarkdownPipeModule],
exports: [OSUpdatePage],
})
export class OSUpdatePageModule {}

View File

@@ -0,0 +1,6 @@
.underline {
margin: 6px 0 8px 16px;
border-style: solid;
border-width: 0px 0px 1px 0px;
border-color: #404040;
}

View File

@@ -0,0 +1,62 @@
import { Component, Input } from '@angular/core'
import { LoadingController, ModalController } from '@ionic/angular'
import { ConfigService } from '../../services/config.service'
import { ApiService } from '../../services/api/embassy-api.service'
import { ErrorToastService } from '../../../../../shared/src/services/error-toast.service'
@Component({
selector: 'os-update',
templateUrl: './os-update.page.html',
styleUrls: ['./os-update.page.scss'],
})
export class OSUpdatePage {
@Input() releaseNotes: { [version: string]: string }
versions: { version: string; notes: string }[] = []
constructor(
private readonly modalCtrl: ModalController,
private readonly loadingCtrl: LoadingController,
private readonly config: ConfigService,
private readonly errToast: ErrorToastService,
private readonly embassyApi: ApiService,
) {}
ngOnInit() {
this.versions = Object.keys(this.releaseNotes)
.sort()
.reverse()
.map(version => {
return {
version,
notes: this.releaseNotes[version],
}
})
}
dismiss() {
this.modalCtrl.dismiss()
}
async updateEOS() {
const loader = await this.loadingCtrl.create({
message: 'Beginning update...',
})
await loader.present()
try {
await this.embassyApi.updateServer({
'marketplace-url': this.config.marketplace.url,
})
this.dismiss()
} catch (e: any) {
this.errToast.present(e)
} finally {
loader.dismiss()
}
}
asIsOrder() {
return 0
}
}

View File

@@ -9,7 +9,6 @@
<ion-content class="ion-padding-top">
<ion-item-group *ngIf="pkg">
<!-- ** standard actions ** -->
<ion-item-divider>Standard Actions</ion-item-divider>
<app-actions-item
@@ -18,11 +17,14 @@
description: 'This will uninstall the service from your Embassy and delete all data permanently.',
icon: 'trash-outline'
}"
(click)="uninstall()">
(click)="tryUninstall()"
>
</app-actions-item>
<!-- ** specific actions ** -->
<ion-item-divider *ngIf="!(pkg.manifest.actions | empty)">Actions for {{ pkg.manifest.title }}</ion-item-divider>
<ion-item-divider *ngIf="!(pkg.manifest.actions | empty)"
>Actions for {{ pkg.manifest.title }}</ion-item-divider
>
<app-actions-item
*ngFor="let action of pkg.manifest.actions | keyvalue: asIsOrder"
[action]="{
@@ -30,7 +32,8 @@
description: action.value.description,
icon: 'play-circle-outline'
}"
(click)="handleAction(action)">
(click)="handleAction(action)"
>
</app-actions-item>
</ion-item-group>
</ion-content>
</ion-content>

View File

@@ -4,6 +4,7 @@ import { ApiService } from 'src/app/services/api/embassy-api.service'
import {
AlertController,
IonContent,
IonicSafeString,
LoadingController,
ModalController,
NavController,
@@ -14,12 +15,11 @@ import {
PackageDataEntry,
PackageMainStatus,
} from 'src/app/services/patch-db/data-model'
import { wizardModal } from 'src/app/components/app-wizard/app-wizard.component'
import { WizardDefs } from 'src/app/components/app-wizard/wizard-defs'
import { Subscription } from 'rxjs'
import { GenericFormPage } from 'src/app/modals/generic-form/generic-form.page'
import { isEmptyObject, ErrorToastService, getPkgId } from '@start9labs/shared'
import { ActionSuccessPage } from 'src/app/modals/action-success/action-success.page'
import { hasCurrentDeps } from 'src/app/util/has-deps'
@Component({
selector: 'app-actions',
@@ -39,7 +39,6 @@ export class AppActionsPage {
private readonly alertCtrl: AlertController,
private readonly errToast: ErrorToastService,
private readonly loadingCtrl: LoadingController,
private readonly wizards: WizardDefs,
private readonly navCtrl: NavController,
private readonly patch: PatchDbService,
) {}
@@ -136,19 +135,52 @@ export class AppActionsPage {
}
}
async uninstall() {
const { id, title, alerts } = this.pkg.manifest
const success = await wizardModal(
this.modalCtrl,
this.wizards.uninstall({
id,
title,
uninstallAlert: alerts.uninstall || undefined,
}),
)
async tryUninstall(): Promise<void> {
const { title, alerts } = this.pkg.manifest
if (success) {
return this.navCtrl.navigateRoot('/services')
let message =
alerts.uninstall ||
`Uninstalling ${title} will permanently delete its data`
if (hasCurrentDeps(this.pkg)) {
message = `${message}. Services that depend on ${title} will no longer work properly and may crash`
}
const alert = await this.alertCtrl.create({
header: 'Warning',
message,
buttons: [
{
text: 'Cancel',
role: 'cancel',
},
{
text: 'Uninstall',
handler: () => {
this.uninstall()
},
cssClass: 'enter-click',
},
],
cssClass: 'alert-warning-message',
})
await alert.present()
}
private async uninstall() {
const loader = await this.loadingCtrl.create({
message: `Beginning uninstall...`,
})
await loader.present()
try {
await this.embassyApi.uninstallPackage({ id: this.pkgId })
this.navCtrl.navigateRoot('/services')
} catch (e: any) {
this.errToast.present(e)
} finally {
loader.dismiss()
}
}
@@ -157,7 +189,6 @@ export class AppActionsPage {
input?: object,
): Promise<boolean> {
const loader = await this.loadingCtrl.create({
spinner: 'lines',
message: 'Executing action...',
})
await loader.present()

View File

@@ -13,50 +13,49 @@
</ion-item>
<ng-container *ngIf="isInstalled">
<ion-item lines="none">
<ion-button
*ngIf="isRunning"
class="action-button"
slot="start"
color="danger"
(click)="stop()"
>
<ion-icon slot="start" name="stop-outline"></ion-icon>
Stop
</ion-button>
<ion-grid>
<ion-row style="padding-left: 12px">
<ion-col>
<ng-container *ngIf="isRunning">
<ion-button class="action-button" color="danger" (click)="tryStop()">
<ion-icon slot="start" name="stop-outline"></ion-icon>
Stop
</ion-button>
</ng-container>
<ion-button
*ngIf="isStopped"
class="action-button"
slot="start"
color="success"
(click)="tryStart()"
>
<ion-icon slot="start" name="play-outline"></ion-icon>
Start
</ion-button>
<ion-button
*ngIf="isStopped"
class="action-button"
color="success"
(click)="tryStart()"
>
<ion-icon slot="start" name="play-outline"></ion-icon>
Start
</ion-button>
<ion-button
*ngIf="!pkgStatus?.configured"
class="action-button"
slot="start"
color="warning"
(click)="presentModalConfig()"
>
<ion-icon slot="start" name="construct-outline"></ion-icon>
Configure
</ion-button>
<ion-button
*ngIf="!pkgStatus?.configured"
class="action-button"
color="warning"
(click)="presentModalConfig()"
>
<ion-icon slot="start" name="construct-outline"></ion-icon>
Configure
</ion-button>
<ion-button
*ngIf="pkgStatus && (interfaces | hasUi)"
class="action-button"
slot="start"
color="primary"
[disabled]="!(pkg.state | isLaunchable: pkgStatus.main.status:interfaces)"
(click)="launchUi()"
>
<ion-icon slot="start" name="open-outline"></ion-icon>
Launch UI
</ion-button>
</ion-item>
</ng-container>
<ion-button
*ngIf="pkgStatus && (interfaces | hasUi)"
class="action-button"
color="primary"
[disabled]="
!(pkg.state | isLaunchable: pkgStatus.main.status:interfaces)
"
(click)="launchUi()"
>
<ion-icon slot="start" name="open-outline"></ion-icon>
Launch UI
</ion-button>
</ion-col>
</ion-row>
</ion-grid>
</ng-container>

View File

@@ -12,16 +12,15 @@ import {
Status,
} from 'src/app/services/patch-db/data-model'
import { ErrorToastService } from '@start9labs/shared'
import { wizardModal } from 'src/app/components/app-wizard/app-wizard.component'
import { WizardDefs } from 'src/app/components/app-wizard/wizard-defs'
import {
AlertController,
IonicSafeString,
LoadingController,
ModalController,
} from '@ionic/angular'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { ModalService } from 'src/app/services/modal.service'
import { DependencyInfo } from '../../pipes/to-dependencies.pipe'
import { hasCurrentDeps } from 'src/app/util/has-deps'
@Component({
selector: 'app-show-status',
@@ -48,9 +47,7 @@ export class AppShowStatusComponent {
private readonly alertCtrl: AlertController,
private readonly errToast: ErrorToastService,
private readonly loadingCtrl: LoadingController,
private readonly modalCtrl: ModalController,
private readonly embassyApi: ApiService,
private readonly wizards: WizardDefs,
private readonly launcherService: UiLauncherService,
private readonly modalService: ModalService,
) {}
@@ -105,41 +102,73 @@ export class AppShowStatusComponent {
this.start()
}
async stop(): Promise<void> {
const { id, title } = this.pkg.manifest
const hasDependents = !!Object.keys(
this.pkg.installed?.['current-dependents'] || {},
).filter(depId => depId !== id).length
async tryStop(): Promise<void> {
const { title, alerts } = this.pkg.manifest
if (!hasDependents) {
const loader = await this.loadingCtrl.create({
message: `Stopping...`,
spinner: 'lines',
let message = alerts.stop || ''
if (hasCurrentDeps(this.pkg)) {
const depMessage = `Services that depend on ${title} will no longer work properly and may crash`
message = message ? `${message}.\n\n${depMessage}` : depMessage
}
if (message) {
const alert = await this.alertCtrl.create({
header: 'Warning',
message,
buttons: [
{
text: 'Cancel',
role: 'cancel',
},
{
text: 'Stop',
handler: () => {
this.stop()
},
cssClass: 'enter-click',
},
],
cssClass: 'alert-warning-message',
})
await loader.present()
try {
await this.embassyApi.stopPackage({ id })
} catch (e: any) {
this.errToast.present(e)
} finally {
loader.dismiss()
}
await alert.present()
} else {
wizardModal(
this.modalCtrl,
this.wizards.stop({
id,
title,
}),
)
this.stop()
}
}
<<<<<<< HEAD
=======
async tryRestart(): Promise<void> {
if (hasCurrentDeps(this.pkg)) {
const alert = await this.alertCtrl.create({
header: 'Warning',
message: `Services that depend on ${this.pkg.manifest.title} may temporarily experiences issues`,
buttons: [
{
text: 'Cancel',
role: 'cancel',
},
{
text: 'Restart',
handler: () => {
this.restart()
},
cssClass: 'enter-click',
},
],
})
await alert.present()
} else {
this.restart()
}
}
>>>>>>> 918a1907... Remove app wiz and dry calls (#1541)
private async start(): Promise<void> {
const loader = await this.loadingCtrl.create({
message: `Starting...`,
spinner: 'lines',
})
await loader.present()
@@ -152,10 +181,43 @@ export class AppShowStatusComponent {
}
}
<<<<<<< HEAD
=======
private async stop(): Promise<void> {
const loader = await this.loadingCtrl.create({
message: 'Stopping...',
})
await loader.present()
try {
await this.embassyApi.stopPackage({ id: this.pkg.manifest.id })
} catch (e: any) {
this.errToast.present(e)
} finally {
loader.dismiss()
}
}
private async restart(): Promise<void> {
const loader = await this.loadingCtrl.create({
message: `Restarting...`,
})
await loader.present()
try {
await this.embassyApi.restartPackage({ id: this.pkg.manifest.id })
} catch (e: any) {
this.errToast.present(e)
} finally {
loader.dismiss()
}
}
>>>>>>> 918a1907... Remove app wiz and dry calls (#1541)
private async presentAlertStart(message: string): Promise<boolean> {
return new Promise(async resolve => {
const alert = await this.alertCtrl.create({
header: 'Warning',
header: 'Alert',
message,
buttons: [
{

View File

@@ -141,7 +141,6 @@ export class DeveloperListPage {
return
const loader = await this.loadingCtrl.create({
spinner: 'lines',
message: 'Creating Project...',
})
await loader.present()
@@ -188,7 +187,6 @@ export class DeveloperListPage {
async editName(id: string, newName: string) {
const loader = await this.loadingCtrl.create({
spinner: 'lines',
message: 'Saving...',
})
await loader.present()
@@ -204,7 +202,6 @@ export class DeveloperListPage {
async delete(id: string) {
const loader = await this.loadingCtrl.create({
spinner: 'lines',
message: 'Removing Project...',
})
await loader.present()

View File

@@ -72,7 +72,6 @@ export class DeveloperMenuPage {
async saveBasicInfo(basicInfo: BasicInfo) {
const loader = await this.loadingCtrl.create({
spinner: 'lines',
message: 'Saving...',
})
await loader.present()

View File

@@ -34,8 +34,7 @@ export class LoginPage {
this.error = ''
this.loader = await this.loadingCtrl.create({
message: 'Logging in',
spinner: 'lines',
message: 'Logging in...',
})
await this.loader.present()

View File

@@ -10,24 +10,24 @@
<ng-container *ngIf="localPkg; else install">
<ng-container *ngIf="localPkg.state === PackageState.Installed">
<ion-button
*ngIf="(version | compareEmver: pkg.manifest.version) === -1"
*ngIf="(localVersion | compareEmver: pkg.manifest.version) === -1"
expand="block"
color="success"
(click)="presentModal('update')"
(click)="tryInstall()"
>
Update
</ion-button>
<ion-button
*ngIf="(version | compareEmver: pkg.manifest.version) === 1"
*ngIf="(localVersion | compareEmver: pkg.manifest.version) === 1"
expand="block"
color="warning"
(click)="presentModal('downgrade')"
(click)="tryInstall()"
>
Downgrade
</ion-button>
<ng-container *ngIf="localStorageService.showDevTools$ | async">
<ion-button
*ngIf="(version | compareEmver: pkg.manifest.version) === 0"
*ngIf="(localVersion | compareEmver: pkg.manifest.version) === 0"
expand="block"
color="success"
(click)="tryInstall()"

View File

@@ -1,19 +1,32 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
import { AlertController, ModalController, NavController } from '@ionic/angular'
import {
ChangeDetectionStrategy,
Component,
Inject,
Input,
} from '@angular/core'
import {
AlertController,
IonicSafeString,
LoadingController,
} from '@ionic/angular'
import {
AbstractMarketplaceService,
MarketplacePkg,
} from '@start9labs/marketplace'
import { pauseFor } from '@start9labs/shared'
import {
Manifest,
PackageDataEntry,
PackageState,
} from 'src/app/services/patch-db/data-model'
import { wizardModal } from 'src/app/components/app-wizard/app-wizard.component'
import { WizardDefs } from 'src/app/components/app-wizard/wizard-defs'
import { LocalStorageService } from 'src/app/services/local-storage.service'
import { MarketplaceService } from 'src/app/services/marketplace.service'
import { hasCurrentDeps } from 'src/app/util/has-deps'
import { Emver } from '../../../../../../../shared/src/services/emver.service'
import { first } from 'rxjs/operators'
import { ErrorToastService } from '../../../../../../../shared/src/services/error-toast.service'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { isEmptyObject } from '../../../../../../../shared/src/util/misc.util'
import { Breakages } from 'src/app/services/api/api.types'
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
@Component({
selector: 'marketplace-show-controls',
@@ -32,60 +45,149 @@ export class MarketplaceShowControlsComponent {
constructor(
private readonly alertCtrl: AlertController,
private readonly modalCtrl: ModalController,
private readonly wizards: WizardDefs,
private readonly navCtrl: NavController,
private readonly marketplaceService: AbstractMarketplaceService,
public readonly localStorageService: LocalStorageService,
@Inject(AbstractMarketplaceService)
private readonly marketplaceService: MarketplaceService,
private readonly loadingCtrl: LoadingController,
private readonly emver: Emver,
private readonly errToast: ErrorToastService,
private readonly embassyApi: ApiService,
private readonly patch: PatchDbService,
) {}
get version(): string {
get localVersion(): string {
return this.localPkg?.manifest.version || ''
}
async tryInstall() {
const { id, title, version, alerts } = this.pkg.manifest
if (!alerts.install) {
this.marketplaceService.install(id, version).subscribe()
if (!this.localPkg) {
this.alertInstall()
} else {
if (
this.emver.compare(this.localVersion, this.pkg.manifest.version) !==
0 &&
hasCurrentDeps(this.localPkg)
) {
this.dryInstall()
} else {
this.install()
}
}
}
private async dryInstall() {
const loader = await this.loadingCtrl.create({
message: 'Checking dependent services...',
})
await loader.present()
const { id, version } = this.pkg.manifest
try {
const breakages = await this.embassyApi.dryUpdatePackage({
id,
version: `=${version}`,
})
if (isEmptyObject(breakages)) {
this.install(loader)
} else {
await loader.dismiss()
const proceed = await this.presentAlertBreakages(breakages)
if (proceed) {
this.install()
}
}
} catch (e: any) {
this.errToast.present(e)
}
}
private async alertInstall() {
const installAlert = this.pkg.manifest.alerts.install
if (!installAlert) return this.install()
const alert = await this.alertCtrl.create({
header: 'Alert',
message: installAlert,
buttons: [
{
text: 'Cancel',
role: 'cancel',
},
{
text: 'Install',
handler: () => {
this.install()
},
cssClass: 'enter-click',
},
],
})
await alert.present()
}
private async install(loader?: HTMLIonLoadingElement) {
const message = 'Beginning Install...'
if (loader) {
loader.message = message
} else {
loader = await this.loadingCtrl.create({ message })
await loader.present()
}
const { id, version } = this.pkg.manifest
try {
await this.marketplaceService
.installPackage({
id,
'version-spec': `=${version}`,
})
.pipe(first())
.toPromise()
} catch (e: any) {
this.errToast.present(e)
} finally {
loader.dismiss()
}
}
private async presentAlertBreakages(breakages: Breakages): Promise<boolean> {
let message: string | IonicSafeString =
'As a result of this update, the following services will no longer work properly and may crash:<ul>'
const localPkgs = this.patch.getData()['package-data']
const bullets = Object.keys(breakages).map(id => {
const title = localPkgs[id].manifest.title
return `<li><b>${title}</b></li>`
})
message = new IonicSafeString(`${message}${bullets}</ul>`)
return new Promise(async resolve => {
const alert = await this.alertCtrl.create({
header: title,
subHeader: version,
message: alerts.install,
header: 'Warning',
message,
buttons: [
{
text: 'Cancel',
role: 'cancel',
handler: () => {
resolve(false)
},
},
{
text: 'Install',
handler: () =>
this.marketplaceService.install(id, version).subscribe(),
text: 'Continue',
handler: () => {
resolve(true)
},
cssClass: 'enter-click',
},
],
cssClass: 'alert-warning-message',
})
await alert.present()
}
}
async presentModal(action: 'update' | 'downgrade') {
// TODO: Fix type
const { id, title, version, dependencies, alerts } = this.pkg
.manifest as Manifest
const value = {
id,
title,
version,
serviceRequirements: dependencies,
installAlert: alerts.install || undefined,
}
wizardModal(
this.modalCtrl,
action === 'update'
? this.wizards.update(value)
: this.wizards.downgrade(value),
)
})
}
}

View File

@@ -72,7 +72,6 @@ export class NotificationsPage {
async delete(id: number, index: number): Promise<void> {
const loader = await this.loadingCtrl.create({
spinner: 'lines',
message: 'Deleting...',
})
await loader.present()
@@ -100,10 +99,10 @@ export class NotificationsPage {
},
{
text: 'Delete',
cssClass: 'enter-click',
handler: () => {
this.deleteAll()
},
cssClass: 'enter-click',
},
],
})
@@ -160,7 +159,6 @@ export class NotificationsPage {
private async deleteAll(): Promise<void> {
const loader = await this.loadingCtrl.create({
spinner: 'lines',
message: 'Deleting...',
})
await loader.present()

View File

@@ -145,7 +145,6 @@ export class MarketplacesPage {
: this.config.marketplace.url
const loader = await this.loadingCtrl.create({
spinner: 'lines',
message: 'Validating Marketplace...',
})
await loader.present()
@@ -189,7 +188,6 @@ export class MarketplacesPage {
)
const loader = await this.loadingCtrl.create({
spinner: 'lines',
message: 'Deleting...',
})
await loader.present()
@@ -219,7 +217,6 @@ export class MarketplacesPage {
if (currentUrls.includes(new URL(url).hostname)) return
const loader = await this.loadingCtrl.create({
spinner: 'lines',
message: 'Validating Marketplace...',
})
@@ -264,7 +261,6 @@ export class MarketplacesPage {
if (currentUrls.includes(new URL(url).hostname)) return
const loader = await this.loadingCtrl.create({
spinner: 'lines',
message: 'Validating Marketplace...',
})
await loader.present()

View File

@@ -66,7 +66,6 @@ export class PreferencesPage {
private async setDbValue(key: string, value: string): Promise<void> {
const loader = await this.loadingCtrl.create({
spinner: 'lines',
message: 'Saving...',
})
await loader.present()

View File

@@ -65,7 +65,6 @@ export class RestorePage {
oldPassword?: string,
): Promise<void> {
const loader = await this.loadingCtrl.create({
spinner: 'lines',
message: 'Decrypting drive...',
})
await loader.present()

View File

@@ -158,7 +158,6 @@ export class ServerBackupPage {
oldPassword?: string,
): Promise<void> {
const loader = await this.loadingCtrl.create({
spinner: 'lines',
message: 'Beginning backup...',
})
await loader.present()

View File

@@ -6,6 +6,7 @@ import { ServerShowPage } from './server-show.page'
import { FormsModule } from '@angular/forms'
import { TextSpinnerComponentModule } from '@start9labs/shared'
import { BadgeMenuComponentModule } from 'src/app/components/badge-menu-button/badge-menu.component.module'
import { OSUpdatePageModule } from 'src/app/modals/os-update/os-update.page.module'
const routes: Routes = [
{
@@ -22,6 +23,7 @@ const routes: Routes = [
RouterModule.forChild(routes),
TextSpinnerComponentModule,
BadgeMenuComponentModule,
OSUpdatePageModule,
],
declarations: [ServerShowPage],
})

View File

@@ -69,18 +69,16 @@
</p>
<!-- "Software Update" button only -->
<p *ngIf="button.title === 'Software Update'">
<ng-container *ngIf="button.disabled | async; else enabled">
<ion-text
*ngIf="server['status-info'].updated"
class="inline"
color="warning"
>
Update Complete, Restart to apply changes
</ion-text>
</ng-container>
<ng-template #enabled>
<ion-text
*ngIf="server['status-info'].updated; else notUpdated"
class="inline"
color="warning"
>
Update Complete. Restart to apply changes
</ion-text>
<ng-template #notUpdated>
<ng-container
*ngIf="eosService.updateAvailable$ | async; else check"
*ngIf="eosService.showUpdate$ | async; else check"
>
<ion-text class="inline" color="success">
<ion-icon name="rocket-outline"></ion-icon>

View File

@@ -11,12 +11,11 @@ import { ActivatedRoute } from '@angular/router'
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
import { Observable, of } from 'rxjs'
import { filter, map, take } from 'rxjs/operators'
import { wizardModal } from 'src/app/components/app-wizard/app-wizard.component'
import { WizardDefs } from 'src/app/components/app-wizard/wizard-defs'
import { exists, isEmptyObject, ErrorToastService } from '@start9labs/shared'
import { EOSService } from 'src/app/services/eos.service'
import { LocalStorageService } from 'src/app/services/local-storage.service'
import { RecoveredPackageDataEntry } from 'src/app/services/patch-db/data-model'
import { OSUpdatePage } from 'src/app/modals/os-update/os-update.page'
@Component({
selector: 'server-show',
@@ -33,7 +32,6 @@ export class ServerShowPage {
constructor(
private readonly alertCtrl: AlertController,
private readonly modalCtrl: ModalController,
private readonly wizards: WizardDefs,
private readonly loadingCtrl: LoadingController,
private readonly errToast: ErrorToastService,
private readonly embassyApi: ApiService,
@@ -63,26 +61,19 @@ export class ServerShowPage {
})
await alert.present()
} else {
const {
version,
headline,
'release-notes': releaseNotes,
} = this.eosService.eos
await wizardModal(
this.modalCtrl,
this.wizards.updateOS({
version,
headline,
releaseNotes,
}),
)
const modal = await this.modalCtrl.create({
componentProps: {
releaseNotes: this.eosService.eos['release-notes'],
},
component: OSUpdatePage,
})
modal.present()
}
}
async presentAlertRestart() {
const alert = await this.alertCtrl.create({
header: 'Confirm',
header: 'Restart',
message:
'Are you sure you want to restart your Embassy? It can take several minutes to come back online.',
buttons: [
@@ -106,7 +97,7 @@ export class ServerShowPage {
const alert = await this.alertCtrl.create({
header: 'Warning',
message:
'Are you sure you want to power down your Embassy? This can take several minutes, and your Embassy will not come back online automatically. To power on again, You will need to physically unplug your Embassy and plug it back in.',
'Are you sure you want to power down your Embassy? This can take several minutes, and your Embassy will not come back online automatically. To power on again, You will need to physically unplug your Embassy and plug it back in',
buttons: [
{
text: 'Cancel',
@@ -120,6 +111,7 @@ export class ServerShowPage {
cssClass: 'enter-click',
},
],
cssClass: 'alert-warning-message',
})
await alert.present()
}
@@ -127,9 +119,9 @@ export class ServerShowPage {
async presentAlertSystemRebuild() {
const minutes = Object.keys(this.patch.getData()['package-data']).length * 2
const alert = await this.alertCtrl.create({
header: 'System Rebuild',
header: 'Warning',
message: new IonicSafeString(
`<ion-text color="warning">Warning:</ion-text> This action will tear down all service containers and rebuild them from scratch. No data will be deleted. This action is useful if your system gets into a bad state, and it should only be performed if you are experiencing general performance or reliability issues. It may take up to ${minutes} minutes to complete. During this time, you will lose all connectivity to your Embassy.`,
`This action will tear down all service containers and rebuild them from scratch. No data will be deleted. This action is useful if your system gets into a bad state, and it should only be performed if you are experiencing general performance or reliability issues. It may take up to ${minutes} minutes to complete. During this time, you will lose all connectivity to your Embassy.`,
),
buttons: [
{
@@ -144,15 +136,16 @@ export class ServerShowPage {
cssClass: 'enter-click',
},
],
cssClass: 'alert-warning-message',
})
await alert.present()
}
async presentAlertRepairDisk() {
const alert = await this.alertCtrl.create({
header: 'Repair Disk',
header: 'Warning',
message: new IonicSafeString(
`<ion-text color="warning">Warning:</ion-text> <p>This action will attempt to preform a disk repair operation and system reboot. No data will be deleted. This action should only be executed if directed by a Start9 support specialist. We recommend backing up your device before preforming this action.</p><p>If anything happens to the device during the reboot (between the bep and chime), such as loosing power, a power surge, unplugging the drive, or unplugging the Embassy, the filesystem *will* be in an unrecoverable state. Please proceed with caution.</p>`,
`<p>This action will attempt to preform a disk repair operation and system reboot. No data will be deleted. This action should only be executed if directed by a Start9 support specialist. We recommend backing up your device before preforming this action.</p><p>If anything happens to the device during the reboot (between the bep and chime), such as loosing power, a power surge, unplugging the drive, or unplugging the Embassy, the filesystem <i>will</i> be in an unrecoverable state. Please proceed with caution.</p>`,
),
buttons: [
{
@@ -173,13 +166,13 @@ export class ServerShowPage {
cssClass: 'enter-click',
},
],
cssClass: 'alert-warning-message',
})
await alert.present()
}
private async restart() {
const loader = await this.loadingCtrl.create({
spinner: 'lines',
message: 'Restarting...',
})
await loader.present()
@@ -195,7 +188,6 @@ export class ServerShowPage {
private async shutdown() {
const loader = await this.loadingCtrl.create({
spinner: 'lines',
message: 'Shutting down...',
})
await loader.present()
@@ -211,7 +203,6 @@ export class ServerShowPage {
private async systemRebuild() {
const loader = await this.loadingCtrl.create({
spinner: 'lines',
message: 'Hard Restarting...',
})
await loader.present()
@@ -227,7 +218,6 @@ export class ServerShowPage {
private async checkForEosUpdate(): Promise<void> {
const loader = await this.loadingCtrl.create({
spinner: 'lines',
message: 'Checking for updates',
})
await loader.present()
@@ -282,14 +272,7 @@ export class ServerShowPage {
action: () =>
this.navCtrl.navigateForward(['restore'], { relativeTo: this.route }),
detail: true,
disabled: this.patch
.watch$('server-info', 'status-info')
.pipe(
map(
status =>
status && (status['backing-up'] || !!status['update-progress']),
),
),
disabled: this.eosService.updatingOrBackingUp$,
},
],
Settings: [
@@ -302,17 +285,7 @@ export class ServerShowPage {
? this.updateEos()
: this.checkForEosUpdate(),
detail: false,
disabled: this.patch
.watch$('server-info', 'status-info')
.pipe(
map(
status =>
status &&
(status['backing-up'] ||
!!status['update-progress'] ||
status.updated),
),
),
disabled: this.eosService.updatingOrBackingUp$,
},
{
title: 'Preferences',

View File

@@ -53,7 +53,6 @@ export class SessionsPage {
async kill(id: string): Promise<void> {
const loader = await this.loadingCtrl.create({
spinner: 'lines',
message: 'Killing session...',
})
await loader.present()

View File

@@ -50,8 +50,7 @@ export class SideloadPage {
}
async setFile(files?: File[]) {
const loader = await this.loadingCtrl.create({
spinner: 'lines',
message: 'Verifying Package',
message: 'Verifying package',
cssClass: 'loader',
})
await loader.present()
@@ -84,8 +83,7 @@ export class SideloadPage {
async handleUpload() {
const loader = await this.loadingCtrl.create({
spinner: 'lines',
message: 'Uploading Package',
message: 'Uploading package',
cssClass: 'loader',
})
await loader.present()

View File

@@ -64,7 +64,6 @@ export class SSHKeysPage {
async add(pubkey: string): Promise<void> {
const loader = await this.loadingCtrl.create({
spinner: 'lines',
message: 'Saving...',
})
await loader.present()
@@ -100,7 +99,6 @@ export class SSHKeysPage {
async delete(i: number): Promise<void> {
const loader = await this.loadingCtrl.create({
spinner: 'lines',
message: 'Deleting...',
})
await loader.present()

View File

@@ -170,7 +170,7 @@ export class WifiPage {
private async setCountry(country: string): Promise<void> {
const loader = await this.loadingCtrl.create({
spinner: 'lines',
message: 'Setting country...',
})
await loader.present()
@@ -261,7 +261,6 @@ export class WifiPage {
private async connect(ssid: string): Promise<void> {
const loader = await this.loadingCtrl.create({
spinner: 'lines',
message: 'Connecting. This could take a while...',
})
await loader.present()
@@ -278,7 +277,6 @@ export class WifiPage {
private async delete(ssid: string): Promise<void> {
const loader = await this.loadingCtrl.create({
spinner: 'lines',
message: 'Deleting...',
})
await loader.present()
@@ -296,7 +294,6 @@ export class WifiPage {
private async save(ssid: string, password: string): Promise<void> {
const loader = await this.loadingCtrl.create({
spinner: 'lines',
message: 'Saving...',
})
await loader.present()
@@ -318,7 +315,6 @@ export class WifiPage {
private async saveAndConnect(ssid: string, password: string): Promise<void> {
const loader = await this.loadingCtrl.create({
spinner: 'lines',
message: 'Connecting. This could take a while...',
})
await loader.present()

View File

@@ -62,6 +62,7 @@ export module Mock {
'Chain state will be lost, as will any funds stored on your Bitcoin Core waller that have not been backed up.',
restore: null,
start: 'Starting Bitcoin is good for your health.',
stop: null,
},
main: {
type: 'docker',
@@ -359,6 +360,7 @@ export module Mock {
restore:
'If this is a duplicate instance of the same LND node, you may loose your funds.',
start: 'Starting LND is good for your health.',
stop: null,
},
main: {
type: 'docker',
@@ -499,10 +501,11 @@ export module Mock {
'marketing-site': '',
'donation-url': 'https://start9.com',
alerts: {
install: null,
install: 'Testing install alert',
uninstall: null,
restore: null,
start: null,
stop: null,
},
main: {
type: 'docker',

View File

@@ -215,15 +215,9 @@ export module RR {
export type StartPackageReq = WithExpire<{ id: string }> // package.start
export type StartPackageRes = WithRevision<null>
export type DryStopPackageReq = StopPackageReq // package.stop.dry
export type DryStopPackageRes = Breakages
export type StopPackageReq = WithExpire<{ id: string }> // package.stop
export type StopPackageRes = WithRevision<null>
export type DryUninstallPackageReq = UninstallPackageReq // package.uninstall.dry
export type DryUninstallPackageRes = Breakages
export type UninstallPackageReq = WithExpire<{ id: string }> // package.uninstall
export type UninstallPackageRes = WithRevision<null>

View File

@@ -233,20 +233,12 @@ export abstract class ApiService implements Source<DataModel>, Http<DataModel> {
startPackage = (params: RR.StartPackageReq) =>
this.syncResponse(() => this.startPackageRaw(params))()
abstract dryStopPackage(
params: RR.DryStopPackageReq,
): Promise<RR.DryStopPackageRes>
protected abstract stopPackageRaw(
params: RR.StopPackageReq,
): Promise<RR.StopPackageRes>
stopPackage = (params: RR.StopPackageReq) =>
this.syncResponse(() => this.stopPackageRaw(params))()
abstract dryUninstallPackage(
params: RR.DryUninstallPackageReq,
): Promise<RR.DryUninstallPackageRes>
protected abstract uninstallPackageRaw(
params: RR.UninstallPackageReq,
): Promise<RR.UninstallPackageRes>
@@ -273,7 +265,7 @@ export abstract class ApiService implements Source<DataModel>, Http<DataModel> {
private syncResponse<
T,
F extends (...args: any[]) => Promise<{ response: T; revision?: Revision }>,
>(f: F, temp?: Operation<unknown>): (...args: Parameters<F>) => Promise<T> {
>(f: F, temp?: Operation<unknown>): (...args: Parameters<F>) => Promise<T> {
return (...a) => {
// let expireId = undefined
// if (temp) {

View File

@@ -12,7 +12,7 @@ export class LiveApiService extends ApiService {
private readonly config: ConfigService,
) {
super()
;(window as any).rpcClient = this
; (window as any).rpcClient = this
}
async getStatic(url: string): Promise<string> {
@@ -306,22 +306,10 @@ export class LiveApiService extends ApiService {
return this.http.rpcRequest({ method: 'package.start', params })
}
async dryStopPackage(
params: RR.DryStopPackageReq,
): Promise<RR.DryStopPackageRes> {
return this.http.rpcRequest({ method: 'package.stop.dry', params })
}
async stopPackageRaw(params: RR.StopPackageReq): Promise<RR.StopPackageRes> {
return this.http.rpcRequest({ method: 'package.stop', params })
}
async dryUninstallPackage(
params: RR.DryUninstallPackageReq,
): Promise<RR.DryUninstallPackageRes> {
return this.http.rpcRequest({ method: 'package.uninstall.dry', params })
}
async deleteRecoveredPackageRaw(
params: RR.DeleteRecoveredPackageReq,
): Promise<RR.DeleteRecoveredPackageRes> {

View File

@@ -501,7 +501,16 @@ export class MockApiService extends ApiService {
params: RR.DryUpdatePackageReq,
): Promise<RR.DryUpdatePackageRes> {
await pauseFor(2000)
return {}
return {
lnd: {
dependency: 'bitcoind',
error: {
type: DependencyErrorType.IncorrectVersion,
expected: '>0.23.0',
received: params.version,
},
},
}
}
async getPackageConfig(
@@ -645,20 +654,6 @@ export class MockApiService extends ApiService {
return this.withRevision(originalPatch)
}
async dryStopPackage(
params: RR.DryStopPackageReq,
): Promise<RR.DryStopPackageRes> {
await pauseFor(2000)
return {
lnd: {
dependency: 'bitcoind',
error: {
type: DependencyErrorType.NotRunning,
},
},
}
}
async stopPackageRaw(params: RR.StopPackageReq): Promise<RR.StopPackageRes> {
await pauseFor(2000)
const path = `/package-data/${params.id}/installed/status/main`
@@ -690,20 +685,6 @@ export class MockApiService extends ApiService {
return this.withRevision(patch)
}
async dryUninstallPackage(
params: RR.DryUninstallPackageReq,
): Promise<RR.DryUninstallPackageRes> {
await pauseFor(2000)
return {
lnd: {
dependency: 'bitcoind',
error: {
type: DependencyErrorType.NotRunning,
},
},
}
}
async uninstallPackageRaw(
params: RR.UninstallPackageReq,
): Promise<RR.UninstallPackageRes> {

View File

@@ -68,6 +68,7 @@ export const mockPatchData: DataModel = {
'Chain state will be lost, as will any funds stored on your Bitcoin Core waller that have not been backed up.',
restore: null,
start: 'Starting Bitcoin is good for your health.',
stop: null,
},
main: {
type: 'docker',
@@ -448,6 +449,7 @@ export const mockPatchData: DataModel = {
restore:
'If this is a duplicate instance of the same LND node, you may loose your funds.',
start: 'Starting LND is good for your health.',
stop: null,
},
main: {
type: 'docker',

View File

@@ -1,10 +1,10 @@
import { Injectable } from '@angular/core'
import { BehaviorSubject } from 'rxjs'
import { BehaviorSubject, combineLatest } from 'rxjs'
import { MarketplaceEOS } from 'src/app/services/api/api.types'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { Emver } from '@start9labs/shared'
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
import { switchMap, take } from 'rxjs/operators'
import { map } from 'rxjs/operators'
@Injectable({
providedIn: 'root',
@@ -13,6 +13,36 @@ export class EOSService {
eos: MarketplaceEOS
updateAvailable$ = new BehaviorSubject<boolean>(false)
readonly updating$ = this.patch.watch$('server-info', 'status-info').pipe(
map(status => {
return status && (!!status['update-progress'] || status.updated)
}),
)
readonly backingUp$ = this.patch.watch$(
'server-info',
'status-info',
'backing-up',
)
readonly updatingOrBackingUp$ = combineLatest([
this.updating$,
this.backingUp$,
]).pipe(
map(([updating, backingUp]) => {
return updating || backingUp
}),
)
readonly showUpdate$ = combineLatest([
this.updateAvailable$,
this.updating$,
]).pipe(
map(([available, updating]) => {
return available && !updating
}),
)
constructor(
private readonly api: ApiService,
private readonly emver: Emver,

View File

@@ -145,28 +145,6 @@ export class MarketplaceService extends AbstractMarketplaceService {
)
}
install(id: string, version?: string): Observable<unknown> {
return defer(() =>
from(
this.loadingCtrl.create({
spinner: 'lines',
message: 'Beginning Installation',
}),
),
).pipe(
tap(loader => loader.present()),
switchMap(loader =>
this.installPackage({
id,
'version-spec': version ? `=${version}` : undefined,
}).pipe(
catchError(e => from(this.errToast.present(e))),
tap(() => loader.dismiss()),
),
),
)
}
installPackage(
req: Omit<RR.InstallPackageReq, 'marketplace-url'>,
): Observable<unknown> {

View File

@@ -32,7 +32,6 @@ export class ServerConfigService {
text: 'Save',
handler: async (data: any) => {
const loader = await this.loadingCtrl.create({
spinner: 'lines',
message: 'Saving...',
})
loader.present()

View File

@@ -0,0 +1,7 @@
import { PackageDataEntry } from '../services/patch-db/data-model'
export function hasCurrentDeps(pkg: PackageDataEntry): boolean {
return !!Object.keys(pkg.installed?.['current-dependents'] || {})
// @TODO fix Manifest type
.filter(depId => depId !== (pkg.manifest as any).id).length
}

View File

@@ -5,7 +5,6 @@ import {
race,
OperatorFunction,
Observer,
combineLatest,
} from 'rxjs'
import { take, map } from 'rxjs/operators'

View File

@@ -205,6 +205,12 @@ ion-button {
}
}
.alert-warning-message {
.alert-title {
color: var(--ion-color-warning);
}
}
.alert-success-message {
.alert-title {
color: var(--ion-color-success);