diff --git a/ui/src/app/modals/os-welcome/os-welcome.page.html b/ui/src/app/modals/os-welcome/os-welcome.page.html index 1622ed7c0..4e4e71754 100644 --- a/ui/src/app/modals/os-welcome/os-welcome.page.html +++ b/ui/src/app/modals/os-welcome/os-welcome.page.html @@ -13,16 +13,16 @@
-

Overview

+

Overview

0.2.8 is a small but important update designed to enhance awareness around potential pitfalls of using certain services. It introduces warnings for installing, uninstalling, backing up, and restoring backups of stateful services such as LND or c-lightning. - This release also draws a distinction between services that can be launched inside the browser and those that are designed to run in the background. + 0.2.8 also draws a distinction between services that can be launched inside the browser and those that are designed to run in the background.

-

Critical Information - Read Carefully

+
Critical - Read Carefully

If you have LND or c-lightning installed, it is critical that you update them to the latest version. An oversight in Start9’s USB backups system has created a situation where restoring a LND or c-lightning backup can result in permanent loss of channel funds. diff --git a/ui/src/app/models/server-model.ts b/ui/src/app/models/server-model.ts index 072b23d1e..5f57de591 100644 --- a/ui/src/app/models/server-model.ts +++ b/ui/src/app/models/server-model.ts @@ -106,6 +106,7 @@ export class ServerModel { ssh: [], notifications: [], welcomeAck: true, + autoCheckUpdates: true, }) } } @@ -124,6 +125,7 @@ export interface S9Server { ssh: SSHFingerprint[] notifications: S9Notification[] welcomeAck: boolean + autoCheckUpdates: boolean } export interface S9Notification { diff --git a/ui/src/app/services/api/api-types.ts b/ui/src/app/services/api/api-types.ts index bc46c0ba8..6f04ef4a9 100644 --- a/ui/src/app/services/api/api-types.ts +++ b/ui/src/app/services/api/api-types.ts @@ -18,6 +18,7 @@ export interface ApiServer { ssh: SSHFingerprint[] serverId: string welcomeAck: boolean + autoCheckUpdates: boolean } /** APPS **/ diff --git a/ui/src/app/services/api/api.service.ts b/ui/src/app/services/api/api.service.ts index de41ccd3b..9d4ec9506 100644 --- a/ui/src/app/services/api/api.service.ts +++ b/ui/src/app/services/api/api.service.ts @@ -28,7 +28,7 @@ export abstract class ApiService { abstract getServerMetrics (): Promise abstract getNotifications (page: number, perPage: number): Promise abstract deleteNotification (id: string): Promise - abstract updateAgent (thing: any): Promise + abstract updateAgent (version: any): Promise abstract acknowledgeOSWelcome (version: string): Promise abstract getAvailableApps (): Promise abstract getAvailableApp (appId: string): Promise diff --git a/ui/src/app/services/api/mock-api.service.ts b/ui/src/app/services/api/mock-api.service.ts index 9421828b8..dfe11d80b 100644 --- a/ui/src/app/services/api/mock-api.service.ts +++ b/ui/src/app/services/api/mock-api.service.ts @@ -12,6 +12,7 @@ import { mockApiAppAvailableFull, mockApiAppAvailableVersionInfo, mockApiAppInst @Injectable() export class MockApiService extends ApiService { welcomeAck = false + constructor ( private readonly appModel: AppModel, private readonly serverModel: ServerModel, @@ -34,7 +35,10 @@ export class MockApiService extends ApiService { async getServer (): Promise { const res = await mockGetServer() - return { ...res, welcomeAck: this.welcomeAck } + return { + ...res, + welcomeAck: this.welcomeAck, + } } async ejectExternalDisk (): Promise { @@ -402,9 +406,11 @@ const mockApiServer: () => ReqRes.GetServerRes = () => ({ serverId: 'start9-mockxyzab', name: 'Embassy:12345678', versionInstalled: '0.2.8', + versionLatest: '0.2.9', status: ServerStatus.RUNNING, alternativeRegistryUrl: 'beta-registry.start9labs.com', welcomeAck: true, + autoCheckUpdates: true, specs: { 'Tor Address': 'nfsnjkcnaskjnlkasnfahj7dh23fdnieqwjdnhjewbfijendiueqwbd.onion', 'CPU': 'Broadcom BCM2711, Quad core Cortex-A72 (ARM v8) 64-bit SoC @ 1.5GHz', diff --git a/ui/src/app/services/sync.notifier.ts b/ui/src/app/services/sync.notifier.ts index 95f72660c..d4b1a448b 100644 --- a/ui/src/app/services/sync.notifier.ts +++ b/ui/src/app/services/sync.notifier.ts @@ -1,26 +1,35 @@ import { Injectable } from '@angular/core' import { ConfigService } from 'src/app/services/config.service' -import { ToastController, NavController, ModalController } from '@ionic/angular' -import { ServerModel, S9Server } from '../models/server-model' +import { ToastController, NavController, ModalController, AlertController } from '@ionic/angular' +import { ServerModel, S9Server, ServerStatus } from '../models/server-model' import { OSWelcomePage } from '../modals/os-welcome/os-welcome.page' import { ApiService } from './api/api.service' +import { Emver } from './emver.service' +import { LoaderService } from './loader.service' @Injectable({ providedIn: 'root', }) export class SyncNotifier { + displayedWelcomeMessage = false + checkedForUpdates = false + constructor ( private readonly config: ConfigService, private readonly toastCtrl: ToastController, private readonly modalCtrl: ModalController, + private readonly alertCtrl: AlertController, private readonly navCtrl: NavController, private readonly serverModel: ServerModel, private readonly apiService: ApiService, + private readonly loader: LoaderService, + private readonly emver: Emver, ) { } async handleSpecial (server: Readonly): Promise { this.handleNotifications(server) this.handleOSWelcome(server) + if (!this.displayedWelcomeMessage) this.handleUpdateCheck(server) } private async handleNotifications (server: Readonly) { @@ -59,26 +68,94 @@ export class SyncNotifier { this.serverModel.update(updates) } - osWelcomeOpen = false private async handleOSWelcome (server: Readonly) { - if (server.welcomeAck || server.versionInstalled !== this.config.version || this.osWelcomeOpen) return + if (server.welcomeAck || server.versionInstalled !== this.config.version || this.displayedWelcomeMessage) return - this.osWelcomeOpen = true - const [modal, _] = await Promise.all([ - this.modalCtrl.create({ - backdropDismiss: false, - component: OSWelcomePage, - presentingElement: await this.modalCtrl.getTop(), - componentProps: { - version: server.versionInstalled, - }, - }), - this.apiService.acknowledgeOSWelcome(this.config.version), - ]) + this.displayedWelcomeMessage = true - modal.onWillDismiss().then(() => { - this.osWelcomeOpen = false + const modal = await this.modalCtrl.create({ + backdropDismiss: false, + component: OSWelcomePage, + presentingElement: await this.modalCtrl.getTop(), + componentProps: { + version: server.versionInstalled, + }, + }) + + modal.onDidDismiss().then(() => { + this.apiService.acknowledgeOSWelcome(this.config.version) + this.handleUpdateCheck(server) }) await modal.present() } + + private async handleUpdateCheck (server: Readonly) { + if (!server.autoCheckUpdates || this.checkedForUpdates) return + + this.checkedForUpdates = true + + if (server.versionLatest && this.emver.compare(server.versionInstalled, server.versionLatest) === -1) { + return this.presentAlertNewOS(server.versionLatest) + } + + try { + const availableApps = await this.apiService.getAvailableApps() + if (!!availableApps.find(app => this.emver.compare(app.versionInstalled, app.versionLatest) === -1)) { + return this.presentAlertNewApps() + } + } catch { + this.checkedForUpdates = false + } + } + + private async presentAlertNewApps () { + const alert = await this.alertCtrl.create({ + backdropDismiss: true, + header: 'Updates Available!', + message: 'New service updates are availbale in the Marketplace.', + buttons: [ + { + text: 'Cancel', + role: 'cancel' + }, + { + text: 'View in Marketplace', + handler: () => { + return this.navCtrl.navigateForward('/services/marketplace') + } + } + ] + }) + await alert.present() + } + + private async presentAlertNewOS (versionLatest: string) { + const alert = await this.alertCtrl.create({ + backdropDismiss: true, + header: 'New EmbassyOS Version!', + message: `Update to EmbassyOS, version ${versionLatest}?`, + buttons: [ + { + text: 'Not now', + role: 'cancel' + }, + { + text: 'Update', + handler: () => { + return this.updateEmbassyOS(versionLatest) + } + } + ] + }) + await alert.present() + } + + private async updateEmbassyOS (versionLatest: string) { + this.loader + .displayDuringAsync(async () => { + await this.apiService.updateAgent(versionLatest) + this.serverModel.update({ status: ServerStatus.UPDATING }) + }) + .catch(e => alert(e)) + } }