mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 12:11:56 +00:00
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:
@@ -91,7 +91,6 @@ export class HomePage {
|
|||||||
|
|
||||||
async restart(): Promise<void> {
|
async restart(): Promise<void> {
|
||||||
const loader = await this.loadingCtrl.create({
|
const loader = await this.loadingCtrl.create({
|
||||||
spinner: 'lines',
|
|
||||||
cssClass: 'loader',
|
cssClass: 'loader',
|
||||||
})
|
})
|
||||||
await loader.present()
|
await loader.present()
|
||||||
@@ -108,7 +107,6 @@ export class HomePage {
|
|||||||
|
|
||||||
async forgetDrive(): Promise<void> {
|
async forgetDrive(): Promise<void> {
|
||||||
const loader = await this.loadingCtrl.create({
|
const loader = await this.loadingCtrl.create({
|
||||||
spinner: 'lines',
|
|
||||||
cssClass: 'loader',
|
cssClass: 'loader',
|
||||||
})
|
})
|
||||||
await loader.present()
|
await loader.present()
|
||||||
@@ -126,7 +124,6 @@ export class HomePage {
|
|||||||
|
|
||||||
async repairDrive(): Promise<void> {
|
async repairDrive(): Promise<void> {
|
||||||
const loader = await this.loadingCtrl.create({
|
const loader = await this.loadingCtrl.create({
|
||||||
spinner: 'lines',
|
|
||||||
cssClass: 'loader',
|
cssClass: 'loader',
|
||||||
})
|
})
|
||||||
await loader.present()
|
await loader.present()
|
||||||
@@ -144,10 +141,9 @@ export class HomePage {
|
|||||||
|
|
||||||
async presentAlertRepairDisk() {
|
async presentAlertRepairDisk() {
|
||||||
const alert = await this.alertCtrl.create({
|
const alert = await this.alertCtrl.create({
|
||||||
header: 'RepairDisk',
|
header: 'Warning',
|
||||||
message: new IonicSafeString(
|
message:
|
||||||
`<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.`,
|
'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: [
|
buttons: [
|
||||||
{
|
{
|
||||||
text: 'Cancel',
|
text: 'Cancel',
|
||||||
@@ -167,6 +163,7 @@ export class HomePage {
|
|||||||
cssClass: 'enter-click',
|
cssClass: 'enter-click',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
cssClass: 'alert-warning-message',
|
||||||
})
|
})
|
||||||
await alert.present()
|
await alert.present()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ import { MarketplacePkg } from '../types/marketplace-pkg'
|
|||||||
import { Marketplace } from '../types/marketplace'
|
import { Marketplace } from '../types/marketplace'
|
||||||
|
|
||||||
export abstract class AbstractMarketplaceService {
|
export abstract class AbstractMarketplaceService {
|
||||||
abstract install(id: string, version?: string): Observable<unknown>
|
|
||||||
|
|
||||||
abstract getMarketplace(): Observable<Marketplace>
|
abstract getMarketplace(): Observable<Marketplace>
|
||||||
|
|
||||||
abstract getReleaseNotes(id: string): Observable<Record<string, string>>
|
abstract getReleaseNotes(id: string): Observable<Record<string, string>>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Url } from '@start9labs/shared'
|
import { Url } from '@start9labs/shared'
|
||||||
|
|
||||||
import { Dependency } from './dependency'
|
import { Dependency } from './dependency'
|
||||||
|
|
||||||
export interface MarketplaceManifest<T = unknown> {
|
export interface MarketplaceManifest<T = unknown> {
|
||||||
@@ -22,6 +21,7 @@ export interface MarketplaceManifest<T = unknown> {
|
|||||||
uninstall: string | null
|
uninstall: string | null
|
||||||
restore: string | null
|
restore: string | null
|
||||||
start: string | null
|
start: string | null
|
||||||
|
stop: string | null
|
||||||
}
|
}
|
||||||
dependencies: Record<string, Dependency<T>>
|
dependencies: Record<string, Dependency<T>>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ export class CifsModal {
|
|||||||
|
|
||||||
async submit(): Promise<void> {
|
async submit(): Promise<void> {
|
||||||
const loader = await this.loadingCtrl.create({
|
const loader = await this.loadingCtrl.create({
|
||||||
spinner: 'lines',
|
|
||||||
message: 'Connecting to shared folder...',
|
message: 'Connecting to shared folder...',
|
||||||
cssClass: 'loader',
|
cssClass: 'loader',
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -100,6 +100,7 @@ export class EmbassyPage {
|
|||||||
this.presentModalPassword(drive)
|
this.presentModalPassword(drive)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
cssClass: 'enter-click',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { ModalController } from '@ionic/angular'
|
import { ModalController } from '@ionic/angular'
|
||||||
import { Storage } from '@ionic/storage-angular'
|
|
||||||
import { Observable, of } from 'rxjs'
|
import { Observable, of } from 'rxjs'
|
||||||
import { filter, share, switchMap, take, tap } from 'rxjs/operators'
|
import { filter, share, switchMap, take, tap } from 'rxjs/operators'
|
||||||
import { isEmptyObject } from '@start9labs/shared'
|
import { isEmptyObject } from '@start9labs/shared'
|
||||||
|
|
||||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
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 { DataModel, UIData } from 'src/app/services/patch-db/data-model'
|
||||||
import { EOSService } from 'src/app/services/eos.service'
|
import { EOSService } from 'src/app/services/eos.service'
|
||||||
import { OSWelcomePage } from 'src/app/modals/os-welcome/os-welcome.page'
|
import { OSWelcomePage } from 'src/app/modals/os-welcome/os-welcome.page'
|
||||||
@@ -39,9 +37,7 @@ export class PatchDataService extends Observable<DataModel | null> {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly patchMonitor: PatchMonitorService,
|
private readonly patchMonitor: PatchMonitorService,
|
||||||
private readonly authService: AuthService,
|
|
||||||
private readonly patch: PatchDbService,
|
private readonly patch: PatchDbService,
|
||||||
private readonly storage: Storage,
|
|
||||||
private readonly eosService: EOSService,
|
private readonly eosService: EOSService,
|
||||||
private readonly config: ConfigService,
|
private readonly config: ConfigService,
|
||||||
private readonly modalCtrl: ModalController,
|
private readonly modalCtrl: ModalController,
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ export class UpdateToastService extends Observable<unknown> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
LOADER: LoadingOptions = {
|
LOADER: LoadingOptions = {
|
||||||
spinner: 'lines',
|
|
||||||
message: 'Restarting...',
|
message: 'Restarting...',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
{{ page.title }}
|
{{ page.title }}
|
||||||
</ion-label>
|
</ion-label>
|
||||||
<ion-icon
|
<ion-icon
|
||||||
*ngIf="page.url === '/embassy' && (eosService.updateAvailable$ | async)"
|
*ngIf="page.url === '/embassy' && (eosService.showUpdate$ | async)"
|
||||||
color="success"
|
color="success"
|
||||||
size="small"
|
size="small"
|
||||||
name="rocket-outline"
|
name="rocket-outline"
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { ChangeDetectionStrategy, Component } from '@angular/core'
|
import { ChangeDetectionStrategy, Component } from '@angular/core'
|
||||||
import { AlertController } from '@ionic/angular'
|
import { AlertController } from '@ionic/angular'
|
||||||
|
|
||||||
import { ConfigService } from '../../services/config.service'
|
import { ConfigService } from '../../services/config.service'
|
||||||
import { LocalStorageService } from '../../services/local-storage.service'
|
import { LocalStorageService } from '../../services/local-storage.service'
|
||||||
import { EOSService } from '../../services/eos.service'
|
import { EOSService } from '../../services/eos.service'
|
||||||
@@ -76,8 +75,8 @@ export class MenuComponent {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Logout',
|
text: 'Logout',
|
||||||
cssClass: 'enter-click',
|
|
||||||
handler: () => this.logout(),
|
handler: () => this.logout(),
|
||||||
|
cssClass: 'enter-click',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -33,11 +33,6 @@
|
|||||||
*ngIf="slide.selector === 'alert'"
|
*ngIf="slide.selector === 'alert'"
|
||||||
[params]="slide.params"
|
[params]="slide.params"
|
||||||
></alert>
|
></alert>
|
||||||
<notes
|
|
||||||
#components
|
|
||||||
*ngIf="slide.selector === 'notes'"
|
|
||||||
[params]="slide.params"
|
|
||||||
></notes>
|
|
||||||
<dependents
|
<dependents
|
||||||
#components
|
#components
|
||||||
*ngIf="slide.selector === 'dependents'"
|
*ngIf="slide.selector === 'dependents'"
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { RouterModule } from '@angular/router'
|
|||||||
import { EmverPipesModule } from '@start9labs/shared'
|
import { EmverPipesModule } from '@start9labs/shared'
|
||||||
import { DependentsComponentModule } from './dependents/dependents.component.module'
|
import { DependentsComponentModule } from './dependents/dependents.component.module'
|
||||||
import { CompleteComponentModule } from './complete/complete.component.module'
|
import { CompleteComponentModule } from './complete/complete.component.module'
|
||||||
import { NotesComponentModule } from './notes/notes.component.module'
|
|
||||||
import { AlertComponentModule } from './alert/alert.component.module'
|
import { AlertComponentModule } from './alert/alert.component.module'
|
||||||
import { SwiperModule } from 'swiper/angular'
|
import { SwiperModule } from 'swiper/angular'
|
||||||
|
|
||||||
@@ -19,7 +18,6 @@ import { SwiperModule } from 'swiper/angular'
|
|||||||
EmverPipesModule,
|
EmverPipesModule,
|
||||||
DependentsComponentModule,
|
DependentsComponentModule,
|
||||||
CompleteComponentModule,
|
CompleteComponentModule,
|
||||||
NotesComponentModule,
|
|
||||||
AlertComponentModule,
|
AlertComponentModule,
|
||||||
SwiperModule,
|
SwiperModule,
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import { IonContent, ModalController } from '@ionic/angular'
|
|||||||
import { CompleteComponent } from './complete/complete.component'
|
import { CompleteComponent } from './complete/complete.component'
|
||||||
import { DependentsComponent } from './dependents/dependents.component'
|
import { DependentsComponent } from './dependents/dependents.component'
|
||||||
import { AlertComponent } from './alert/alert.component'
|
import { AlertComponent } from './alert/alert.component'
|
||||||
import { NotesComponent } from './notes/notes.component'
|
|
||||||
import { WizardAction } from './wizard-types'
|
import { WizardAction } from './wizard-types'
|
||||||
import SwiperCore, { Swiper } from 'swiper'
|
import SwiperCore, { Swiper } from 'swiper'
|
||||||
import { IonicSlides } from '@ionic/angular'
|
import { IonicSlides } from '@ionic/angular'
|
||||||
@@ -87,7 +86,6 @@ export class AppWizardComponent {
|
|||||||
|
|
||||||
export type SlideDefinition =
|
export type SlideDefinition =
|
||||||
| { selector: 'alert'; params: AlertComponent['params'] }
|
| { selector: 'alert'; params: AlertComponent['params'] }
|
||||||
| { selector: 'notes'; params: NotesComponent['params'] }
|
|
||||||
| { selector: 'dependents'; params: DependentsComponent['params'] }
|
| { selector: 'dependents'; params: DependentsComponent['params'] }
|
||||||
| { selector: 'complete'; params: CompleteComponent['params'] }
|
| { selector: 'complete'; params: CompleteComponent['params'] }
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
|
||||||
@@ -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 {}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -34,14 +34,6 @@ export class WizardDefs {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
{
|
|
||||||
selector: 'dependents',
|
|
||||||
params: {
|
|
||||||
verb: 'updating',
|
|
||||||
title,
|
|
||||||
Fn: () => this.embassyApi.dryUpdatePackage({ id, version }),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
selector: 'complete',
|
selector: 'complete',
|
||||||
params: {
|
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: {
|
downgrade(values: {
|
||||||
id: string
|
id: string
|
||||||
title: string
|
title: string
|
||||||
@@ -132,14 +76,6 @@ export class WizardDefs {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
{
|
|
||||||
selector: 'dependents',
|
|
||||||
params: {
|
|
||||||
verb: 'downgrading',
|
|
||||||
title,
|
|
||||||
Fn: () => this.embassyApi.dryUpdatePackage({ id, version }),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
selector: 'complete',
|
selector: 'complete',
|
||||||
params: {
|
params: {
|
||||||
@@ -180,14 +116,6 @@ export class WizardDefs {
|
|||||||
message: uninstallAlert || defaultUninstallWarning(title),
|
message: uninstallAlert || defaultUninstallWarning(title),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
selector: 'dependents',
|
|
||||||
params: {
|
|
||||||
verb: 'uninstalling',
|
|
||||||
title,
|
|
||||||
Fn: () => this.embassyApi.dryUninstallPackage({ id }),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
selector: 'complete',
|
selector: 'complete',
|
||||||
params: {
|
params: {
|
||||||
@@ -210,14 +138,6 @@ export class WizardDefs {
|
|||||||
const { title, id } = values
|
const { title, id } = values
|
||||||
|
|
||||||
const slides: SlideDefinition[] = [
|
const slides: SlideDefinition[] = [
|
||||||
{
|
|
||||||
selector: 'dependents',
|
|
||||||
params: {
|
|
||||||
verb: 'stopping',
|
|
||||||
title,
|
|
||||||
Fn: () => this.embassyApi.dryStopPackage({ id }),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
selector: 'complete',
|
selector: 'complete',
|
||||||
params: {
|
params: {
|
||||||
|
|||||||
@@ -142,7 +142,6 @@ export class BackupDrivesComponent {
|
|||||||
|
|
||||||
private async addCifs(value: RR.AddBackupTargetReq): Promise<boolean> {
|
private async addCifs(value: RR.AddBackupTargetReq): Promise<boolean> {
|
||||||
const loader = await this.loadingCtrl.create({
|
const loader = await this.loadingCtrl.create({
|
||||||
spinner: 'lines',
|
|
||||||
message: 'Testing connectivity to shared folder...',
|
message: 'Testing connectivity to shared folder...',
|
||||||
})
|
})
|
||||||
await loader.present()
|
await loader.present()
|
||||||
@@ -200,7 +199,6 @@ export class BackupDrivesComponent {
|
|||||||
index: number,
|
index: number,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const loader = await this.loadingCtrl.create({
|
const loader = await this.loadingCtrl.create({
|
||||||
spinner: 'lines',
|
|
||||||
message: 'Testing connectivity to shared folder...',
|
message: 'Testing connectivity to shared folder...',
|
||||||
})
|
})
|
||||||
await loader.present()
|
await loader.present()
|
||||||
@@ -217,7 +215,6 @@ 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({
|
const loader = await this.loadingCtrl.create({
|
||||||
spinner: 'lines',
|
|
||||||
message: 'Removing...',
|
message: 'Removing...',
|
||||||
})
|
})
|
||||||
await loader.present()
|
await loader.present()
|
||||||
|
|||||||
@@ -110,7 +110,7 @@
|
|||||||
fill="solid"
|
fill="solid"
|
||||||
color="primary"
|
color="primary"
|
||||||
[disabled]="saving"
|
[disabled]="saving"
|
||||||
(click)="save()"
|
(click)="tryConfigure()"
|
||||||
class="enter-click btn-128"
|
class="enter-click btn-128"
|
||||||
[class.no-click]="saving"
|
[class.no-click]="saving"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -10,10 +10,10 @@ import { ApiService } from 'src/app/services/api/embassy-api.service'
|
|||||||
import {
|
import {
|
||||||
ErrorToastService,
|
ErrorToastService,
|
||||||
getErrorMessage,
|
getErrorMessage,
|
||||||
|
isEmptyObject,
|
||||||
isObject,
|
isObject,
|
||||||
} from '@start9labs/shared'
|
} from '@start9labs/shared'
|
||||||
import { DependentInfo } from 'src/app/types/dependent-info'
|
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 { WizardDefs } from 'src/app/components/app-wizard/wizard-defs'
|
||||||
import { ConfigSpec } from 'src/app/pkg-config/config-types'
|
import { ConfigSpec } from 'src/app/pkg-config/config-types'
|
||||||
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
||||||
@@ -24,6 +24,8 @@ import {
|
|||||||
FormService,
|
FormService,
|
||||||
} from 'src/app/services/form.service'
|
} from 'src/app/services/form.service'
|
||||||
import { compare, Operation, getValueByPointer } from 'fast-json-patch'
|
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({
|
@Component({
|
||||||
selector: 'app-config',
|
selector: 'app-config',
|
||||||
@@ -35,7 +37,7 @@ export class AppConfigPage {
|
|||||||
@Input() pkgId: string
|
@Input() pkgId: string
|
||||||
@Input() dependentInfo?: DependentInfo
|
@Input() dependentInfo?: DependentInfo
|
||||||
diff: string[] // only if dependent info
|
diff: string[] // only if dependent info
|
||||||
pkg?: PackageDataEntry
|
pkg: PackageDataEntry
|
||||||
loadingText: string | undefined
|
loadingText: string | undefined
|
||||||
configSpec: ConfigSpec
|
configSpec: ConfigSpec
|
||||||
configForm: FormGroup
|
configForm: FormGroup
|
||||||
@@ -45,7 +47,6 @@ export class AppConfigPage {
|
|||||||
loadingError: string | IonicSafeString
|
loadingError: string | IonicSafeString
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly wizards: WizardDefs,
|
|
||||||
private readonly embassyApi: ApiService,
|
private readonly embassyApi: ApiService,
|
||||||
private readonly errToast: ErrorToastService,
|
private readonly errToast: ErrorToastService,
|
||||||
private readonly loadingCtrl: LoadingController,
|
private readonly loadingCtrl: LoadingController,
|
||||||
@@ -127,7 +128,7 @@ export class AppConfigPage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async save() {
|
async tryConfigure() {
|
||||||
convertValuesRecursive(this.configSpec, this.configForm)
|
convertValuesRecursive(this.configSpec, this.configForm)
|
||||||
|
|
||||||
if (this.configForm.invalid) {
|
if (this.configForm.invalid) {
|
||||||
@@ -137,48 +138,104 @@ export class AppConfigPage {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasDependents = !!Object.keys(
|
this.saving = true
|
||||||
this.pkg?.installed?.['current-dependents'] || {},
|
|
||||||
).filter(depId => depId !== this.pkgId).length
|
|
||||||
|
|
||||||
const config = this.configForm.value
|
if (hasCurrentDeps(this.pkg)) {
|
||||||
|
this.dryConfigure()
|
||||||
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()
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
const success = await wizardModal(
|
this.configure()
|
||||||
this.modalCtrl,
|
|
||||||
this.wizards.configure({
|
|
||||||
manifest: this.pkg!.manifest,
|
|
||||||
config,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
if (success) {
|
|
||||||
this.modalCtrl.dismiss()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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[] {
|
private getDiff(patch: Operation[]): string[] {
|
||||||
return patch.map(op => {
|
return patch.map(op => {
|
||||||
let message: string
|
let message: string
|
||||||
|
|||||||
@@ -68,7 +68,6 @@ export class AppRecoverSelectPage {
|
|||||||
.map(option => option.id)
|
.map(option => option.id)
|
||||||
|
|
||||||
const loader = await this.loadingCtrl.create({
|
const loader = await this.loadingCtrl.create({
|
||||||
spinner: 'lines',
|
|
||||||
message: 'Initializing...',
|
message: 'Initializing...',
|
||||||
})
|
})
|
||||||
await loader.present()
|
await loader.present()
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -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 {}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
.underline {
|
||||||
|
margin: 6px 0 8px 16px;
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 0px 0px 1px 0px;
|
||||||
|
border-color: #404040;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,7 +9,6 @@
|
|||||||
|
|
||||||
<ion-content class="ion-padding-top">
|
<ion-content class="ion-padding-top">
|
||||||
<ion-item-group *ngIf="pkg">
|
<ion-item-group *ngIf="pkg">
|
||||||
|
|
||||||
<!-- ** standard actions ** -->
|
<!-- ** standard actions ** -->
|
||||||
<ion-item-divider>Standard Actions</ion-item-divider>
|
<ion-item-divider>Standard Actions</ion-item-divider>
|
||||||
<app-actions-item
|
<app-actions-item
|
||||||
@@ -18,11 +17,14 @@
|
|||||||
description: 'This will uninstall the service from your Embassy and delete all data permanently.',
|
description: 'This will uninstall the service from your Embassy and delete all data permanently.',
|
||||||
icon: 'trash-outline'
|
icon: 'trash-outline'
|
||||||
}"
|
}"
|
||||||
(click)="uninstall()">
|
(click)="tryUninstall()"
|
||||||
|
>
|
||||||
</app-actions-item>
|
</app-actions-item>
|
||||||
|
|
||||||
<!-- ** specific actions ** -->
|
<!-- ** 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
|
<app-actions-item
|
||||||
*ngFor="let action of pkg.manifest.actions | keyvalue: asIsOrder"
|
*ngFor="let action of pkg.manifest.actions | keyvalue: asIsOrder"
|
||||||
[action]="{
|
[action]="{
|
||||||
@@ -30,7 +32,8 @@
|
|||||||
description: action.value.description,
|
description: action.value.description,
|
||||||
icon: 'play-circle-outline'
|
icon: 'play-circle-outline'
|
||||||
}"
|
}"
|
||||||
(click)="handleAction(action)">
|
(click)="handleAction(action)"
|
||||||
|
>
|
||||||
</app-actions-item>
|
</app-actions-item>
|
||||||
</ion-item-group>
|
</ion-item-group>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { ApiService } from 'src/app/services/api/embassy-api.service'
|
|||||||
import {
|
import {
|
||||||
AlertController,
|
AlertController,
|
||||||
IonContent,
|
IonContent,
|
||||||
|
IonicSafeString,
|
||||||
LoadingController,
|
LoadingController,
|
||||||
ModalController,
|
ModalController,
|
||||||
NavController,
|
NavController,
|
||||||
@@ -14,12 +15,11 @@ import {
|
|||||||
PackageDataEntry,
|
PackageDataEntry,
|
||||||
PackageMainStatus,
|
PackageMainStatus,
|
||||||
} from 'src/app/services/patch-db/data-model'
|
} 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 { Subscription } from 'rxjs'
|
||||||
import { GenericFormPage } from 'src/app/modals/generic-form/generic-form.page'
|
import { GenericFormPage } from 'src/app/modals/generic-form/generic-form.page'
|
||||||
import { isEmptyObject, ErrorToastService, getPkgId } from '@start9labs/shared'
|
import { isEmptyObject, ErrorToastService, getPkgId } from '@start9labs/shared'
|
||||||
import { ActionSuccessPage } from 'src/app/modals/action-success/action-success.page'
|
import { ActionSuccessPage } from 'src/app/modals/action-success/action-success.page'
|
||||||
|
import { hasCurrentDeps } from 'src/app/util/has-deps'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-actions',
|
selector: 'app-actions',
|
||||||
@@ -39,7 +39,6 @@ export class AppActionsPage {
|
|||||||
private readonly alertCtrl: AlertController,
|
private readonly alertCtrl: AlertController,
|
||||||
private readonly errToast: ErrorToastService,
|
private readonly errToast: ErrorToastService,
|
||||||
private readonly loadingCtrl: LoadingController,
|
private readonly loadingCtrl: LoadingController,
|
||||||
private readonly wizards: WizardDefs,
|
|
||||||
private readonly navCtrl: NavController,
|
private readonly navCtrl: NavController,
|
||||||
private readonly patch: PatchDbService,
|
private readonly patch: PatchDbService,
|
||||||
) {}
|
) {}
|
||||||
@@ -136,19 +135,52 @@ export class AppActionsPage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async uninstall() {
|
async tryUninstall(): Promise<void> {
|
||||||
const { id, title, alerts } = this.pkg.manifest
|
const { title, alerts } = this.pkg.manifest
|
||||||
const success = await wizardModal(
|
|
||||||
this.modalCtrl,
|
|
||||||
this.wizards.uninstall({
|
|
||||||
id,
|
|
||||||
title,
|
|
||||||
uninstallAlert: alerts.uninstall || undefined,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
if (success) {
|
let message =
|
||||||
return this.navCtrl.navigateRoot('/services')
|
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,
|
input?: object,
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const loader = await this.loadingCtrl.create({
|
const loader = await this.loadingCtrl.create({
|
||||||
spinner: 'lines',
|
|
||||||
message: 'Executing action...',
|
message: 'Executing action...',
|
||||||
})
|
})
|
||||||
await loader.present()
|
await loader.present()
|
||||||
|
|||||||
@@ -13,50 +13,49 @@
|
|||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<ng-container *ngIf="isInstalled">
|
<ng-container *ngIf="isInstalled">
|
||||||
<ion-item lines="none">
|
<ion-grid>
|
||||||
<ion-button
|
<ion-row style="padding-left: 12px">
|
||||||
*ngIf="isRunning"
|
<ion-col>
|
||||||
class="action-button"
|
<ng-container *ngIf="isRunning">
|
||||||
slot="start"
|
<ion-button class="action-button" color="danger" (click)="tryStop()">
|
||||||
color="danger"
|
<ion-icon slot="start" name="stop-outline"></ion-icon>
|
||||||
(click)="stop()"
|
Stop
|
||||||
>
|
</ion-button>
|
||||||
<ion-icon slot="start" name="stop-outline"></ion-icon>
|
</ng-container>
|
||||||
Stop
|
|
||||||
</ion-button>
|
|
||||||
|
|
||||||
<ion-button
|
<ion-button
|
||||||
*ngIf="isStopped"
|
*ngIf="isStopped"
|
||||||
class="action-button"
|
class="action-button"
|
||||||
slot="start"
|
color="success"
|
||||||
color="success"
|
(click)="tryStart()"
|
||||||
(click)="tryStart()"
|
>
|
||||||
>
|
<ion-icon slot="start" name="play-outline"></ion-icon>
|
||||||
<ion-icon slot="start" name="play-outline"></ion-icon>
|
Start
|
||||||
Start
|
</ion-button>
|
||||||
</ion-button>
|
|
||||||
|
|
||||||
<ion-button
|
<ion-button
|
||||||
*ngIf="!pkgStatus?.configured"
|
*ngIf="!pkgStatus?.configured"
|
||||||
class="action-button"
|
class="action-button"
|
||||||
slot="start"
|
color="warning"
|
||||||
color="warning"
|
(click)="presentModalConfig()"
|
||||||
(click)="presentModalConfig()"
|
>
|
||||||
>
|
<ion-icon slot="start" name="construct-outline"></ion-icon>
|
||||||
<ion-icon slot="start" name="construct-outline"></ion-icon>
|
Configure
|
||||||
Configure
|
</ion-button>
|
||||||
</ion-button>
|
|
||||||
|
|
||||||
<ion-button
|
<ion-button
|
||||||
*ngIf="pkgStatus && (interfaces | hasUi)"
|
*ngIf="pkgStatus && (interfaces | hasUi)"
|
||||||
class="action-button"
|
class="action-button"
|
||||||
slot="start"
|
color="primary"
|
||||||
color="primary"
|
[disabled]="
|
||||||
[disabled]="!(pkg.state | isLaunchable: pkgStatus.main.status:interfaces)"
|
!(pkg.state | isLaunchable: pkgStatus.main.status:interfaces)
|
||||||
(click)="launchUi()"
|
"
|
||||||
>
|
(click)="launchUi()"
|
||||||
<ion-icon slot="start" name="open-outline"></ion-icon>
|
>
|
||||||
Launch UI
|
<ion-icon slot="start" name="open-outline"></ion-icon>
|
||||||
</ion-button>
|
Launch UI
|
||||||
</ion-item>
|
</ion-button>
|
||||||
</ng-container>
|
</ion-col>
|
||||||
|
</ion-row>
|
||||||
|
</ion-grid>
|
||||||
|
</ng-container>
|
||||||
@@ -12,16 +12,15 @@ import {
|
|||||||
Status,
|
Status,
|
||||||
} from 'src/app/services/patch-db/data-model'
|
} from 'src/app/services/patch-db/data-model'
|
||||||
import { ErrorToastService } from '@start9labs/shared'
|
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 {
|
import {
|
||||||
AlertController,
|
AlertController,
|
||||||
|
IonicSafeString,
|
||||||
LoadingController,
|
LoadingController,
|
||||||
ModalController,
|
|
||||||
} from '@ionic/angular'
|
} from '@ionic/angular'
|
||||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||||
import { ModalService } from 'src/app/services/modal.service'
|
import { ModalService } from 'src/app/services/modal.service'
|
||||||
import { DependencyInfo } from '../../pipes/to-dependencies.pipe'
|
import { DependencyInfo } from '../../pipes/to-dependencies.pipe'
|
||||||
|
import { hasCurrentDeps } from 'src/app/util/has-deps'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-show-status',
|
selector: 'app-show-status',
|
||||||
@@ -48,9 +47,7 @@ export class AppShowStatusComponent {
|
|||||||
private readonly alertCtrl: AlertController,
|
private readonly alertCtrl: AlertController,
|
||||||
private readonly errToast: ErrorToastService,
|
private readonly errToast: ErrorToastService,
|
||||||
private readonly loadingCtrl: LoadingController,
|
private readonly loadingCtrl: LoadingController,
|
||||||
private readonly modalCtrl: ModalController,
|
|
||||||
private readonly embassyApi: ApiService,
|
private readonly embassyApi: ApiService,
|
||||||
private readonly wizards: WizardDefs,
|
|
||||||
private readonly launcherService: UiLauncherService,
|
private readonly launcherService: UiLauncherService,
|
||||||
private readonly modalService: ModalService,
|
private readonly modalService: ModalService,
|
||||||
) {}
|
) {}
|
||||||
@@ -105,41 +102,73 @@ export class AppShowStatusComponent {
|
|||||||
this.start()
|
this.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
async stop(): Promise<void> {
|
async tryStop(): Promise<void> {
|
||||||
const { id, title } = this.pkg.manifest
|
const { title, alerts } = this.pkg.manifest
|
||||||
const hasDependents = !!Object.keys(
|
|
||||||
this.pkg.installed?.['current-dependents'] || {},
|
|
||||||
).filter(depId => depId !== id).length
|
|
||||||
|
|
||||||
if (!hasDependents) {
|
let message = alerts.stop || ''
|
||||||
const loader = await this.loadingCtrl.create({
|
if (hasCurrentDeps(this.pkg)) {
|
||||||
message: `Stopping...`,
|
const depMessage = `Services that depend on ${title} will no longer work properly and may crash`
|
||||||
spinner: 'lines',
|
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 alert.present()
|
||||||
await this.embassyApi.stopPackage({ id })
|
|
||||||
} catch (e: any) {
|
|
||||||
this.errToast.present(e)
|
|
||||||
} finally {
|
|
||||||
loader.dismiss()
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
wizardModal(
|
this.stop()
|
||||||
this.modalCtrl,
|
|
||||||
this.wizards.stop({
|
|
||||||
id,
|
|
||||||
title,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<<<<<<< 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> {
|
private async start(): Promise<void> {
|
||||||
const loader = await this.loadingCtrl.create({
|
const loader = await this.loadingCtrl.create({
|
||||||
message: `Starting...`,
|
message: `Starting...`,
|
||||||
spinner: 'lines',
|
|
||||||
})
|
})
|
||||||
await loader.present()
|
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> {
|
private async presentAlertStart(message: string): Promise<boolean> {
|
||||||
return new Promise(async resolve => {
|
return new Promise(async resolve => {
|
||||||
const alert = await this.alertCtrl.create({
|
const alert = await this.alertCtrl.create({
|
||||||
header: 'Warning',
|
header: 'Alert',
|
||||||
message,
|
message,
|
||||||
buttons: [
|
buttons: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -141,7 +141,6 @@ export class DeveloperListPage {
|
|||||||
return
|
return
|
||||||
|
|
||||||
const loader = await this.loadingCtrl.create({
|
const loader = await this.loadingCtrl.create({
|
||||||
spinner: 'lines',
|
|
||||||
message: 'Creating Project...',
|
message: 'Creating Project...',
|
||||||
})
|
})
|
||||||
await loader.present()
|
await loader.present()
|
||||||
@@ -188,7 +187,6 @@ export class DeveloperListPage {
|
|||||||
|
|
||||||
async editName(id: string, newName: string) {
|
async editName(id: string, newName: string) {
|
||||||
const loader = await this.loadingCtrl.create({
|
const loader = await this.loadingCtrl.create({
|
||||||
spinner: 'lines',
|
|
||||||
message: 'Saving...',
|
message: 'Saving...',
|
||||||
})
|
})
|
||||||
await loader.present()
|
await loader.present()
|
||||||
@@ -204,7 +202,6 @@ export class DeveloperListPage {
|
|||||||
|
|
||||||
async delete(id: string) {
|
async delete(id: string) {
|
||||||
const loader = await this.loadingCtrl.create({
|
const loader = await this.loadingCtrl.create({
|
||||||
spinner: 'lines',
|
|
||||||
message: 'Removing Project...',
|
message: 'Removing Project...',
|
||||||
})
|
})
|
||||||
await loader.present()
|
await loader.present()
|
||||||
|
|||||||
@@ -72,7 +72,6 @@ export class DeveloperMenuPage {
|
|||||||
|
|
||||||
async saveBasicInfo(basicInfo: BasicInfo) {
|
async saveBasicInfo(basicInfo: BasicInfo) {
|
||||||
const loader = await this.loadingCtrl.create({
|
const loader = await this.loadingCtrl.create({
|
||||||
spinner: 'lines',
|
|
||||||
message: 'Saving...',
|
message: 'Saving...',
|
||||||
})
|
})
|
||||||
await loader.present()
|
await loader.present()
|
||||||
|
|||||||
@@ -34,8 +34,7 @@ export class LoginPage {
|
|||||||
this.error = ''
|
this.error = ''
|
||||||
|
|
||||||
this.loader = await this.loadingCtrl.create({
|
this.loader = await this.loadingCtrl.create({
|
||||||
message: 'Logging in',
|
message: 'Logging in...',
|
||||||
spinner: 'lines',
|
|
||||||
})
|
})
|
||||||
await this.loader.present()
|
await this.loader.present()
|
||||||
|
|
||||||
|
|||||||
@@ -10,24 +10,24 @@
|
|||||||
<ng-container *ngIf="localPkg; else install">
|
<ng-container *ngIf="localPkg; else install">
|
||||||
<ng-container *ngIf="localPkg.state === PackageState.Installed">
|
<ng-container *ngIf="localPkg.state === PackageState.Installed">
|
||||||
<ion-button
|
<ion-button
|
||||||
*ngIf="(version | compareEmver: pkg.manifest.version) === -1"
|
*ngIf="(localVersion | compareEmver: pkg.manifest.version) === -1"
|
||||||
expand="block"
|
expand="block"
|
||||||
color="success"
|
color="success"
|
||||||
(click)="presentModal('update')"
|
(click)="tryInstall()"
|
||||||
>
|
>
|
||||||
Update
|
Update
|
||||||
</ion-button>
|
</ion-button>
|
||||||
<ion-button
|
<ion-button
|
||||||
*ngIf="(version | compareEmver: pkg.manifest.version) === 1"
|
*ngIf="(localVersion | compareEmver: pkg.manifest.version) === 1"
|
||||||
expand="block"
|
expand="block"
|
||||||
color="warning"
|
color="warning"
|
||||||
(click)="presentModal('downgrade')"
|
(click)="tryInstall()"
|
||||||
>
|
>
|
||||||
Downgrade
|
Downgrade
|
||||||
</ion-button>
|
</ion-button>
|
||||||
<ng-container *ngIf="localStorageService.showDevTools$ | async">
|
<ng-container *ngIf="localStorageService.showDevTools$ | async">
|
||||||
<ion-button
|
<ion-button
|
||||||
*ngIf="(version | compareEmver: pkg.manifest.version) === 0"
|
*ngIf="(localVersion | compareEmver: pkg.manifest.version) === 0"
|
||||||
expand="block"
|
expand="block"
|
||||||
color="success"
|
color="success"
|
||||||
(click)="tryInstall()"
|
(click)="tryInstall()"
|
||||||
|
|||||||
@@ -1,19 +1,32 @@
|
|||||||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
|
import {
|
||||||
import { AlertController, ModalController, NavController } from '@ionic/angular'
|
ChangeDetectionStrategy,
|
||||||
|
Component,
|
||||||
|
Inject,
|
||||||
|
Input,
|
||||||
|
} from '@angular/core'
|
||||||
|
import {
|
||||||
|
AlertController,
|
||||||
|
IonicSafeString,
|
||||||
|
LoadingController,
|
||||||
|
} from '@ionic/angular'
|
||||||
import {
|
import {
|
||||||
AbstractMarketplaceService,
|
AbstractMarketplaceService,
|
||||||
MarketplacePkg,
|
MarketplacePkg,
|
||||||
} from '@start9labs/marketplace'
|
} from '@start9labs/marketplace'
|
||||||
import { pauseFor } from '@start9labs/shared'
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Manifest,
|
|
||||||
PackageDataEntry,
|
PackageDataEntry,
|
||||||
PackageState,
|
PackageState,
|
||||||
} from 'src/app/services/patch-db/data-model'
|
} 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 { 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({
|
@Component({
|
||||||
selector: 'marketplace-show-controls',
|
selector: 'marketplace-show-controls',
|
||||||
@@ -32,60 +45,149 @@ export class MarketplaceShowControlsComponent {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly alertCtrl: AlertController,
|
private readonly alertCtrl: AlertController,
|
||||||
private readonly modalCtrl: ModalController,
|
|
||||||
private readonly wizards: WizardDefs,
|
|
||||||
private readonly navCtrl: NavController,
|
|
||||||
private readonly marketplaceService: AbstractMarketplaceService,
|
|
||||||
public readonly localStorageService: LocalStorageService,
|
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 || ''
|
return this.localPkg?.manifest.version || ''
|
||||||
}
|
}
|
||||||
|
|
||||||
async tryInstall() {
|
async tryInstall() {
|
||||||
const { id, title, version, alerts } = this.pkg.manifest
|
if (!this.localPkg) {
|
||||||
|
this.alertInstall()
|
||||||
if (!alerts.install) {
|
|
||||||
this.marketplaceService.install(id, version).subscribe()
|
|
||||||
} else {
|
} 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({
|
const alert = await this.alertCtrl.create({
|
||||||
header: title,
|
header: 'Warning',
|
||||||
subHeader: version,
|
message,
|
||||||
message: alerts.install,
|
|
||||||
buttons: [
|
buttons: [
|
||||||
{
|
{
|
||||||
text: 'Cancel',
|
text: 'Cancel',
|
||||||
role: 'cancel',
|
role: 'cancel',
|
||||||
|
handler: () => {
|
||||||
|
resolve(false)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Install',
|
text: 'Continue',
|
||||||
handler: () =>
|
handler: () => {
|
||||||
this.marketplaceService.install(id, version).subscribe(),
|
resolve(true)
|
||||||
|
},
|
||||||
|
cssClass: 'enter-click',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
cssClass: 'alert-warning-message',
|
||||||
})
|
})
|
||||||
|
|
||||||
await alert.present()
|
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),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,7 +72,6 @@ export class NotificationsPage {
|
|||||||
|
|
||||||
async delete(id: number, index: number): Promise<void> {
|
async delete(id: number, index: number): Promise<void> {
|
||||||
const loader = await this.loadingCtrl.create({
|
const loader = await this.loadingCtrl.create({
|
||||||
spinner: 'lines',
|
|
||||||
message: 'Deleting...',
|
message: 'Deleting...',
|
||||||
})
|
})
|
||||||
await loader.present()
|
await loader.present()
|
||||||
@@ -100,10 +99,10 @@ export class NotificationsPage {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Delete',
|
text: 'Delete',
|
||||||
cssClass: 'enter-click',
|
|
||||||
handler: () => {
|
handler: () => {
|
||||||
this.deleteAll()
|
this.deleteAll()
|
||||||
},
|
},
|
||||||
|
cssClass: 'enter-click',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
@@ -160,7 +159,6 @@ export class NotificationsPage {
|
|||||||
|
|
||||||
private async deleteAll(): Promise<void> {
|
private async deleteAll(): Promise<void> {
|
||||||
const loader = await this.loadingCtrl.create({
|
const loader = await this.loadingCtrl.create({
|
||||||
spinner: 'lines',
|
|
||||||
message: 'Deleting...',
|
message: 'Deleting...',
|
||||||
})
|
})
|
||||||
await loader.present()
|
await loader.present()
|
||||||
|
|||||||
@@ -145,7 +145,6 @@ export class MarketplacesPage {
|
|||||||
: this.config.marketplace.url
|
: this.config.marketplace.url
|
||||||
|
|
||||||
const loader = await this.loadingCtrl.create({
|
const loader = await this.loadingCtrl.create({
|
||||||
spinner: 'lines',
|
|
||||||
message: 'Validating Marketplace...',
|
message: 'Validating Marketplace...',
|
||||||
})
|
})
|
||||||
await loader.present()
|
await loader.present()
|
||||||
@@ -189,7 +188,6 @@ export class MarketplacesPage {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const loader = await this.loadingCtrl.create({
|
const loader = await this.loadingCtrl.create({
|
||||||
spinner: 'lines',
|
|
||||||
message: 'Deleting...',
|
message: 'Deleting...',
|
||||||
})
|
})
|
||||||
await loader.present()
|
await loader.present()
|
||||||
@@ -219,7 +217,6 @@ export class MarketplacesPage {
|
|||||||
if (currentUrls.includes(new URL(url).hostname)) return
|
if (currentUrls.includes(new URL(url).hostname)) return
|
||||||
|
|
||||||
const loader = await this.loadingCtrl.create({
|
const loader = await this.loadingCtrl.create({
|
||||||
spinner: 'lines',
|
|
||||||
message: 'Validating Marketplace...',
|
message: 'Validating Marketplace...',
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -264,7 +261,6 @@ export class MarketplacesPage {
|
|||||||
if (currentUrls.includes(new URL(url).hostname)) return
|
if (currentUrls.includes(new URL(url).hostname)) return
|
||||||
|
|
||||||
const loader = await this.loadingCtrl.create({
|
const loader = await this.loadingCtrl.create({
|
||||||
spinner: 'lines',
|
|
||||||
message: 'Validating Marketplace...',
|
message: 'Validating Marketplace...',
|
||||||
})
|
})
|
||||||
await loader.present()
|
await loader.present()
|
||||||
|
|||||||
@@ -66,7 +66,6 @@ export class PreferencesPage {
|
|||||||
|
|
||||||
private async setDbValue(key: string, value: string): Promise<void> {
|
private async setDbValue(key: string, value: string): Promise<void> {
|
||||||
const loader = await this.loadingCtrl.create({
|
const loader = await this.loadingCtrl.create({
|
||||||
spinner: 'lines',
|
|
||||||
message: 'Saving...',
|
message: 'Saving...',
|
||||||
})
|
})
|
||||||
await loader.present()
|
await loader.present()
|
||||||
|
|||||||
@@ -65,7 +65,6 @@ export class RestorePage {
|
|||||||
oldPassword?: string,
|
oldPassword?: string,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const loader = await this.loadingCtrl.create({
|
const loader = await this.loadingCtrl.create({
|
||||||
spinner: 'lines',
|
|
||||||
message: 'Decrypting drive...',
|
message: 'Decrypting drive...',
|
||||||
})
|
})
|
||||||
await loader.present()
|
await loader.present()
|
||||||
|
|||||||
@@ -158,7 +158,6 @@ export class ServerBackupPage {
|
|||||||
oldPassword?: string,
|
oldPassword?: string,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const loader = await this.loadingCtrl.create({
|
const loader = await this.loadingCtrl.create({
|
||||||
spinner: 'lines',
|
|
||||||
message: 'Beginning backup...',
|
message: 'Beginning backup...',
|
||||||
})
|
})
|
||||||
await loader.present()
|
await loader.present()
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { ServerShowPage } from './server-show.page'
|
|||||||
import { FormsModule } from '@angular/forms'
|
import { FormsModule } from '@angular/forms'
|
||||||
import { TextSpinnerComponentModule } from '@start9labs/shared'
|
import { TextSpinnerComponentModule } from '@start9labs/shared'
|
||||||
import { BadgeMenuComponentModule } from 'src/app/components/badge-menu-button/badge-menu.component.module'
|
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 = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
@@ -22,6 +23,7 @@ const routes: Routes = [
|
|||||||
RouterModule.forChild(routes),
|
RouterModule.forChild(routes),
|
||||||
TextSpinnerComponentModule,
|
TextSpinnerComponentModule,
|
||||||
BadgeMenuComponentModule,
|
BadgeMenuComponentModule,
|
||||||
|
OSUpdatePageModule,
|
||||||
],
|
],
|
||||||
declarations: [ServerShowPage],
|
declarations: [ServerShowPage],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -69,18 +69,16 @@
|
|||||||
</p>
|
</p>
|
||||||
<!-- "Software Update" button only -->
|
<!-- "Software Update" button only -->
|
||||||
<p *ngIf="button.title === 'Software Update'">
|
<p *ngIf="button.title === 'Software Update'">
|
||||||
<ng-container *ngIf="button.disabled | async; else enabled">
|
<ion-text
|
||||||
<ion-text
|
*ngIf="server['status-info'].updated; else notUpdated"
|
||||||
*ngIf="server['status-info'].updated"
|
class="inline"
|
||||||
class="inline"
|
color="warning"
|
||||||
color="warning"
|
>
|
||||||
>
|
Update Complete. Restart to apply changes
|
||||||
Update Complete, Restart to apply changes
|
</ion-text>
|
||||||
</ion-text>
|
<ng-template #notUpdated>
|
||||||
</ng-container>
|
|
||||||
<ng-template #enabled>
|
|
||||||
<ng-container
|
<ng-container
|
||||||
*ngIf="eosService.updateAvailable$ | async; else check"
|
*ngIf="eosService.showUpdate$ | async; else check"
|
||||||
>
|
>
|
||||||
<ion-text class="inline" color="success">
|
<ion-text class="inline" color="success">
|
||||||
<ion-icon name="rocket-outline"></ion-icon>
|
<ion-icon name="rocket-outline"></ion-icon>
|
||||||
|
|||||||
@@ -11,12 +11,11 @@ import { ActivatedRoute } from '@angular/router'
|
|||||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||||
import { Observable, of } from 'rxjs'
|
import { Observable, of } from 'rxjs'
|
||||||
import { filter, map, take } from 'rxjs/operators'
|
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 { exists, isEmptyObject, ErrorToastService } from '@start9labs/shared'
|
||||||
import { EOSService } from 'src/app/services/eos.service'
|
import { EOSService } from 'src/app/services/eos.service'
|
||||||
import { LocalStorageService } from 'src/app/services/local-storage.service'
|
import { LocalStorageService } from 'src/app/services/local-storage.service'
|
||||||
import { RecoveredPackageDataEntry } from 'src/app/services/patch-db/data-model'
|
import { RecoveredPackageDataEntry } from 'src/app/services/patch-db/data-model'
|
||||||
|
import { OSUpdatePage } from 'src/app/modals/os-update/os-update.page'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'server-show',
|
selector: 'server-show',
|
||||||
@@ -33,7 +32,6 @@ export class ServerShowPage {
|
|||||||
constructor(
|
constructor(
|
||||||
private readonly alertCtrl: AlertController,
|
private readonly alertCtrl: AlertController,
|
||||||
private readonly modalCtrl: ModalController,
|
private readonly modalCtrl: ModalController,
|
||||||
private readonly wizards: WizardDefs,
|
|
||||||
private readonly loadingCtrl: LoadingController,
|
private readonly loadingCtrl: LoadingController,
|
||||||
private readonly errToast: ErrorToastService,
|
private readonly errToast: ErrorToastService,
|
||||||
private readonly embassyApi: ApiService,
|
private readonly embassyApi: ApiService,
|
||||||
@@ -63,26 +61,19 @@ export class ServerShowPage {
|
|||||||
})
|
})
|
||||||
await alert.present()
|
await alert.present()
|
||||||
} else {
|
} else {
|
||||||
const {
|
const modal = await this.modalCtrl.create({
|
||||||
version,
|
componentProps: {
|
||||||
headline,
|
releaseNotes: this.eosService.eos['release-notes'],
|
||||||
'release-notes': releaseNotes,
|
},
|
||||||
} = this.eosService.eos
|
component: OSUpdatePage,
|
||||||
|
})
|
||||||
await wizardModal(
|
modal.present()
|
||||||
this.modalCtrl,
|
|
||||||
this.wizards.updateOS({
|
|
||||||
version,
|
|
||||||
headline,
|
|
||||||
releaseNotes,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async presentAlertRestart() {
|
async presentAlertRestart() {
|
||||||
const alert = await this.alertCtrl.create({
|
const alert = await this.alertCtrl.create({
|
||||||
header: 'Confirm',
|
header: 'Restart',
|
||||||
message:
|
message:
|
||||||
'Are you sure you want to restart your Embassy? It can take several minutes to come back online.',
|
'Are you sure you want to restart your Embassy? It can take several minutes to come back online.',
|
||||||
buttons: [
|
buttons: [
|
||||||
@@ -106,7 +97,7 @@ export class ServerShowPage {
|
|||||||
const alert = await this.alertCtrl.create({
|
const alert = await this.alertCtrl.create({
|
||||||
header: 'Warning',
|
header: 'Warning',
|
||||||
message:
|
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: [
|
buttons: [
|
||||||
{
|
{
|
||||||
text: 'Cancel',
|
text: 'Cancel',
|
||||||
@@ -120,6 +111,7 @@ export class ServerShowPage {
|
|||||||
cssClass: 'enter-click',
|
cssClass: 'enter-click',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
cssClass: 'alert-warning-message',
|
||||||
})
|
})
|
||||||
await alert.present()
|
await alert.present()
|
||||||
}
|
}
|
||||||
@@ -127,9 +119,9 @@ export class ServerShowPage {
|
|||||||
async presentAlertSystemRebuild() {
|
async presentAlertSystemRebuild() {
|
||||||
const minutes = Object.keys(this.patch.getData()['package-data']).length * 2
|
const minutes = Object.keys(this.patch.getData()['package-data']).length * 2
|
||||||
const alert = await this.alertCtrl.create({
|
const alert = await this.alertCtrl.create({
|
||||||
header: 'System Rebuild',
|
header: 'Warning',
|
||||||
message: new IonicSafeString(
|
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: [
|
buttons: [
|
||||||
{
|
{
|
||||||
@@ -144,15 +136,16 @@ export class ServerShowPage {
|
|||||||
cssClass: 'enter-click',
|
cssClass: 'enter-click',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
cssClass: 'alert-warning-message',
|
||||||
})
|
})
|
||||||
await alert.present()
|
await alert.present()
|
||||||
}
|
}
|
||||||
|
|
||||||
async presentAlertRepairDisk() {
|
async presentAlertRepairDisk() {
|
||||||
const alert = await this.alertCtrl.create({
|
const alert = await this.alertCtrl.create({
|
||||||
header: 'Repair Disk',
|
header: 'Warning',
|
||||||
message: new IonicSafeString(
|
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: [
|
buttons: [
|
||||||
{
|
{
|
||||||
@@ -173,13 +166,13 @@ export class ServerShowPage {
|
|||||||
cssClass: 'enter-click',
|
cssClass: 'enter-click',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
cssClass: 'alert-warning-message',
|
||||||
})
|
})
|
||||||
await alert.present()
|
await alert.present()
|
||||||
}
|
}
|
||||||
|
|
||||||
private async restart() {
|
private async restart() {
|
||||||
const loader = await this.loadingCtrl.create({
|
const loader = await this.loadingCtrl.create({
|
||||||
spinner: 'lines',
|
|
||||||
message: 'Restarting...',
|
message: 'Restarting...',
|
||||||
})
|
})
|
||||||
await loader.present()
|
await loader.present()
|
||||||
@@ -195,7 +188,6 @@ export class ServerShowPage {
|
|||||||
|
|
||||||
private async shutdown() {
|
private async shutdown() {
|
||||||
const loader = await this.loadingCtrl.create({
|
const loader = await this.loadingCtrl.create({
|
||||||
spinner: 'lines',
|
|
||||||
message: 'Shutting down...',
|
message: 'Shutting down...',
|
||||||
})
|
})
|
||||||
await loader.present()
|
await loader.present()
|
||||||
@@ -211,7 +203,6 @@ export class ServerShowPage {
|
|||||||
|
|
||||||
private async systemRebuild() {
|
private async systemRebuild() {
|
||||||
const loader = await this.loadingCtrl.create({
|
const loader = await this.loadingCtrl.create({
|
||||||
spinner: 'lines',
|
|
||||||
message: 'Hard Restarting...',
|
message: 'Hard Restarting...',
|
||||||
})
|
})
|
||||||
await loader.present()
|
await loader.present()
|
||||||
@@ -227,7 +218,6 @@ export class ServerShowPage {
|
|||||||
|
|
||||||
private async checkForEosUpdate(): Promise<void> {
|
private async checkForEosUpdate(): Promise<void> {
|
||||||
const loader = await this.loadingCtrl.create({
|
const loader = await this.loadingCtrl.create({
|
||||||
spinner: 'lines',
|
|
||||||
message: 'Checking for updates',
|
message: 'Checking for updates',
|
||||||
})
|
})
|
||||||
await loader.present()
|
await loader.present()
|
||||||
@@ -282,14 +272,7 @@ export class ServerShowPage {
|
|||||||
action: () =>
|
action: () =>
|
||||||
this.navCtrl.navigateForward(['restore'], { relativeTo: this.route }),
|
this.navCtrl.navigateForward(['restore'], { relativeTo: this.route }),
|
||||||
detail: true,
|
detail: true,
|
||||||
disabled: this.patch
|
disabled: this.eosService.updatingOrBackingUp$,
|
||||||
.watch$('server-info', 'status-info')
|
|
||||||
.pipe(
|
|
||||||
map(
|
|
||||||
status =>
|
|
||||||
status && (status['backing-up'] || !!status['update-progress']),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
Settings: [
|
Settings: [
|
||||||
@@ -302,17 +285,7 @@ export class ServerShowPage {
|
|||||||
? this.updateEos()
|
? this.updateEos()
|
||||||
: this.checkForEosUpdate(),
|
: this.checkForEosUpdate(),
|
||||||
detail: false,
|
detail: false,
|
||||||
disabled: this.patch
|
disabled: this.eosService.updatingOrBackingUp$,
|
||||||
.watch$('server-info', 'status-info')
|
|
||||||
.pipe(
|
|
||||||
map(
|
|
||||||
status =>
|
|
||||||
status &&
|
|
||||||
(status['backing-up'] ||
|
|
||||||
!!status['update-progress'] ||
|
|
||||||
status.updated),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Preferences',
|
title: 'Preferences',
|
||||||
|
|||||||
@@ -53,7 +53,6 @@ export class SessionsPage {
|
|||||||
|
|
||||||
async kill(id: string): Promise<void> {
|
async kill(id: string): Promise<void> {
|
||||||
const loader = await this.loadingCtrl.create({
|
const loader = await this.loadingCtrl.create({
|
||||||
spinner: 'lines',
|
|
||||||
message: 'Killing session...',
|
message: 'Killing session...',
|
||||||
})
|
})
|
||||||
await loader.present()
|
await loader.present()
|
||||||
|
|||||||
@@ -50,8 +50,7 @@ export class SideloadPage {
|
|||||||
}
|
}
|
||||||
async setFile(files?: File[]) {
|
async setFile(files?: File[]) {
|
||||||
const loader = await this.loadingCtrl.create({
|
const loader = await this.loadingCtrl.create({
|
||||||
spinner: 'lines',
|
message: 'Verifying package',
|
||||||
message: 'Verifying Package',
|
|
||||||
cssClass: 'loader',
|
cssClass: 'loader',
|
||||||
})
|
})
|
||||||
await loader.present()
|
await loader.present()
|
||||||
@@ -84,8 +83,7 @@ export class SideloadPage {
|
|||||||
|
|
||||||
async handleUpload() {
|
async handleUpload() {
|
||||||
const loader = await this.loadingCtrl.create({
|
const loader = await this.loadingCtrl.create({
|
||||||
spinner: 'lines',
|
message: 'Uploading package',
|
||||||
message: 'Uploading Package',
|
|
||||||
cssClass: 'loader',
|
cssClass: 'loader',
|
||||||
})
|
})
|
||||||
await loader.present()
|
await loader.present()
|
||||||
|
|||||||
@@ -64,7 +64,6 @@ export class SSHKeysPage {
|
|||||||
|
|
||||||
async add(pubkey: string): Promise<void> {
|
async add(pubkey: string): Promise<void> {
|
||||||
const loader = await this.loadingCtrl.create({
|
const loader = await this.loadingCtrl.create({
|
||||||
spinner: 'lines',
|
|
||||||
message: 'Saving...',
|
message: 'Saving...',
|
||||||
})
|
})
|
||||||
await loader.present()
|
await loader.present()
|
||||||
@@ -100,7 +99,6 @@ export class SSHKeysPage {
|
|||||||
|
|
||||||
async delete(i: number): Promise<void> {
|
async delete(i: number): Promise<void> {
|
||||||
const loader = await this.loadingCtrl.create({
|
const loader = await this.loadingCtrl.create({
|
||||||
spinner: 'lines',
|
|
||||||
message: 'Deleting...',
|
message: 'Deleting...',
|
||||||
})
|
})
|
||||||
await loader.present()
|
await loader.present()
|
||||||
|
|||||||
@@ -170,7 +170,7 @@ export class WifiPage {
|
|||||||
|
|
||||||
private async setCountry(country: string): Promise<void> {
|
private async setCountry(country: string): Promise<void> {
|
||||||
const loader = await this.loadingCtrl.create({
|
const loader = await this.loadingCtrl.create({
|
||||||
spinner: 'lines',
|
message: 'Setting country...',
|
||||||
})
|
})
|
||||||
await loader.present()
|
await loader.present()
|
||||||
|
|
||||||
@@ -261,7 +261,6 @@ export class WifiPage {
|
|||||||
|
|
||||||
private async connect(ssid: string): Promise<void> {
|
private async connect(ssid: string): Promise<void> {
|
||||||
const loader = await this.loadingCtrl.create({
|
const loader = await this.loadingCtrl.create({
|
||||||
spinner: 'lines',
|
|
||||||
message: 'Connecting. This could take a while...',
|
message: 'Connecting. This could take a while...',
|
||||||
})
|
})
|
||||||
await loader.present()
|
await loader.present()
|
||||||
@@ -278,7 +277,6 @@ export class WifiPage {
|
|||||||
|
|
||||||
private async delete(ssid: string): Promise<void> {
|
private async delete(ssid: string): Promise<void> {
|
||||||
const loader = await this.loadingCtrl.create({
|
const loader = await this.loadingCtrl.create({
|
||||||
spinner: 'lines',
|
|
||||||
message: 'Deleting...',
|
message: 'Deleting...',
|
||||||
})
|
})
|
||||||
await loader.present()
|
await loader.present()
|
||||||
@@ -296,7 +294,6 @@ export class WifiPage {
|
|||||||
|
|
||||||
private async save(ssid: string, password: string): Promise<void> {
|
private async save(ssid: string, password: string): Promise<void> {
|
||||||
const loader = await this.loadingCtrl.create({
|
const loader = await this.loadingCtrl.create({
|
||||||
spinner: 'lines',
|
|
||||||
message: 'Saving...',
|
message: 'Saving...',
|
||||||
})
|
})
|
||||||
await loader.present()
|
await loader.present()
|
||||||
@@ -318,7 +315,6 @@ export class WifiPage {
|
|||||||
|
|
||||||
private async saveAndConnect(ssid: string, password: string): Promise<void> {
|
private async saveAndConnect(ssid: string, password: string): Promise<void> {
|
||||||
const loader = await this.loadingCtrl.create({
|
const loader = await this.loadingCtrl.create({
|
||||||
spinner: 'lines',
|
|
||||||
message: 'Connecting. This could take a while...',
|
message: 'Connecting. This could take a while...',
|
||||||
})
|
})
|
||||||
await loader.present()
|
await loader.present()
|
||||||
|
|||||||
@@ -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.',
|
'Chain state will be lost, as will any funds stored on your Bitcoin Core waller that have not been backed up.',
|
||||||
restore: null,
|
restore: null,
|
||||||
start: 'Starting Bitcoin is good for your health.',
|
start: 'Starting Bitcoin is good for your health.',
|
||||||
|
stop: null,
|
||||||
},
|
},
|
||||||
main: {
|
main: {
|
||||||
type: 'docker',
|
type: 'docker',
|
||||||
@@ -359,6 +360,7 @@ export module Mock {
|
|||||||
restore:
|
restore:
|
||||||
'If this is a duplicate instance of the same LND node, you may loose your funds.',
|
'If this is a duplicate instance of the same LND node, you may loose your funds.',
|
||||||
start: 'Starting LND is good for your health.',
|
start: 'Starting LND is good for your health.',
|
||||||
|
stop: null,
|
||||||
},
|
},
|
||||||
main: {
|
main: {
|
||||||
type: 'docker',
|
type: 'docker',
|
||||||
@@ -499,10 +501,11 @@ export module Mock {
|
|||||||
'marketing-site': '',
|
'marketing-site': '',
|
||||||
'donation-url': 'https://start9.com',
|
'donation-url': 'https://start9.com',
|
||||||
alerts: {
|
alerts: {
|
||||||
install: null,
|
install: 'Testing install alert',
|
||||||
uninstall: null,
|
uninstall: null,
|
||||||
restore: null,
|
restore: null,
|
||||||
start: null,
|
start: null,
|
||||||
|
stop: null,
|
||||||
},
|
},
|
||||||
main: {
|
main: {
|
||||||
type: 'docker',
|
type: 'docker',
|
||||||
|
|||||||
@@ -215,15 +215,9 @@ export module RR {
|
|||||||
export type StartPackageReq = WithExpire<{ id: string }> // package.start
|
export type StartPackageReq = WithExpire<{ id: string }> // package.start
|
||||||
export type StartPackageRes = WithRevision<null>
|
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 StopPackageReq = WithExpire<{ id: string }> // package.stop
|
||||||
export type StopPackageRes = WithRevision<null>
|
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 UninstallPackageReq = WithExpire<{ id: string }> // package.uninstall
|
||||||
export type UninstallPackageRes = WithRevision<null>
|
export type UninstallPackageRes = WithRevision<null>
|
||||||
|
|
||||||
|
|||||||
@@ -233,20 +233,12 @@ export abstract class ApiService implements Source<DataModel>, Http<DataModel> {
|
|||||||
startPackage = (params: RR.StartPackageReq) =>
|
startPackage = (params: RR.StartPackageReq) =>
|
||||||
this.syncResponse(() => this.startPackageRaw(params))()
|
this.syncResponse(() => this.startPackageRaw(params))()
|
||||||
|
|
||||||
abstract dryStopPackage(
|
|
||||||
params: RR.DryStopPackageReq,
|
|
||||||
): Promise<RR.DryStopPackageRes>
|
|
||||||
|
|
||||||
protected abstract stopPackageRaw(
|
protected abstract stopPackageRaw(
|
||||||
params: RR.StopPackageReq,
|
params: RR.StopPackageReq,
|
||||||
): Promise<RR.StopPackageRes>
|
): Promise<RR.StopPackageRes>
|
||||||
stopPackage = (params: RR.StopPackageReq) =>
|
stopPackage = (params: RR.StopPackageReq) =>
|
||||||
this.syncResponse(() => this.stopPackageRaw(params))()
|
this.syncResponse(() => this.stopPackageRaw(params))()
|
||||||
|
|
||||||
abstract dryUninstallPackage(
|
|
||||||
params: RR.DryUninstallPackageReq,
|
|
||||||
): Promise<RR.DryUninstallPackageRes>
|
|
||||||
|
|
||||||
protected abstract uninstallPackageRaw(
|
protected abstract uninstallPackageRaw(
|
||||||
params: RR.UninstallPackageReq,
|
params: RR.UninstallPackageReq,
|
||||||
): Promise<RR.UninstallPackageRes>
|
): Promise<RR.UninstallPackageRes>
|
||||||
@@ -273,7 +265,7 @@ export abstract class ApiService implements Source<DataModel>, Http<DataModel> {
|
|||||||
private syncResponse<
|
private syncResponse<
|
||||||
T,
|
T,
|
||||||
F extends (...args: any[]) => Promise<{ response: T; revision?: Revision }>,
|
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) => {
|
return (...a) => {
|
||||||
// let expireId = undefined
|
// let expireId = undefined
|
||||||
// if (temp) {
|
// if (temp) {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export class LiveApiService extends ApiService {
|
|||||||
private readonly config: ConfigService,
|
private readonly config: ConfigService,
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
;(window as any).rpcClient = this
|
; (window as any).rpcClient = this
|
||||||
}
|
}
|
||||||
|
|
||||||
async getStatic(url: string): Promise<string> {
|
async getStatic(url: string): Promise<string> {
|
||||||
@@ -306,22 +306,10 @@ export class LiveApiService extends ApiService {
|
|||||||
return this.http.rpcRequest({ method: 'package.start', params })
|
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> {
|
async stopPackageRaw(params: RR.StopPackageReq): Promise<RR.StopPackageRes> {
|
||||||
return this.http.rpcRequest({ method: 'package.stop', params })
|
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(
|
async deleteRecoveredPackageRaw(
|
||||||
params: RR.DeleteRecoveredPackageReq,
|
params: RR.DeleteRecoveredPackageReq,
|
||||||
): Promise<RR.DeleteRecoveredPackageRes> {
|
): Promise<RR.DeleteRecoveredPackageRes> {
|
||||||
|
|||||||
@@ -501,7 +501,16 @@ export class MockApiService extends ApiService {
|
|||||||
params: RR.DryUpdatePackageReq,
|
params: RR.DryUpdatePackageReq,
|
||||||
): Promise<RR.DryUpdatePackageRes> {
|
): Promise<RR.DryUpdatePackageRes> {
|
||||||
await pauseFor(2000)
|
await pauseFor(2000)
|
||||||
return {}
|
return {
|
||||||
|
lnd: {
|
||||||
|
dependency: 'bitcoind',
|
||||||
|
error: {
|
||||||
|
type: DependencyErrorType.IncorrectVersion,
|
||||||
|
expected: '>0.23.0',
|
||||||
|
received: params.version,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getPackageConfig(
|
async getPackageConfig(
|
||||||
@@ -645,20 +654,6 @@ export class MockApiService extends ApiService {
|
|||||||
return this.withRevision(originalPatch)
|
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> {
|
async stopPackageRaw(params: RR.StopPackageReq): Promise<RR.StopPackageRes> {
|
||||||
await pauseFor(2000)
|
await pauseFor(2000)
|
||||||
const path = `/package-data/${params.id}/installed/status/main`
|
const path = `/package-data/${params.id}/installed/status/main`
|
||||||
@@ -690,20 +685,6 @@ export class MockApiService extends ApiService {
|
|||||||
return this.withRevision(patch)
|
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(
|
async uninstallPackageRaw(
|
||||||
params: RR.UninstallPackageReq,
|
params: RR.UninstallPackageReq,
|
||||||
): Promise<RR.UninstallPackageRes> {
|
): Promise<RR.UninstallPackageRes> {
|
||||||
|
|||||||
@@ -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.',
|
'Chain state will be lost, as will any funds stored on your Bitcoin Core waller that have not been backed up.',
|
||||||
restore: null,
|
restore: null,
|
||||||
start: 'Starting Bitcoin is good for your health.',
|
start: 'Starting Bitcoin is good for your health.',
|
||||||
|
stop: null,
|
||||||
},
|
},
|
||||||
main: {
|
main: {
|
||||||
type: 'docker',
|
type: 'docker',
|
||||||
@@ -448,6 +449,7 @@ export const mockPatchData: DataModel = {
|
|||||||
restore:
|
restore:
|
||||||
'If this is a duplicate instance of the same LND node, you may loose your funds.',
|
'If this is a duplicate instance of the same LND node, you may loose your funds.',
|
||||||
start: 'Starting LND is good for your health.',
|
start: 'Starting LND is good for your health.',
|
||||||
|
stop: null,
|
||||||
},
|
},
|
||||||
main: {
|
main: {
|
||||||
type: 'docker',
|
type: 'docker',
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { BehaviorSubject } from 'rxjs'
|
import { BehaviorSubject, combineLatest } from 'rxjs'
|
||||||
import { MarketplaceEOS } from 'src/app/services/api/api.types'
|
import { MarketplaceEOS } from 'src/app/services/api/api.types'
|
||||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||||
import { Emver } from '@start9labs/shared'
|
import { Emver } from '@start9labs/shared'
|
||||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||||
import { switchMap, take } from 'rxjs/operators'
|
import { map } from 'rxjs/operators'
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
@@ -13,6 +13,36 @@ export class EOSService {
|
|||||||
eos: MarketplaceEOS
|
eos: MarketplaceEOS
|
||||||
updateAvailable$ = new BehaviorSubject<boolean>(false)
|
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(
|
constructor(
|
||||||
private readonly api: ApiService,
|
private readonly api: ApiService,
|
||||||
private readonly emver: Emver,
|
private readonly emver: Emver,
|
||||||
|
|||||||
@@ -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(
|
installPackage(
|
||||||
req: Omit<RR.InstallPackageReq, 'marketplace-url'>,
|
req: Omit<RR.InstallPackageReq, 'marketplace-url'>,
|
||||||
): Observable<unknown> {
|
): Observable<unknown> {
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ export class ServerConfigService {
|
|||||||
text: 'Save',
|
text: 'Save',
|
||||||
handler: async (data: any) => {
|
handler: async (data: any) => {
|
||||||
const loader = await this.loadingCtrl.create({
|
const loader = await this.loadingCtrl.create({
|
||||||
spinner: 'lines',
|
|
||||||
message: 'Saving...',
|
message: 'Saving...',
|
||||||
})
|
})
|
||||||
loader.present()
|
loader.present()
|
||||||
|
|||||||
7
frontend/projects/ui/src/app/util/has-deps.ts
Normal file
7
frontend/projects/ui/src/app/util/has-deps.ts
Normal 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
|
||||||
|
}
|
||||||
@@ -5,7 +5,6 @@ import {
|
|||||||
race,
|
race,
|
||||||
OperatorFunction,
|
OperatorFunction,
|
||||||
Observer,
|
Observer,
|
||||||
combineLatest,
|
|
||||||
} from 'rxjs'
|
} from 'rxjs'
|
||||||
import { take, map } from 'rxjs/operators'
|
import { take, map } from 'rxjs/operators'
|
||||||
|
|
||||||
|
|||||||
@@ -205,6 +205,12 @@ ion-button {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.alert-warning-message {
|
||||||
|
.alert-title {
|
||||||
|
color: var(--ion-color-warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.alert-success-message {
|
.alert-success-message {
|
||||||
.alert-title {
|
.alert-title {
|
||||||
color: var(--ion-color-success);
|
color: var(--ion-color-success);
|
||||||
|
|||||||
Reference in New Issue
Block a user