ui: add embassy os release notes

This commit is contained in:
Aaron Greenspan
2021-01-28 17:22:05 -07:00
committed by Aiden McClelland
parent 68faa17ab6
commit 79604182c8
18 changed files with 148 additions and 115 deletions

View File

@@ -11,7 +11,7 @@
<ion-slides *ngIf="!($error$ | async)" id="slide-show" style="--bullet-background: white" pager="false"> <ion-slides *ngIf="!($error$ | async)" id="slide-show" style="--bullet-background: white" pager="false">
<ion-slide *ngFor="let slide of params.slideDefinitions"> <ion-slide *ngFor="let slide of params.slideDefinitions">
<dependencies #components *ngIf="slide.selector === 'dependencies'" [params]="slide.params"></dependencies> <dependencies #components *ngIf="slide.selector === 'dependencies'" [params]="slide.params"></dependencies>
<developer-notes #components *ngIf="slide.selector === 'developer-notes'" [params]="slide.params"></developer-notes> <notes #components *ngIf="slide.selector === 'notes'" [params]="slide.params"></notes>
<dependents #components *ngIf="slide.selector === 'dependents'" [params]="slide.params" [finished]="finished"></dependents> <dependents #components *ngIf="slide.selector === 'dependents'" [params]="slide.params" [finished]="finished"></dependents>
<complete #components *ngIf="slide.selector === 'complete'" [params]="slide.params" [finished]="finished"></complete> <complete #components *ngIf="slide.selector === 'complete'" [params]="slide.params" [finished]="finished"></complete>
</ion-slide> </ion-slide>

View File

@@ -7,7 +7,7 @@ import { SharingModule } from 'src/app/modules/sharing.module'
import { DependenciesComponentModule } from './dependencies/dependencies.component.module' import { DependenciesComponentModule } from './dependencies/dependencies.component.module'
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 { DeveloperNotesComponentModule } from './developer-notes/developer-notes.component.module' import { DeveloperNotesComponentModule } from './notes/notes.component.module'
@NgModule({ @NgModule({
declarations: [ declarations: [

View File

@@ -47,6 +47,7 @@
font-size: small; font-size: small;
border-width: 0px 0px 1px 0px; border-width: 0px 0px 1px 0px;
border-color: #393b40; border-color: #393b40;
text-align: left;
} }
@media (min-width:500px) { @media (min-width:500px) {
@@ -57,6 +58,7 @@
font-size: medium; font-size: medium;
border-width: 0px 0px 1px 0px; border-width: 0px 0px 1px 0px;
border-color: #393b40; border-color: #393b40;
text-align: left;
} }
} }

View File

@@ -1,4 +1,4 @@
import { Component, Input, OnInit, QueryList, ViewChild, ViewChildren } from '@angular/core' import { Component, Input, NgZone, OnInit, QueryList, ViewChild, ViewChildren } from '@angular/core'
import { IonContent, IonSlides, ModalController } from '@ionic/angular' import { IonContent, IonSlides, ModalController } from '@ionic/angular'
import { BehaviorSubject, combineLatest, Subscription } from 'rxjs' import { BehaviorSubject, combineLatest, Subscription } from 'rxjs'
import { map } from 'rxjs/operators' import { map } from 'rxjs/operators'
@@ -7,7 +7,7 @@ import { capitalizeFirstLetter } from 'src/app/util/misc.util'
import { CompleteComponent } from './complete/complete.component' import { CompleteComponent } from './complete/complete.component'
import { DependenciesComponent } from './dependencies/dependencies.component' import { DependenciesComponent } from './dependencies/dependencies.component'
import { DependentsComponent } from './dependents/dependents.component' import { DependentsComponent } from './dependents/dependents.component'
import { DeveloperNotesComponent } from './developer-notes/developer-notes.component' import { DeveloperNotesComponent } from './notes/notes.component'
import { Colorable, Loadable } from './loadable' import { Colorable, Loadable } from './loadable'
import { WizardAction } from './wizard-types' import { WizardAction } from './wizard-types'
@@ -50,7 +50,7 @@ export class InstallWizardComponent extends Cleanup implements OnInit {
$currentColor$: BehaviorSubject<string> = new BehaviorSubject('medium') $currentColor$: BehaviorSubject<string> = new BehaviorSubject('medium')
$error$ = new BehaviorSubject(undefined) $error$ = new BehaviorSubject(undefined)
constructor (private readonly modalController: ModalController) { super() } constructor (private readonly modalController: ModalController, private readonly zone: NgZone) { super() }
ngOnInit () { } ngOnInit () { }
ngAfterViewInit () { ngAfterViewInit () {
@@ -80,15 +80,17 @@ export class InstallWizardComponent extends Cleanup implements OnInit {
private async slide () { private async slide () {
if (this.slideComponents[this.slideIndex + 1] === undefined) { return this.finished({ final: true }) } if (this.slideComponents[this.slideIndex + 1] === undefined) { return this.finished({ final: true }) }
this.slideIndex += 1 this.zone.run(async () => {
this.currentSlide.load() this.slideIndex += 1
await this.slideContainer.lockSwipes(false) this.currentSlide.load()
await Promise.all([ await this.slideContainer.lockSwipes(false)
this.contentContainer.scrollToTop(), await Promise.all([
this.slideContainer.slideNext(500), this.contentContainer.scrollToTop(),
]) this.slideContainer.slideNext(500),
await this.slideContainer.lockSwipes(true) ])
this.slideContainer.update() await this.slideContainer.lockSwipes(true)
// this.slideContainer.update()
})
} }
} }
@@ -114,7 +116,7 @@ export type SlideDefinition = SlideCommon & (
selector: 'complete', selector: 'complete',
params: CompleteComponent['params'] params: CompleteComponent['params']
} | { } | {
selector: 'developer-notes', selector: 'notes',
params: DeveloperNotesComponent['params'] params: DeveloperNotesComponent['params']
} }
) )

View File

@@ -2,11 +2,11 @@
<div style="margin-top: 25px;"> <div style="margin-top: 25px;">
<div style="margin: 15px; display: flex; justify-content: center; align-items: center;"> <div style="margin: 15px; display: flex; justify-content: center; align-items: center;">
<ion-label [color]="$color$ | async" style="font-size: xx-large; font-weight: bold;"> <ion-label [color]="$color$ | async" style="font-size: xx-large; font-weight: bold;">
Warning {{params.title}}
</ion-label> </ion-label>
</div> </div>
<div class="long-message"> <div class="long-message">
{{params.developerNotes}} {{params.notes}}
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,6 +1,6 @@
import { NgModule } from '@angular/core' import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common' import { CommonModule } from '@angular/common'
import { DeveloperNotesComponent } from './developer-notes.component' import { DeveloperNotesComponent } from './notes.component'
import { IonicModule } from '@ionic/angular' import { IonicModule } from '@ionic/angular'
import { RouterModule } from '@angular/router' import { RouterModule } from '@angular/router'
import { SharingModule } from 'src/app/modules/sharing.module' import { SharingModule } from 'src/app/modules/sharing.module'

View File

@@ -4,24 +4,24 @@ import { Colorable, Loadable } from '../loadable'
import { WizardAction } from '../wizard-types' import { WizardAction } from '../wizard-types'
@Component({ @Component({
selector: 'developer-notes', selector: 'notes',
templateUrl: './developer-notes.component.html', templateUrl: './notes.component.html',
styleUrls: ['../install-wizard.component.scss'], styleUrls: ['../install-wizard.component.scss'],
}) })
export class DeveloperNotesComponent implements OnInit, Loadable, Colorable { export class DeveloperNotesComponent implements OnInit, Loadable, Colorable {
@Input() params: { @Input() params: {
action: WizardAction action: WizardAction
developerNotes: string notes: string
title: string
titleColor: string
} }
$loading$ = new BehaviorSubject(false) $loading$ = new BehaviorSubject(false)
$color$ = new BehaviorSubject('warning') $color$ = new BehaviorSubject('light')
$cancel$ = new Subject<void>() $cancel$ = new Subject<void>()
load () { } load () { }
constructor () { } constructor () { }
ngOnInit () { ngOnInit () { this.$color$.next(this.params.titleColor) }
console.log('Developer Notes', this.params)
}
} }

View File

@@ -1,5 +1,6 @@
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { AppModel, AppStatus } from 'src/app/models/app-model' import { AppModel, AppStatus } from 'src/app/models/app-model'
import { OsUpdateService } from 'src/app/services/os-update.service'
import { exists } from 'src/app/util/misc.util' import { exists } from 'src/app/util/misc.util'
import { AppDependency, DependentBreakage, AppInstalledPreview } from '../../models/app-types' import { AppDependency, DependentBreakage, AppInstalledPreview } from '../../models/app-types'
import { ApiService } from '../../services/api/api.service' import { ApiService } from '../../services/api/api.service'
@@ -7,7 +8,11 @@ import { InstallWizardComponent, SlideDefinition, TopbarParams } from './install
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class WizardBaker { export class WizardBaker {
constructor (private readonly apiService: ApiService, private readonly appModel: AppModel) { } constructor (
private readonly apiService: ApiService,
private readonly updateService: OsUpdateService,
private readonly appModel: AppModel
) { }
install (values: { install (values: {
id: string, title: string, version: string, serviceRequirements: AppDependency[], installAlert?: string id: string, title: string, version: string, serviceRequirements: AppDependency[], installAlert?: string
@@ -23,8 +28,8 @@ export class WizardBaker {
const toolbar: TopbarParams = { action, title, version } const toolbar: TopbarParams = { action, title, version }
const slideDefinitions: SlideDefinition[] = [ const slideDefinitions: SlideDefinition[] = [
installAlert ? { selector: 'developer-notes', cancelButton: { afterLoading: { text: 'Cancel' } }, nextButton: 'Next', params: { installAlert ? { selector: 'notes', cancelButton: { afterLoading: { text: 'Cancel' } }, nextButton: 'Next', params: {
action, developerNotes: installAlert, action, notes: installAlert, title: 'Warning', titleColor: 'warning',
}} : undefined, }} : undefined,
{ selector: 'dependencies', cancelButton: { afterLoading: { text: 'Cancel' } }, nextButton: 'Install', params: { { selector: 'dependencies', cancelButton: { afterLoading: { text: 'Cancel' } }, nextButton: 'Install', params: {
action, title, version, serviceRequirements, action, title, version, serviceRequirements,
@@ -52,8 +57,8 @@ export class WizardBaker {
const toolbar: TopbarParams = { action, title, version } const toolbar: TopbarParams = { action, title, version }
const slideDefinitions: SlideDefinition[] = [ const slideDefinitions: SlideDefinition[] = [
installAlert ? { selector: 'developer-notes', cancelButton: { afterLoading: { text: 'Cancel' } }, nextButton: 'Next', params: { installAlert ? { selector: 'notes', cancelButton: { afterLoading: { text: 'Cancel' } }, nextButton: 'Next', params: {
action, developerNotes: installAlert, action, notes: installAlert, title: 'Warning', titleColor: 'warning',
}} : undefined, }} : undefined,
{ selector: 'dependencies', cancelButton: { afterLoading: { text: 'Cancel' } }, nextButton: 'Update', params: { { selector: 'dependencies', cancelButton: { afterLoading: { text: 'Cancel' } }, nextButton: 'Update', params: {
action, title, version, serviceRequirements, action, title, version, serviceRequirements,
@@ -70,6 +75,29 @@ export class WizardBaker {
return { toolbar, slideDefinitions: slideDefinitions.filter(exists) } return { toolbar, slideDefinitions: slideDefinitions.filter(exists) }
} }
updateOS (values: {
version: string, releaseNotes: string
}): InstallWizardComponent['params'] {
const { version, releaseNotes } = values
validate(version, exists, 'missing version')
validate(releaseNotes, exists, 'missing updateMessage')
const action = 'update'
const title = 'EmbassyOS'
const toolbar: TopbarParams = { action, title, version }
const slideDefinitions: SlideDefinition[] = [
{ selector: 'notes', cancelButton: { afterLoading: { text: 'Cancel' } }, nextButton: 'Update OS', params: {
action, notes: releaseNotes, title: 'Release Notes', titleColor: 'dark',
}},
{ selector: 'complete', finishButton: 'Dismiss', cancelButton: { whileLoading: { } }, params: {
action, verb: 'beginning update for', title, executeAction: () => this.updateService.updateEmbassyOS(version),
}},
]
return { toolbar, slideDefinitions: slideDefinitions.filter(exists) }
}
downgrade (values: { downgrade (values: {
id: string, title: string, version: string, serviceRequirements: AppDependency[], installAlert?: string id: string, title: string, version: string, serviceRequirements: AppDependency[], installAlert?: string
}): InstallWizardComponent['params'] { }): InstallWizardComponent['params'] {
@@ -84,8 +112,8 @@ export class WizardBaker {
const toolbar: TopbarParams = { action, title, version } const toolbar: TopbarParams = { action, title, version }
const slideDefinitions: SlideDefinition[] = [ const slideDefinitions: SlideDefinition[] = [
installAlert ? { selector: 'developer-notes', cancelButton: { afterLoading: { text: 'Cancel' } }, nextButton: 'Next', params: { installAlert ? { selector: 'notes', cancelButton: { afterLoading: { text: 'Cancel' } }, nextButton: 'Next', params: {
action, developerNotes: installAlert, action, notes: installAlert, title: 'Warning', titleColor: 'warning',
}} : undefined, }} : undefined,
{ selector: 'dependencies', cancelButton: { afterLoading: { text: 'Cancel' } }, nextButton: 'Downgrade', params: { { selector: 'dependencies', cancelButton: { afterLoading: { text: 'Cancel' } }, nextButton: 'Downgrade', params: {
action, title, version, serviceRequirements, action, title, version, serviceRequirements,
@@ -115,8 +143,8 @@ export class WizardBaker {
const toolbar: TopbarParams = { action, title, version } const toolbar: TopbarParams = { action, title, version }
const slideDefinitions: SlideDefinition[] = [ const slideDefinitions: SlideDefinition[] = [
{ selector: 'developer-notes', cancelButton: { afterLoading: { text: 'Cancel' } }, nextButton: 'Continue', params: { { selector: 'notes', cancelButton: { afterLoading: { text: 'Cancel' } }, nextButton: 'Continue', params: {
action, developerNotes: uninstallAlert || defaultUninstallationWarning(title) }, action, notes: uninstallAlert || defaultUninstallationWarning(title), title: 'Warning', titleColor: 'warning' },
}, },
{ selector: 'dependents', cancelButton: { whileLoading: { }, afterLoading: { text: 'Cancel' } }, nextButton: 'Uninstall', params: { { selector: 'dependents', cancelButton: { whileLoading: { }, afterLoading: { text: 'Cancel' } }, nextButton: 'Uninstall', params: {
action, verb: 'uninstalling', title, fetchBreakages: () => this.apiService.uninstallApp(id, true).then( ({ breakages }) => breakages ), action, verb: 'uninstalling', title, fetchBreakages: () => this.apiService.uninstallApp(id, true).then( ({ breakages }) => breakages ),

View File

@@ -1,5 +1,5 @@
<ion-item button lines="none" *ngIf="updateAvailable$ | async as version" (click)="confirmUpdate(version)"> <ion-item button lines="none" *ngIf="updateAvailable$ | async as res" (click)="confirmUpdate(res)">
<ion-label> <ion-label>
New EmbassyOS Version {{version | displayEmver}} Available! New EmbassyOS Version {{res.versionLatest | displayEmver}} Available!
</ion-label> </ion-label>
</ion-item> </ion-item>

View File

@@ -1,9 +1,10 @@
import { Component } from '@angular/core' import { Component } from '@angular/core'
import { OsUpdateService } from 'src/app/services/os-update.service' import { OsUpdateService } from 'src/app/services/os-update.service'
import { Observable } from 'rxjs' import { Observable } from 'rxjs'
import { AlertController } from '@ionic/angular' import { ModalController } from '@ionic/angular'
import { LoaderService } from 'src/app/services/loader.service' import { WizardBaker } from '../install-wizard/prebaked-wizards'
import { displayEmver } from 'src/app/pipes/emver.pipe' import { wizardModal } from '../install-wizard/install-wizard.component'
import { ReqRes } from 'src/app/services/api/api.service'
@Component({ @Component({
selector: 'update-os-banner', selector: 'update-os-banner',
@@ -11,38 +12,24 @@ import { displayEmver } from 'src/app/pipes/emver.pipe'
styleUrls: ['./update-os-banner.component.scss'], styleUrls: ['./update-os-banner.component.scss'],
}) })
export class UpdateOsBannerComponent { export class UpdateOsBannerComponent {
updateAvailable$: Observable<undefined | string> updateAvailable$: Observable<undefined | ReqRes.GetVersionLatestRes>
constructor ( constructor (
private readonly osUpdateService: OsUpdateService, private readonly osUpdateService: OsUpdateService,
private readonly alertCtrl: AlertController, private readonly modalCtrl: ModalController,
private readonly loader: LoaderService, private readonly wizardBaker: WizardBaker,
) { ) {
this.updateAvailable$ = this.osUpdateService.watchForUpdateAvailable$() this.updateAvailable$ = this.osUpdateService.watchForUpdateAvailable$()
} }
ngOnInit () { } ngOnInit () { }
async confirmUpdate (versionLatest: string) { async confirmUpdate (res: ReqRes.GetVersionLatestRes) {
const alert = await this.alertCtrl.create({ await wizardModal(
header: `Update EmbassyOS`, this.modalCtrl,
message: `Update EmbassyOS to version ${displayEmver(versionLatest)}?`, this.wizardBaker.updateOS({
buttons: [ version: res.versionLatest,
{ releaseNotes: res.releaseNotes,
text: 'Cancel', }),
role: 'cancel',
},
{
text: 'Update',
handler: () => this.update(versionLatest),
},
],
})
await alert.present()
}
private async update (versionLatest: string) {
return this.loader.displayDuringP(
this.osUpdateService.updateEmbassyOS(versionLatest),
) )
} }
} }

View File

@@ -115,7 +115,7 @@ export interface S9Server {
name: string name: string
origin: string origin: string
versionInstalled: string versionInstalled: string
versionLatest: string | undefined versionLatest: string | undefined // not on the api as of 0.2.8
status: ServerStatus status: ServerStatus
badge: number badge: number
alternativeRegistryUrl: string | null alternativeRegistryUrl: string | null

View File

@@ -81,6 +81,13 @@
</ion-item> </ion-item>
</ng-container> </ng-container>
<ion-item-divider style="color: var(--ion-color-primary); font-weight: bold;">Release Notes</ion-item-divider>
<ion-item lines="none" button details="true" [disabled]="" (click)="presentModalReleaseNotes()" [disabled]="$newVersionLoading$ | async">
<ion-icon slot="start" name="newspaper-outline" color="medium"></ion-icon>
<ion-label *ngIf="!($newVersionLoading$ | async)"><ion-text style="font-weight: bold;" color="medium">New in {{ vars.versionViewing | displayEmver }}</ion-text></ion-label>
<ion-spinner style="display: block; margin: auto;" name="lines" color="dark" *ngIf="$newVersionLoading$ | async"></ion-spinner>
</ion-item>
<ion-item-divider class="divider">Description</ion-item-divider> <ion-item-divider class="divider">Description</ion-item-divider>
<ion-item lines="none"> <ion-item lines="none">
<ion-label class="ion-text-wrap"> <ion-label class="ion-text-wrap">
@@ -90,13 +97,6 @@
</ion-label> </ion-label>
</ion-item> </ion-item>
<ion-item-divider>Release Notes</ion-item-divider>
<ion-item lines="none" button details="true" [disabled]="" (click)="presentModalReleaseNotes()" [disabled]="$newVersionLoading$ | async">
<ion-icon slot="start" name="newspaper-outline" color="medium"></ion-icon>
<ion-label *ngIf="!($newVersionLoading$ | async)"><ion-text color="medium">New in {{ vars.versionViewing | displayEmver }}</ion-text></ion-label>
<ion-spinner style="display: block; margin: auto;" name="lines" color="dark" *ngIf="$newVersionLoading$ | async"></ion-spinner>
</ion-item>
<ng-container *ngIf="(vars.serviceRequirements)?.length"> <ng-container *ngIf="(vars.serviceRequirements)?.length">
<ion-item-divider class="divider">Service Dependencies <ion-item-divider class="divider">Service Dependencies
<ion-button style="position: relative; right: 10px;" size="small" fill="clear" color="medium" (click)="presentPopover(serviceDependencyDefintion, $event)"> <ion-button style="position: relative; right: 10px;" size="small" fill="clear" color="medium" (click)="presentPopover(serviceDependencyDefintion, $event)">

View File

@@ -1,10 +1,10 @@
import { Component, HostListener, NgZone } from '@angular/core' import { Component, NgZone } from '@angular/core'
import { ActivatedRoute } from '@angular/router' import { ActivatedRoute } from '@angular/router'
import { AppAvailableFull, AppAvailableVersionSpecificInfo, AppDependency } from 'src/app/models/app-types' import { AppAvailableFull, AppAvailableVersionSpecificInfo } from 'src/app/models/app-types'
import { ApiService } from 'src/app/services/api/api.service' import { ApiService } from 'src/app/services/api/api.service'
import { AlertController, ModalController, NavController, PopoverController } from '@ionic/angular' import { AlertController, ModalController, NavController, PopoverController } from '@ionic/angular'
import { markAsLoadingDuring$ } from 'src/app/services/loader.service' import { markAsLoadingDuring$ } from 'src/app/services/loader.service'
import { BehaviorSubject, from, merge, Observable, of, Subscription } from 'rxjs' import { BehaviorSubject, from, Observable, of } from 'rxjs'
import { catchError, concatMap, filter, switchMap, tap } from 'rxjs/operators' import { catchError, concatMap, filter, switchMap, tap } from 'rxjs/operators'
import { Recommendation } from 'src/app/components/recommendation-button/recommendation-button.component' import { Recommendation } from 'src/app/components/recommendation-button/recommendation-button.component'
import { wizardModal } from 'src/app/components/install-wizard/install-wizard.component' import { wizardModal } from 'src/app/components/install-wizard/install-wizard.component'
@@ -13,7 +13,6 @@ import { AppModel } from 'src/app/models/app-model'
import { initPropertySubject, peekProperties, PropertySubject } from 'src/app/util/property-subject.util' import { initPropertySubject, peekProperties, PropertySubject } from 'src/app/util/property-subject.util'
import { Cleanup } from 'src/app/util/cleanup' import { Cleanup } from 'src/app/util/cleanup'
import { InformationPopoverComponent } from 'src/app/components/information-popover/information-popover.component' import { InformationPopoverComponent } from 'src/app/components/information-popover/information-popover.component'
import { pauseFor } from 'src/app/util/misc.util'
import { AppReleaseNotesPage } from 'src/app/modals/app-release-notes/app-release-notes.page' import { AppReleaseNotesPage } from 'src/app/modals/app-release-notes/app-release-notes.page'
import { Emver } from 'src/app/services/emver.service' import { Emver } from 'src/app/services/emver.service'
import { displayEmver } from 'src/app/pipes/emver.pipe' import { displayEmver } from 'src/app/pipes/emver.pipe'

View File

@@ -1,7 +1,7 @@
import { Component } from '@angular/core' import { Component } from '@angular/core'
import { LoadingOptions } from '@ionic/core' import { LoadingOptions } from '@ionic/core'
import { ServerModel, ServerStatus } from 'src/app/models/server-model' import { ServerModel, ServerStatus } from 'src/app/models/server-model'
import { AlertController } from '@ionic/angular' import { AlertController, ModalController } from '@ionic/angular'
import { S9Server } from 'src/app/models/server-model' import { S9Server } from 'src/app/models/server-model'
import { ApiService } from 'src/app/services/api/api.service' import { ApiService } from 'src/app/services/api/api.service'
import { SyncDaemon } from 'src/app/services/sync.service' import { SyncDaemon } from 'src/app/services/sync.service'
@@ -10,6 +10,8 @@ import { PropertySubject, toObservable } from 'src/app/util/property-subject.uti
import { doForAtLeast } from 'src/app/util/misc.util' import { doForAtLeast } from 'src/app/util/misc.util'
import { LoaderService } from 'src/app/services/loader.service' import { LoaderService } from 'src/app/services/loader.service'
import { Emver } from 'src/app/services/emver.service' import { Emver } from 'src/app/services/emver.service'
import { wizardModal } from 'src/app/components/install-wizard/install-wizard.component'
import { WizardBaker } from 'src/app/components/install-wizard/prebaked-wizards'
@Component({ @Component({
selector: 'server-show', selector: 'server-show',
@@ -35,6 +37,8 @@ export class ServerShowPage {
private readonly apiService: ApiService, private readonly apiService: ApiService,
private readonly syncDaemon: SyncDaemon, private readonly syncDaemon: SyncDaemon,
private readonly emver: Emver, private readonly emver: Emver,
private readonly modalCtrl: ModalController,
private readonly wizardBaker: WizardBaker,
) { } ) { }
async ngOnInit () { async ngOnInit () {
@@ -83,9 +87,9 @@ export class ServerShowPage {
await loader.present() await loader.present()
try { try {
const { versionLatest } = await this.apiService.getVersionLatest() const { versionLatest, releaseNotes } = await this.apiService.getVersionLatest()
if (this.emver.compare(this.server.versionInstalled.getValue(), versionLatest) === -1) { if (this.emver.compare(this.server.versionInstalled.getValue(), versionLatest) === -1) {
this.presentAlertUpdate(versionLatest) this.presentAlertUpdate(versionLatest, releaseNotes)
} else { } else {
this.presentAlertUpToDate() this.presentAlertUpToDate()
} }
@@ -106,7 +110,7 @@ export class ServerShowPage {
await alert.present() await alert.present()
} }
async presentAlertUpdate (versionLatest: string) { async presentAlertUpdate (versionLatest: string, releaseNotes: string) {
const alert = await this.alertCtrl.create({ const alert = await this.alertCtrl.create({
backdropDismiss: false, backdropDismiss: false,
header: 'Confirm', header: 'Confirm',
@@ -119,7 +123,7 @@ export class ServerShowPage {
{ {
text: 'Update', text: 'Update',
handler: () => { handler: () => {
this.updateEmbassyOS(versionLatest) this.updateEmbassyOS(versionLatest, releaseNotes)
}, },
}, },
], ],
@@ -171,17 +175,18 @@ export class ServerShowPage {
await alert.present() await alert.present()
} }
private async updateEmbassyOS (versionLatest: string) { private async updateEmbassyOS (versionLatest: string, releaseNotes: string) {
this.loader const { cancelled } = await wizardModal(
.displayDuringAsync(async () => { this.modalCtrl,
await this.apiService.updateAgent(versionLatest) this.wizardBaker.updateOS({
this.serverModel.update({ status: ServerStatus.UPDATING }) version: versionLatest,
// hides the "Update Embassy to..." button for this intance of the component releaseNotes: releaseNotes,
this.updatingFreeze = true }),
this.updating = true )
setTimeout(() => this.updatingFreeze = false, 8000) if (cancelled) return
}) this.updatingFreeze = true
.catch(e => this.setError(e)) this.updating = true
setTimeout(() => this.updatingFreeze = false, 8000)
} }
private async restart () { private async restart () {

View File

@@ -67,7 +67,7 @@ export module ReqRes {
export type PostLoginRes = Unit export type PostLoginRes = Unit
export type GetCheckAuthRes = { } export type GetCheckAuthRes = { }
export type GetServerRes = ApiServer export type GetServerRes = ApiServer
export type GetVersionLatestRes = { versionLatest: string, canUpdate: boolean } // canUpdate not supported at least at 0.2.8 export type GetVersionLatestRes = { versionLatest: string, releaseNotes: string }
export type GetServerMetricsRes = ServerMetrics export type GetServerMetricsRes = ServerMetrics
export type GetAppAvailableRes = ApiAppAvailableFull export type GetAppAvailableRes = ApiAppAvailableFull
export type GetAppAvailableVersionInfoRes = AppAvailableVersionSpecificInfo export type GetAppAvailableVersionInfoRes = AppAvailableVersionSpecificInfo

View File

@@ -410,7 +410,7 @@ const mockApiServer: () => ReqRes.GetServerRes = () => ({
status: ServerStatus.RUNNING, status: ServerStatus.RUNNING,
alternativeRegistryUrl: 'beta-registry.start9labs.com', alternativeRegistryUrl: 'beta-registry.start9labs.com',
welcomeAck: true, welcomeAck: true,
autoCheckUpdates: false, autoCheckUpdates: true,
specs: { specs: {
'Tor Address': 'nfsnjkcnaskjnlkasnfahj7dh23fdnieqwjdnhjewbfijendiueqwbd.onion', 'Tor Address': 'nfsnjkcnaskjnlkasnfahj7dh23fdnieqwjdnhjewbfijendiueqwbd.onion',
'CPU': 'Broadcom BCM2711, Quad core Cortex-A72 (ARM v8) 64-bit SoC @ 1.5GHz', 'CPU': 'Broadcom BCM2711, Quad core Cortex-A72 (ARM v8) 64-bit SoC @ 1.5GHz',
@@ -440,7 +440,10 @@ const mockApiServer: () => ReqRes.GetServerRes = () => ({
const mockVersionLatest: ReqRes.GetVersionLatestRes = { const mockVersionLatest: ReqRes.GetVersionLatestRes = {
versionLatest: '15.2.8.6', versionLatest: '15.2.8.6',
canUpdate: true, releaseNotes: `Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent aliquet, sapien sit amet pretium lacinia, neque tortor consectetur nunc, non volutpat lectus neque in leo. Curabitur a odio eleifend, placerat purus non, aliquet nulla. Aliquam eget lacinia lectus. Aliquam gravida elit eu magna pretium, non interdum tortor vulputate. Ut ac tortor vel tellus blandit malesuada ac ac tortor. Integer tincidunt est quam, non convallis sapien vehicula sed. Donec ullamcorper convallis massa, nec euismod enim tempus vitae. In condimentum semper pulvinar. Sed viverra est id lectus tincidunt, et malesuada eros malesuada.
Curabitur scelerisque eu mauris eget dapibus. In egestas est sit amet nisi cursus iaculis. Mauris consequat pharetra ex, vitae sollicitudin tortor viverra id. Suspendisse lacinia justo id tincidunt feugiat. Nunc risus elit, viverra vel vestibulum ac, varius vel eros. Nam at tellus tempor, semper metus et, tristique elit. Vivamus a dui sit amet orci tincidunt tincidunt. Cras ut velit pretium, euismod dolor non, pulvinar lorem. Praesent dignissim eros quis tortor bibendum, nec convallis libero viverra. Aenean sit amet massa maximus eros congue pellentesque ac nec massa. Nam feugiat felis mi, a aliquet enim porta eget.
Phasellus pellentesque magna vel elit malesuada elementum. Curabitur maximus scelerisque vulputate. Duis facilisis et nisi sed facilisis. Ut consectetur odio tortor, vitae elementum velit scelerisque eget. Maecenas bibendum, massa eu bibendum rhoncus, turpis ex condimentum elit, vel pulvinar ex mi sed urna. Etiam ac erat lectus. Suspendisse dignissim massa tortor. Donec ac dolor in tortor faucibus scelerisque. Nullam et lacus eros. Cras eget sapien nec felis condimentum tincidunt. Praesent ac ante dui. Nam euismod nunc neque, et scelerisque erat efficitur nec. Aenean efficitur tincidunt nulla, ac tempor leo blandit sed. Duis sed tellus quis ante consequat ornare nec vitae eros. Praesent ultrices nunc ut lacus tincidunt finibus. Praesent at eros non est commodo ultricies.
Curabitur eu felis convallis, lobortis nulla laoreet, commodo lacus. Vestibulum at sapien sed metus tincidunt vulputate. Donec cursus augue non sapien imperdiet cursus. Aliquam pellentesque ligula id magna blandit rutrum. Aliquam mattis ipsum leo, nec pharetra lectus tristique eu. Duis egestas mollis aliquam. Duis aliquet dictum risus, quis dictum mauris finibus id.`,
} }
const mockApiServerMetrics: ReqRes.GetServerMetricsRes = { const mockApiServerMetrics: ReqRes.GetServerMetricsRes = {

View File

@@ -8,12 +8,13 @@ import { Emver } from './emver.service'
// call checkForUpdates in marketplace pages, can subscribe globally however // call checkForUpdates in marketplace pages, can subscribe globally however
type UpdateAvailable = { versionLatest: string, releaseNotes: string}
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class OsUpdateService { export class OsUpdateService {
// holds version latest if update available, undefined if not. // holds version latest if update available, undefined if not.
private readonly $updateAvailable$ = new BehaviorSubject<string>(undefined) private readonly $updateAvailable$ = new BehaviorSubject<UpdateAvailable>(undefined)
watchForUpdateAvailable$ (): Observable<undefined | string> { watchForUpdateAvailable$ (): Observable<undefined | UpdateAvailable> {
return this.$updateAvailable$.asObservable().pipe(distinctUntilChanged()) return this.$updateAvailable$.asObservable().pipe(distinctUntilChanged())
} }
@@ -25,7 +26,7 @@ export class OsUpdateService {
) { } ) { }
// emits the latest version or re-checks to see if there's a new latest version // emits the latest version or re-checks to see if there's a new latest version
checkWhenNotAvailable$ (): Observable<undefined | string> { checkWhenNotAvailable$ (): Observable<undefined | UpdateAvailable> {
return this.$updateAvailable$.pipe( return this.$updateAvailable$.pipe(
take(1), take(1),
concatMap(vl => vl ? of(vl) : this.checkForUpdates$()), concatMap(vl => vl ? of(vl) : this.checkForUpdates$()),
@@ -33,12 +34,12 @@ export class OsUpdateService {
} }
// can sub to this imperatively and take the return value as gospel, or watch the $updateAvailable$ subject for the same info. // can sub to this imperatively and take the return value as gospel, or watch the $updateAvailable$ subject for the same info.
checkForUpdates$ (): Observable<undefined | string> { checkForUpdates$ (): Observable<undefined | UpdateAvailable> {
return forkJoin([ return forkJoin([
this.serverModel.watch().versionInstalled.pipe(take(1)), this.serverModel.watch().versionInstalled.pipe(take(1)),
this.apiService.getVersionLatest(), this.apiService.getVersionLatest(),
]).pipe( ]).pipe(
map(([vi, vl]) => this.updateIsAvailable(vi, vl.versionLatest) ? vl.versionLatest : undefined), map(([vi, vl]) => this.updateIsAvailable(vi, vl) ? vl : undefined),
catchError(e => { catchError(e => {
console.error(`OsUpdateService Error: ${e}`) console.error(`OsUpdateService Error: ${e}`)
return of(undefined) return of(undefined)
@@ -48,9 +49,9 @@ export class OsUpdateService {
) )
} }
updateIsAvailable (vi: string, vl: string): boolean { updateIsAvailable (vi: string, vl: UpdateAvailable): boolean {
if (!vi || !vl) return false if (!vi || !vl) return false
if (this.emver.compare(vi, vl) === -1) { if (this.emver.compare(vi, vl.versionLatest) === -1) {
this.$updateAvailable$.next(vl) this.$updateAvailable$.next(vl)
return true return true
} else { } else {

View File

@@ -1,12 +1,13 @@
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { AlertController, IonicSafeString, ModalController, NavController } from '@ionic/angular' import { AlertController, IonicSafeString, ModalController, NavController } from '@ionic/angular'
import { wizardModal } from '../components/install-wizard/install-wizard.component'
import { WizardBaker } from '../components/install-wizard/prebaked-wizards'
import { OSWelcomePage } from '../modals/os-welcome/os-welcome.page' import { OSWelcomePage } from '../modals/os-welcome/os-welcome.page'
import { S9Server } from '../models/server-model' import { S9Server } from '../models/server-model'
import { displayEmver } from '../pipes/emver.pipe' import { displayEmver } from '../pipes/emver.pipe'
import { ApiService } from './api/api.service' import { ApiService, ReqRes } from './api/api.service'
import { ConfigService } from './config.service' import { ConfigService } from './config.service'
import { Emver } from './emver.service' import { Emver } from './emver.service'
import { LoaderService } from './loader.service'
import { OsUpdateService } from './os-update.service' import { OsUpdateService } from './os-update.service'
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
@@ -14,12 +15,12 @@ export class StartupAlertsNotifier {
constructor ( constructor (
private readonly alertCtrl: AlertController, private readonly alertCtrl: AlertController,
private readonly navCtrl: NavController, private readonly navCtrl: NavController,
private readonly loader: LoaderService,
private readonly config: ConfigService, private readonly config: ConfigService,
private readonly modalCtrl: ModalController, private readonly modalCtrl: ModalController,
private readonly apiService: ApiService, private readonly apiService: ApiService,
private readonly emver: Emver, private readonly emver: Emver,
private readonly osUpdateService: OsUpdateService, private readonly osUpdateService: OsUpdateService,
private readonly wizardBaker: WizardBaker,
) { } ) { }
// This takes our three checks and filters down to those that should run. // This takes our three checks and filters down to those that should run.
@@ -54,7 +55,7 @@ export class StartupAlertsNotifier {
display: s => this.displayOsWelcome(s), display: s => this.displayOsWelcome(s),
hasRun: false, hasRun: false,
} }
osUpdate: Check<string | undefined> = { osUpdate: Check<ReqRes.GetVersionLatestRes | undefined> = {
name: 'osUpdate', name: 'osUpdate',
shouldRun: s => this.shouldRunOsUpdateCheck(s), shouldRun: s => this.shouldRunOsUpdateCheck(s),
check: s => this.osUpdateCheck(s), check: s => this.osUpdateCheck(s),
@@ -83,9 +84,9 @@ export class StartupAlertsNotifier {
return server.autoCheckUpdates return server.autoCheckUpdates
} }
private async osUpdateCheck (s: Readonly<S9Server>): Promise<string | undefined> { private async osUpdateCheck (s: Readonly<S9Server>): Promise<ReqRes.GetVersionLatestRes | undefined> {
const { versionLatest } = await this.apiService.getVersionLatest() const res = await this.apiService.getVersionLatest()
return this.osUpdateService.updateIsAvailable(s.versionInstalled, versionLatest) ? versionLatest : undefined return this.osUpdateService.updateIsAvailable(s.versionInstalled, res) ? res : undefined
} }
private async appsCheck (): Promise<boolean> { private async appsCheck (): Promise<boolean> {
@@ -113,12 +114,17 @@ export class StartupAlertsNotifier {
}) })
} }
private async displayOsUpdateCheck (versionLatest: string | undefined): Promise<boolean> { private async displayOsUpdateCheck (res: ReqRes.GetVersionLatestRes): Promise<boolean> {
const { update } = await this.presentAlertNewOS(versionLatest) const { update } = await this.presentAlertNewOS(res.versionLatest)
if (update) { if (update) {
await this.loader.displayDuringP( const { cancelled } = await wizardModal(
this.osUpdateService.updateEmbassyOS(versionLatest), this.modalCtrl,
).catch(e => alert(e)) this.wizardBaker.updateOS({
version: res.versionLatest,
releaseNotes: res.releaseNotes,
}),
)
if (cancelled) return true
return false return false
} }
return true return true
@@ -134,7 +140,7 @@ export class StartupAlertsNotifier {
<div>New service updates are available in the Marketplace.</div> <div>New service updates are available in the Marketplace.</div>
<div style="font-size:x-small">You can disable these checks in your Embassy Config</div> <div style="font-size:x-small">You can disable these checks in your Embassy Config</div>
</div> </div>
` `,
), ),
buttons: [ buttons: [
{ {
@@ -165,7 +171,7 @@ export class StartupAlertsNotifier {
<div>Update EmbassyOS to version ${displayEmver(versionLatest)}?</div> <div>Update EmbassyOS to version ${displayEmver(versionLatest)}?</div>
<div style="font-size:x-small">You can disable these checks in your Embassy Config</div> <div style="font-size:x-small">You can disable these checks in your Embassy Config</div>
</div> </div>
` `,
), ),
buttons: [ buttons: [
{ {