diff --git a/ui/src/app/app.component.html b/ui/src/app/app.component.html index 75de58045..4f57d664c 100644 --- a/ui/src/app/app.component.html +++ b/ui/src/app/app.component.html @@ -58,7 +58,6 @@ - @@ -89,6 +88,7 @@ + diff --git a/ui/src/app/app.component.ts b/ui/src/app/app.component.ts index c47740c7b..17398c683 100644 --- a/ui/src/app/app.component.ts +++ b/ui/src/app/app.component.ts @@ -16,6 +16,7 @@ import { ConnectionFailure, ConnectionService } from './services/connection.serv import { StartupAlertsService } from './services/startup-alerts.service' import { ConfigService } from './services/config.service' import { isEmptyObject } from './util/misc.util' +import { MarketplaceApiService } from './services/api/marketplace/marketplace-api.service' @Component({ selector: 'app-root', @@ -56,12 +57,13 @@ export class AppComponent { private readonly storage: Storage, private readonly authService: AuthService, private readonly router: Router, - private readonly api: ApiService, + private readonly embassyApi: ApiService, private readonly http: HttpService, private readonly alertCtrl: AlertController, private readonly loader: LoaderService, private readonly emver: Emver, private readonly connectionService: ConnectionService, + private readonly marketplaceApi: MarketplaceApiService, private readonly startupAlertsService: StartupAlertsService, private readonly toastCtrl: ToastController, private readonly patch: PatchDbService, @@ -95,26 +97,30 @@ export class AppComponent { ) .subscribe(_ => { this.showMenu = true - this.router.navigate([''], { replaceUrl: true }) - this.connectionService.start() + // if on the login screen, route to dashboard + if (this.router.url.startsWith('/login')) { + this.router.navigate([''], { replaceUrl: true }) + } + // start the connection monitor + this.connectionService.start(auth) + // watch connection to display connectivity issues + this.marketplaceApi.init(auth) // watch connection to display connectivity issues this.watchConnection(auth) - // watch router to highlight selected menu item + // // watch router to highlight selected menu item this.watchRouter(auth) - // watch status to display/hide maintenance page + // // watch status to display/hide maintenance page this.watchStatus(auth) - // watch version to refresh browser window + // // watch version to refresh browser window this.watchVersion(auth) - // watch unread notification count to display toast + // // watch unread notification count to display toast this.watchNotifications(auth) - // run startup alerts + // // run startup alerts this.startupAlertsService.runChecks() }) - // UNVERIFIED } else if (auth === AuthState.UNVERIFIED) { this.showMenu = false - this.connectionService.stop() this.patch.stop() this.storage.clear() this.router.navigate(['/login'], { replaceUrl: true }) @@ -220,7 +226,6 @@ export class AppComponent { this.patch.watch$('server-info', 'unread-notification-count') .pipe( takeWhile(() => auth === AuthState.VERIFIED), - finalize(() => console.log('FINALIZING!!!')), ) .subscribe(count => { this.unreadCount = count @@ -271,7 +276,7 @@ export class AppComponent { private async logout () { this.loader.of(LoadingSpinner('Logging out...')) - .displayDuringP(this.api.logout({ })) + .displayDuringP(this.embassyApi.logout({ })) .then(() => this.authService.setUnverified()) .catch(e => this.setError(e)) } diff --git a/ui/src/app/components/install-wizard/prebaked-wizards.ts b/ui/src/app/components/install-wizard/prebaked-wizards.ts index 5836b00f6..20d252fb4 100644 --- a/ui/src/app/components/install-wizard/prebaked-wizards.ts +++ b/ui/src/app/components/install-wizard/prebaked-wizards.ts @@ -8,7 +8,7 @@ import { InstallWizardComponent, SlideDefinition, TopbarParams } from './install @Injectable({ providedIn: 'root' }) export class WizardBaker { constructor ( - private readonly apiService: ApiService, + private readonly embassyApi: ApiService, ) { } install (values: { @@ -43,7 +43,7 @@ export class WizardBaker { action, verb: 'beginning installation for', title, - executeAction: () => this.apiService.installPackage({ id, version }), + executeAction: () => this.embassyApi.installPackage({ id, version }), }, }, bottomBar: { @@ -89,7 +89,7 @@ export class WizardBaker { action, verb: 'updating', title, - fetchBreakages: () => this.apiService.dryUpdatePackage({ id, version }).then(breakages => breakages), + fetchBreakages: () => this.embassyApi.dryUpdatePackage({ id, version }).then(breakages => breakages), }, }, bottomBar: { @@ -104,7 +104,7 @@ export class WizardBaker { action, verb: 'beginning update for', title, - executeAction: () => this.apiService.installPackage({ id, version }), + executeAction: () => this.embassyApi.installPackage({ id, version }), }, }, bottomBar: { @@ -149,7 +149,7 @@ export class WizardBaker { action, verb: 'beginning update for', title, - executeAction: () => this.apiService.updateServer({ }), + executeAction: () => this.embassyApi.updateServer({ }), }, }, bottomBar: { @@ -191,7 +191,7 @@ export class WizardBaker { action, verb: 'downgrading', title, - fetchBreakages: () => this.apiService.dryUpdatePackage({ id, version }).then(breakages => breakages), + fetchBreakages: () => this.embassyApi.dryUpdatePackage({ id, version }).then(breakages => breakages), }, }, bottomBar: { @@ -204,7 +204,7 @@ export class WizardBaker { action, verb: 'beginning downgrade for', title, - executeAction: () => this.apiService.installPackage({ id, version }), + executeAction: () => this.embassyApi.installPackage({ id, version }), }, }, bottomBar: { @@ -246,7 +246,7 @@ export class WizardBaker { action, verb: 'uninstalling', title, - fetchBreakages: () => this.apiService.dryRemovePackage({ id }).then(breakages => breakages), + fetchBreakages: () => this.embassyApi.dryRemovePackage({ id }).then(breakages => breakages), }, }, bottomBar: { cancel: { whileLoading: { }, afterLoading: { text: 'Cancel' } }, next: 'Uninstall' }, @@ -258,7 +258,7 @@ export class WizardBaker { action, verb: 'uninstalling', title, - executeAction: () => this.apiService.removePackage({ id }), + executeAction: () => this.embassyApi.removePackage({ id }), }, }, bottomBar: { finish: 'Dismiss', cancel: { whileLoading: { } } }, diff --git a/ui/src/app/guards/maintenance.guard.ts b/ui/src/app/guards/maintenance.guard.ts index 206e7eaa1..d69bea4a0 100644 --- a/ui/src/app/guards/maintenance.guard.ts +++ b/ui/src/app/guards/maintenance.guard.ts @@ -7,6 +7,7 @@ import { PatchDbService } from '../services/patch-db/patch-db.service' providedIn: 'root', }) export class MaintenanceGuard implements CanActivate, CanActivateChild { + constructor ( private readonly router: Router, private readonly patch: PatchDbService, diff --git a/ui/src/app/modals/markdown/markdown.page.ts b/ui/src/app/modals/markdown/markdown.page.ts index 781d82f01..8c6abaf3c 100644 --- a/ui/src/app/modals/markdown/markdown.page.ts +++ b/ui/src/app/modals/markdown/markdown.page.ts @@ -17,12 +17,12 @@ export class MarkdownPage { constructor ( private readonly modalCtrl: ModalController, private readonly errToast: ErrorToastService, - private readonly apiService: ApiService, + private readonly embassyApi: ApiService, ) { } async ngOnInit () { try { - this.content = await this.apiService.getStatic(this.contentUrl) + this.content = await this.embassyApi.getStatic(this.contentUrl) } catch (e) { console.error(e.message) this.errToast.present(e.message) diff --git a/ui/src/app/modals/os-welcome/os-welcome.page.ts b/ui/src/app/modals/os-welcome/os-welcome.page.ts index ca5e89e70..85eade5ad 100644 --- a/ui/src/app/modals/os-welcome/os-welcome.page.ts +++ b/ui/src/app/modals/os-welcome/os-welcome.page.ts @@ -13,12 +13,12 @@ export class OSWelcomePage { constructor ( private readonly modalCtrl: ModalController, - private readonly apiService: ApiService, + private readonly embassyApi: ApiService, private readonly config: ConfigService, ) { } async dismiss () { - this.apiService.setDbValue({ pointer: '/welcome-ack', value: this.config.version }) + this.embassyApi.setDbValue({ pointer: '/welcome-ack', value: this.config.version }) .catch(console.error) // return false to skip subsequent alert modals (e.g. check for updates modals) diff --git a/ui/src/app/modules/sharing.module.ts b/ui/src/app/modules/sharing.module.ts index e2b7156d1..a35d53e21 100644 --- a/ui/src/app/modules/sharing.module.ts +++ b/ui/src/app/modules/sharing.module.ts @@ -8,7 +8,6 @@ import { TruncateCenterPipe, TruncateEndPipe } from '../pipes/truncate.pipe' import { MaskPipe } from '../pipes/mask.pipe' import { HasUiPipe, LaunchablePipe } from '../pipes/ui.pipe' import { EmptyPipe } from '../pipes/empty.pipe' -import { StatusPipe } from '../pipes/status.pipe' import { NotificationColorPipe } from '../pipes/notification-color.pipe' @NgModule({ @@ -26,7 +25,6 @@ import { NotificationColorPipe } from '../pipes/notification-color.pipe' HasUiPipe, LaunchablePipe, EmptyPipe, - StatusPipe, NotificationColorPipe, ], imports: [], @@ -44,7 +42,6 @@ import { NotificationColorPipe } from '../pipes/notification-color.pipe' HasUiPipe, LaunchablePipe, EmptyPipe, - StatusPipe, NotificationColorPipe, ], }) diff --git a/ui/src/app/pages/apps-routes/app-actions/app-actions.page.ts b/ui/src/app/pages/apps-routes/app-actions/app-actions.page.ts index 60a07091b..42858ede2 100644 --- a/ui/src/app/pages/apps-routes/app-actions/app-actions.page.ts +++ b/ui/src/app/pages/apps-routes/app-actions/app-actions.page.ts @@ -26,7 +26,7 @@ export class AppActionsPage { constructor ( private readonly route: ActivatedRoute, - private readonly apiService: ApiService, + private readonly embassyApi: ApiService, private readonly modalCtrl: ModalController, private readonly alertCtrl: AlertController, private readonly loaderService: LoaderService, @@ -123,7 +123,7 @@ export class AppActionsPage { private async executeAction (pkgId: string, actionId: string) { try { const res = await this.loaderService.displayDuringP( - this.apiService.executePackageAction({ id: pkgId, 'action-id': actionId }), + this.embassyApi.executePackageAction({ id: pkgId, 'action-id': actionId }), ) const successAlert = await this.alertCtrl.create({ diff --git a/ui/src/app/pages/apps-routes/app-config/app-config.page.ts b/ui/src/app/pages/apps-routes/app-config/app-config.page.ts index 5b79fbba2..369b142f6 100644 --- a/ui/src/app/pages/apps-routes/app-config/app-config.page.ts +++ b/ui/src/app/pages/apps-routes/app-config/app-config.page.ts @@ -50,7 +50,7 @@ export class AppConfigPage { private readonly navCtrl: NavController, private readonly route: ActivatedRoute, private readonly wizardBaker: WizardBaker, - private readonly apiService: ApiService, + private readonly embassyApi: ApiService, private readonly loader: LoaderService, private readonly alertCtrl: AlertController, private readonly modalController: ModalController, @@ -86,12 +86,12 @@ export class AppConfigPage { .pipe( tap(pkg => this.pkg = pkg), tap(() => this.loadingText = 'Fetching config spec...'), - concatMap(() => this.apiService.getPackageConfig({ id: pkgId })), + concatMap(() => this.embassyApi.getPackageConfig({ id: pkgId })), concatMap(({ spec, config }) => { const rec = history.state && history.state.configRecommendation as Recommendation if (rec) { this.loadingText = `Setting properties to accommodate ${rec.dependentTitle}...` - return from(this.apiService.dryConfigureDependency({ 'dependency-id': pkgId, 'dependent-id': rec.dependentId })) + return from(this.embassyApi.dryConfigureDependency({ 'dependency-id': pkgId, 'dependent-id': rec.dependentId })) .pipe( map(res => ({ spec, @@ -162,7 +162,7 @@ export class AppConfigPage { spinner: 'lines', cssClass: 'loader', }).displayDuringAsync(async () => { - const breakages = await this.apiService.drySetPackageConfig({ id: pkg.manifest.id, config: this.config }) + const breakages = await this.embassyApi.drySetPackageConfig({ id: pkg.manifest.id, config: this.config }) if (!isEmptyObject(breakages.length)) { const { cancelled } = await wizardModal( @@ -175,7 +175,7 @@ export class AppConfigPage { if (cancelled) return { skip: true } } - return this.apiService.setPackageConfig({ id: pkg.manifest.id, config: this.config }) + return this.embassyApi.setPackageConfig({ id: pkg.manifest.id, config: this.config }) .then(() => ({ skip: false })) }) .then(({ skip }) => { diff --git a/ui/src/app/pages/apps-routes/app-instructions/app-instructions.page.ts b/ui/src/app/pages/apps-routes/app-instructions/app-instructions.page.ts index 54ee897b5..89c2f587c 100644 --- a/ui/src/app/pages/apps-routes/app-instructions/app-instructions.page.ts +++ b/ui/src/app/pages/apps-routes/app-instructions/app-instructions.page.ts @@ -19,15 +19,17 @@ export class AppInstructionsPage { constructor ( private readonly route: ActivatedRoute, private readonly errToast: ErrorToastService, - private readonly apiService: ApiService, + private readonly embassyApi: ApiService, private readonly patch: PatchDbService, ) { } async ngOnInit () { const pkgId = this.route.snapshot.paramMap.get('pkgId') + const url = this.patch.data['package-data'][pkgId]['static-files'].instructions + try { - this.instructions = await this.apiService.getStatic(url) + this.instructions = await this.embassyApi.getStatic(url) } catch (e) { console.error(e) this.errToast.present(e.message) @@ -35,8 +37,4 @@ export class AppInstructionsPage { this.loading = false } } - - ngAfterViewInit () { - this.content.scrollToPoint(undefined, 1) - } } diff --git a/ui/src/app/pages/apps-routes/app-interfaces/app-interfaces.page.html b/ui/src/app/pages/apps-routes/app-interfaces/app-interfaces.page.html index b00bccb3f..57fc03920 100644 --- a/ui/src/app/pages/apps-routes/app-interfaces/app-interfaces.page.html +++ b/ui/src/app/pages/apps-routes/app-interfaces/app-interfaces.page.html @@ -10,7 +10,7 @@ - + {{ interface.value.name }} diff --git a/ui/src/app/pages/apps-routes/app-list/app-list.page.html b/ui/src/app/pages/apps-routes/app-list/app-list.page.html index 57f9ac3e0..89d3d6bc7 100644 --- a/ui/src/app/pages/apps-routes/app-list/app-list.page.html +++ b/ui/src/app/pages/apps-routes/app-list/app-list.page.html @@ -8,41 +8,40 @@ -
-
-
-

Welcome to your Embassy

-

Get started by installing your first service.

-
- - - Marketplace - +
+
+

Welcome to your Embassy

+

Get started by installing your first service.

- - - - - - -
-
- -
-
- - icon - - - - - - {{ pkg.value.manifest.title }} - -
-
-
-
-
+ + + Marketplace +
+ + + + + + +
+
+ +
+
+ + icon + + + + + + + {{ pkg.value.entry.manifest.title }} + +
+
+
+
+
diff --git a/ui/src/app/pages/apps-routes/app-list/app-list.page.ts b/ui/src/app/pages/apps-routes/app-list/app-list.page.ts index 55c972cdd..714a7c885 100644 --- a/ui/src/app/pages/apps-routes/app-list/app-list.page.ts +++ b/ui/src/app/pages/apps-routes/app-list/app-list.page.ts @@ -1,10 +1,11 @@ import { Component } from '@angular/core' import { ConfigService } from 'src/app/services/config.service' -import { ConnectionService } from 'src/app/services/connection.service' +import { ConnectionFailure, ConnectionService } from 'src/app/services/connection.service' import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' import { PackageDataEntry } from 'src/app/services/patch-db/data-model' -import { combineLatest, Subscription } from 'rxjs' +import { Subscription } from 'rxjs' import { PkgStatusRendering, renderPkgStatus } from 'src/app/services/pkg-status-rendering.service' +import { distinctUntilChanged, filter } from 'rxjs/operators' @Component({ selector: 'app-list', @@ -12,70 +13,94 @@ import { PkgStatusRendering, renderPkgStatus } from 'src/app/services/pkg-status styleUrls: ['./app-list.page.scss'], }) export class AppListPage { - connected: boolean subs: Subscription[] = [] - serviceInfo: { [id: string]: { - bulbInfo: { + connectionFailure: boolean + pkgs: { [id: string]: { + entry: PackageDataEntry + bulb: { class: string img: string } - rendering: PkgStatusRendering + statusRendering: PkgStatusRendering | null + sub: Subscription | null }} = { } constructor ( private readonly config: ConfigService, - public readonly connectionService: ConnectionService, + private readonly connectionService: ConnectionService, public readonly patch: PatchDbService, ) { } ngOnInit () { this.subs = [ - combineLatest([ - this.patch.connected$(), - this.patch.watch$('package-data'), - ]) - .subscribe(([connected, pkgs]) => { - this.connected = connected + this.patch.watch$('package-data') + .pipe( + filter(obj => { + return Object.keys(obj).length !== Object.keys(this.pkgs).length + }), + ) + .subscribe(pkgs => { + const ids = Object.keys(pkgs) + console.log('PKGSPKGS', ids) - Object.keys(pkgs).forEach(pkgId => { - let bulbClass = 'bulb-on' - let img = '' - - if (!this.connected) { - bulbClass = 'bulb-off', - img = 'assets/img/off-bulb.png' - } - - const rendering = renderPkgStatus(pkgs[pkgId].state, pkgs[pkgId].installed.status) - switch (rendering.color) { - case 'danger': - img = 'assets/img/danger-bulb.png' - break - case 'success': - img = 'assets/img/success-bulb.png' - break - case 'warning': - img = 'assets/img/warning-bulb.png' - break - default: - bulbClass = 'bulb-off', - img = 'assets/img/off-bulb.png' - break - } - - this.serviceInfo[pkgId] = { - bulbInfo: { - class: bulbClass, - img, - }, - rendering, + Object.keys(this.pkgs).forEach(id => { + if (!ids.includes(id)) { + this.pkgs[id].sub.unsubscribe() + delete this.pkgs[id] } }) + + ids.forEach(id => { + // if already subscribed, return + if (this.pkgs[id]) return + this.pkgs[id] = { + entry: pkgs[id], + bulb: { + class: 'bulb-off', + img: 'assets/img/off-bulb.png', + }, + statusRendering: renderPkgStatus(pkgs[id].state, pkgs[id].installed?.status), + sub: null, + } + // subscribe to pkg + this.pkgs[id].sub = this.patch.watch$('package-data', id).subscribe(pkg => { + let bulbClass = 'bulb-on' + let img = '' + const statusRendering = renderPkgStatus(pkgs[id].state, pkgs[id].installed?.status) + switch (statusRendering.color) { + case 'danger': + img = 'assets/img/danger-bulb.png' + break + case 'success': + img = 'assets/img/success-bulb.png' + break + case 'warning': + img = 'assets/img/warning-bulb.png' + break + default: + bulbClass = 'bulb-off', + img = 'assets/img/off-bulb.png' + break + } + this.pkgs[id].entry = pkg + this.pkgs[id].bulb = { + class: bulbClass, + img, + } + this.pkgs[id].statusRendering = statusRendering + }) + }) + }), + + this.connectionService.watchFailure$() + .subscribe(connectionFailure => { + this.connectionFailure = connectionFailure !== ConnectionFailure.None }), ] } ngOnDestroy () { + Object.values(this.pkgs).forEach(pkg => pkg.sub.unsubscribe()) this.subs.forEach(sub => sub.unsubscribe()) } diff --git a/ui/src/app/pages/apps-routes/app-logs/app-logs.page.ts b/ui/src/app/pages/apps-routes/app-logs/app-logs.page.ts index 99c71cdc8..5e0a7bbaa 100644 --- a/ui/src/app/pages/apps-routes/app-logs/app-logs.page.ts +++ b/ui/src/app/pages/apps-routes/app-logs/app-logs.page.ts @@ -17,7 +17,7 @@ export class AppLogsPage { constructor ( private readonly route: ActivatedRoute, private readonly errToast: ErrorToastService, - private readonly apiService: ApiService, + private readonly embassyApi: ApiService, ) { } ngOnInit () { @@ -29,7 +29,7 @@ export class AppLogsPage { this.logs = '' try { - const logs = await this.apiService.getPackageLogs({ id: this.pkgId }) + const logs = await this.embassyApi.getPackageLogs({ id: this.pkgId }) this.logs = logs.map(l => `${l.timestamp} ${l.log}`).join('\n\n') setTimeout(async () => await this.content.scrollToBottom(100), 200) } catch (e) { diff --git a/ui/src/app/pages/apps-routes/app-manifest/app-manifest.page.ts b/ui/src/app/pages/apps-routes/app-manifest/app-manifest.page.ts index 852ee7dfd..10fdc0a36 100644 --- a/ui/src/app/pages/apps-routes/app-manifest/app-manifest.page.ts +++ b/ui/src/app/pages/apps-routes/app-manifest/app-manifest.page.ts @@ -3,7 +3,6 @@ import { ActivatedRoute } from '@angular/router' import { Subscription } from 'rxjs' import { PackageDataEntry } from 'src/app/services/patch-db/data-model' import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' -import { getManifest } from 'src/app/services/config.service' import * as JsonPointer from 'json-pointer' import { IonContent } from '@ionic/angular' @@ -56,7 +55,7 @@ export class AppManifestPage { } private setNode () { - this.node = JsonPointer.get(getManifest(this.pkg), this.pointer || '') + this.node = JsonPointer.get(this.pkg.manifest, this.pointer || '') } async goToNested (key: string): Promise { diff --git a/ui/src/app/pages/apps-routes/app-metrics/app-metrics.page.html b/ui/src/app/pages/apps-routes/app-metrics/app-metrics.page.html index e21c12060..a85baed77 100644 --- a/ui/src/app/pages/apps-routes/app-metrics/app-metrics.page.html +++ b/ui/src/app/pages/apps-routes/app-metrics/app-metrics.page.html @@ -9,7 +9,7 @@ - + @@ -17,15 +17,15 @@ - + No health checks - + -
+
diff --git a/ui/src/app/pages/apps-routes/app-metrics/app-metrics.page.ts b/ui/src/app/pages/apps-routes/app-metrics/app-metrics.page.ts index f3ceefa98..602f6a267 100644 --- a/ui/src/app/pages/apps-routes/app-metrics/app-metrics.page.ts +++ b/ui/src/app/pages/apps-routes/app-metrics/app-metrics.page.ts @@ -1,10 +1,11 @@ import { Component, ViewChild } from '@angular/core' import { ActivatedRoute } from '@angular/router' import { IonContent } from '@ionic/angular' +import { Subscription } from 'rxjs' import { Metric } from 'src/app/services/api/api.types' import { ApiService } from 'src/app/services/api/embassy/embassy-api.service' import { ErrorToastService } from 'src/app/services/error-toast.service' -import { PackageDataEntry } from 'src/app/services/patch-db/data-model' +import { MainStatus } from 'src/app/services/patch-db/data-model' import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' import { pauseFor } from 'src/app/util/misc.util' @@ -16,9 +17,10 @@ import { pauseFor } from 'src/app/util/misc.util' export class AppMetricsPage { loading = true pkgId: string - pkg: PackageDataEntry + mainStatus: MainStatus going = false metrics: Metric + subs: Subscription[] = [] @ViewChild(IonContent) content: IonContent @@ -26,12 +28,17 @@ export class AppMetricsPage { private readonly route: ActivatedRoute, private readonly errToast: ErrorToastService, private readonly patch: PatchDbService, - private readonly apiService: ApiService, + private readonly embassyApi: ApiService, ) { } ngOnInit () { this.pkgId = this.route.snapshot.paramMap.get('pkgId') - this.pkg = this.patch.data['package-data'][this.pkgId] + this.subs = [ + this.patch.watch$('package-data', this.pkgId, 'installed', 'status', 'main') + .subscribe(main => { + this.mainStatus = main + }), + ] this.startDaemon() } @@ -42,6 +49,7 @@ export class AppMetricsPage { ngOnDestroy () { this.stopDaemon() + this.subs.forEach(sub => sub.unsubscribe()) } async startDaemon (): Promise { @@ -58,7 +66,7 @@ export class AppMetricsPage { async getMetrics (): Promise { try { - this.metrics = await this.apiService.getPkgMetrics({ id: this.pkgId}) + this.metrics = await this.embassyApi.getPkgMetrics({ id: this.pkgId}) } catch (e) { console.error(e) this.errToast.present(e.message) diff --git a/ui/src/app/pages/apps-routes/app-properties/app-properties.page.ts b/ui/src/app/pages/apps-routes/app-properties/app-properties.page.ts index 775c7c031..ddee2fdc3 100644 --- a/ui/src/app/pages/apps-routes/app-properties/app-properties.page.ts +++ b/ui/src/app/pages/apps-routes/app-properties/app-properties.page.ts @@ -30,7 +30,7 @@ export class AppPropertiesPage { constructor ( private readonly route: ActivatedRoute, - private readonly apiService: ApiService, + private readonly embassyApi: ApiService, private readonly errToast: ErrorToastService, private readonly alertCtrl: AlertController, private readonly toastCtrl: ToastController, @@ -41,16 +41,20 @@ export class AppPropertiesPage { async ngOnInit () { this.pkgId = this.route.snapshot.paramMap.get('pkgId') - this.running = this.patch.data['package-data'][this.pkgId].installed?.status.main.status === PackageMainStatus.Running await this.getProperties() this.subs = [ - this.route.queryParams.subscribe(queryParams => { + this.route.queryParams + .subscribe(queryParams => { if (queryParams['pointer'] === this.pointer) return this.pointer = queryParams['pointer'] this.node = JsonPointer.get(this.properties, this.pointer || '') }), + this.patch.watch$('package-data', this.pkgId, 'installed', 'status', 'main', 'status') + .subscribe(status => { + this.running = status === PackageMainStatus.Running + }), ] } @@ -115,7 +119,7 @@ export class AppPropertiesPage { private async getProperties (): Promise { this.loading = true try { - this.properties = await this.apiService.getPackageProperties({ id: this.pkgId }) + this.properties = await this.embassyApi.getPackageProperties({ id: this.pkgId }) this.node = JsonPointer.get(this.properties, this.pointer || '') } catch (e) { console.error(e) diff --git a/ui/src/app/pages/apps-routes/app-restore/app-restore.page.html b/ui/src/app/pages/apps-routes/app-restore/app-restore.page.html index fa5bc597a..d52b60bad 100644 --- a/ui/src/app/pages/apps-routes/app-restore/app-restore.page.html +++ b/ui/src/app/pages/apps-routes/app-restore/app-restore.page.html @@ -21,7 +21,7 @@

Warning

- Restoring from backup will overwrite all current data for {{ title }} . + Restoring from backup will overwrite all current data for {{ patch.data['package-data'][pkgId].manifest.title }} .

diff --git a/ui/src/app/pages/apps-routes/app-restore/app-restore.page.ts b/ui/src/app/pages/apps-routes/app-restore/app-restore.page.ts index 53ddddaf7..2b10d4b14 100644 --- a/ui/src/app/pages/apps-routes/app-restore/app-restore.page.ts +++ b/ui/src/app/pages/apps-routes/app-restore/app-restore.page.ts @@ -6,6 +6,7 @@ import { DiskInfo } from 'src/app/services/api/api.types' import { ActivatedRoute } from '@angular/router' import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' import { Subscription } from 'rxjs' +import { take } from 'rxjs/operators' @Component({ selector: 'app-restore', @@ -26,15 +27,13 @@ export class AppRestorePage { constructor ( private readonly route: ActivatedRoute, private readonly modalCtrl: ModalController, - private readonly apiService: ApiService, + private readonly embassyApi: ApiService, private readonly loadingCtrl: LoadingController, - private readonly patch: PatchDbService, + public readonly patch: PatchDbService, ) { } ngOnInit () { this.pkgId = this.route.snapshot.paramMap.get('pkgId') - this.title = this.patch.data['package-data'][this.pkgId].manifest.title - this.getExternalDisks() } @@ -49,7 +48,7 @@ export class AppRestorePage { async getExternalDisks (): Promise { try { - this.disks = await this.apiService.getDisks({ }) + this.disks = await this.embassyApi.getDisks({ }) this.allPartitionsMounted = Object.values(this.disks).every(d => Object.values(d.partitions).every(p => p['is-mounted'])) } catch (e) { console.error(e) @@ -87,7 +86,7 @@ export class AppRestorePage { await loader.present() try { - await this.apiService.restorePackage({ + await this.embassyApi.restorePackage({ id: this.pkgId, logicalname, password, diff --git a/ui/src/app/pages/apps-routes/app-show/app-show.page.ts b/ui/src/app/pages/apps-routes/app-show/app-show.page.ts index 120ce4083..35a89f669 100644 --- a/ui/src/app/pages/apps-routes/app-show/app-show.page.ts +++ b/ui/src/app/pages/apps-routes/app-show/app-show.page.ts @@ -40,7 +40,7 @@ export class AppShowPage { private readonly errToast: ErrorToastService, private readonly loader: LoaderService, private readonly modalCtrl: ModalController, - private readonly apiService: ApiService, + private readonly embassyApi: ApiService, private readonly wizardBaker: WizardBaker, private readonly config: ConfigService, public readonly patch: PatchDbService, @@ -49,13 +49,13 @@ export class AppShowPage { async ngOnInit () { this.pkgId = this.route.snapshot.paramMap.get('pkgId') - this.pkg = this.patch.data['package-data'][this.pkgId] this.subs = [ combineLatest([ this.patch.connected$(), this.patch.watch$('package-data', this.pkgId), ]) .subscribe(([connected, pkg]) => { + this.pkg = pkg this.connected = connected this.rendering = renderPkgStatus(pkg.state, pkg.installed.status) }), @@ -82,7 +82,7 @@ export class AppShowPage { spinner: 'lines', cssClass: 'loader', }).displayDuringAsync(async () => { - const breakages = await this.apiService.dryStopPackage({ id }) + const breakages = await this.embassyApi.dryStopPackage({ id }) console.log('BREAKAGES', breakages) @@ -100,7 +100,7 @@ export class AppShowPage { if (cancelled) return { } } - return this.apiService.stopPackage({ id }).then(chill) + return this.embassyApi.stopPackage({ id }).then(chill) }).catch(e => this.setError(e)) } @@ -211,7 +211,7 @@ export class AppShowPage { spinner: 'lines', cssClass: 'loader', }).displayDuringP( - this.apiService.startPackage({ id: this.pkgId }), + this.embassyApi.startPackage({ id: this.pkgId }), ).catch(e => this.setError(e)) } @@ -235,7 +235,8 @@ export class AppShowPage { title: 'Monitor', icon: 'medkit-outline', color: 'danger', - disabled: [], + // @TODO make the disabled check better. Don't want to list every status here. Monitor should be disabled except is pkg is running. + disabled: [FEStatus.Installing, FEStatus.Updating, FEStatus.Removing, FEStatus.BackingUp, FEStatus.Restoring], }, { action: () => this.navCtrl.navigateForward(['config'], { relativeTo: this.route }), diff --git a/ui/src/app/pages/marketplace-routes/app-release-notes/app-release-notes.module.ts b/ui/src/app/pages/marketplace-routes/app-release-notes/app-release-notes.module.ts index a6457ef85..8d4c06a60 100644 --- a/ui/src/app/pages/marketplace-routes/app-release-notes/app-release-notes.module.ts +++ b/ui/src/app/pages/marketplace-routes/app-release-notes/app-release-notes.module.ts @@ -5,6 +5,7 @@ import { IonicModule } from '@ionic/angular' import { AppReleaseNotes } from './app-release-notes.page' import { PwaBackComponentModule } from 'src/app/components/pwa-back-button/pwa-back.component.module' import { SharingModule } from 'src/app/modules/sharing.module' +import { TextSpinnerComponentModule } from 'src/app/components/text-spinner/text-spinner.component.module' const routes: Routes = [ { @@ -20,6 +21,7 @@ const routes: Routes = [ RouterModule.forChild(routes), PwaBackComponentModule, SharingModule, + TextSpinnerComponentModule, ], declarations: [AppReleaseNotes], }) diff --git a/ui/src/app/pages/marketplace-routes/app-release-notes/app-release-notes.page.html b/ui/src/app/pages/marketplace-routes/app-release-notes/app-release-notes.page.html index d214de22d..e6fd347b6 100644 --- a/ui/src/app/pages/marketplace-routes/app-release-notes/app-release-notes.page.html +++ b/ui/src/app/pages/marketplace-routes/app-release-notes/app-release-notes.page.html @@ -8,7 +8,7 @@ - +
diff --git a/ui/src/app/pages/marketplace-routes/marketplace-list/marketplace-list.page.html b/ui/src/app/pages/marketplace-routes/marketplace-list/marketplace-list.page.html index d4d0831f7..fd2298753 100644 --- a/ui/src/app/pages/marketplace-routes/marketplace-list/marketplace-list.page.html +++ b/ui/src/app/pages/marketplace-routes/marketplace-list/marketplace-list.page.html @@ -53,7 +53,7 @@

{{ pkg.manifest.title }}

{{ pkg.manifest.description.short }}

- +

Installed Update Available diff --git a/ui/src/app/pages/marketplace-routes/marketplace-list/marketplace-list.page.ts b/ui/src/app/pages/marketplace-routes/marketplace-list/marketplace-list.page.ts index 5b44b4eba..4146adb77 100644 --- a/ui/src/app/pages/marketplace-routes/marketplace-list/marketplace-list.page.ts +++ b/ui/src/app/pages/marketplace-routes/marketplace-list/marketplace-list.page.ts @@ -3,12 +3,12 @@ import { MarketplaceData, MarketplaceEOS, MarketplacePkg } from 'src/app/service import { wizardModal } from 'src/app/components/install-wizard/install-wizard.component' import { IonContent, ModalController } from '@ionic/angular' import { WizardBaker } from 'src/app/components/install-wizard/prebaked-wizards' -import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' -import { PackageState } from 'src/app/services/patch-db/data-model' +import { PackageDataEntry, PackageState } from 'src/app/services/patch-db/data-model' import { Subscription } from 'rxjs' import { ErrorToastService } from 'src/app/services/error-toast.service' import { MarketplaceService } from '../marketplace.service' import { MarketplaceApiService } from 'src/app/services/api/marketplace/marketplace-api.service' +import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' @Component({ selector: 'marketplace-list', @@ -17,6 +17,8 @@ import { MarketplaceApiService } from 'src/app/services/api/marketplace/marketpl }) export class MarketplaceListPage { @ViewChild(IonContent) content: IonContent + localPkgs: { [id: string]: PackageDataEntry } + pageLoading = true pkgsLoading = true @@ -37,7 +39,7 @@ export class MarketplaceListPage { constructor ( private readonly marketplaceService: MarketplaceService, - private readonly marketplaceApiService: MarketplaceApiService, + private readonly marketplaceApi: MarketplaceApiService, private readonly modalCtrl: ModalController, private readonly errToast: ErrorToastService, private readonly wizardBaker: WizardBaker, @@ -45,11 +47,16 @@ export class MarketplaceListPage { ) { } async ngOnInit () { + this.subs = [ + this.patch.watch$('package-data').subscribe(pkgs => { + this.localPkgs = pkgs + }), + ] try { const [data, eos] = await Promise.all([ - this.marketplaceApiService.getMarketplaceData({ }), - this.marketplaceApiService.getEos({ }), + this.marketplaceApi.getMarketplaceData({ }), + this.marketplaceApi.getEos({ }), this.getPkgs(), ]) this.eos = eos @@ -107,7 +114,7 @@ export class MarketplaceListPage { if (this.pkgs.length) { this.pkgsLoading = false } - await this.marketplaceService.getUpdates(this.patch.data['package-data']) + await this.marketplaceService.getUpdates(this.localPkgs) this.pkgs = this.marketplaceService.updates } else { const pkgs = await this.marketplaceService.getPkgs( diff --git a/ui/src/app/pages/marketplace-routes/marketplace.service.ts b/ui/src/app/pages/marketplace-routes/marketplace.service.ts index 463b731b2..90b23fbb7 100644 --- a/ui/src/app/pages/marketplace-routes/marketplace.service.ts +++ b/ui/src/app/pages/marketplace-routes/marketplace.service.ts @@ -12,32 +12,30 @@ export class MarketplaceService { updates: MarketplacePkg[] = null releaseNotes: { [id: string]: { [version: string]: string - } } + } } = { } constructor ( - private readonly marketplaceApiService: MarketplaceApiService, + private readonly marketplaceApi: MarketplaceApiService, private readonly emver: Emver, ) { } - async getUpdates (pkgData: { [id: string]: PackageDataEntry}) : Promise { - const idAndCurrentVersions = Object.keys(pkgData).map(key => ({ id: key, version: pkgData[key].manifest.version })) - console.log(JSON.stringify(idAndCurrentVersions)) - const latestPkgs = (await this.marketplaceApiService.getMarketplacePkgs({ + async getUpdates (localPkgs: { [id: string]: PackageDataEntry }) : Promise { + const idAndCurrentVersions = Object.keys(localPkgs).map(key => ({ id: key, version: localPkgs[key].manifest.version })) + const latestPkgs = (await this.marketplaceApi.getMarketplacePkgs({ ids: idAndCurrentVersions, })) const updates = latestPkgs.filter(latestPkg => { const latestVersion = latestPkg.manifest.version - const curVersion = pkgData[latestPkg.manifest.id]?.manifest.version + const curVersion = localPkgs[latestPkg.manifest.id]?.manifest.version return !!curVersion && this.emver.compare(latestVersion, curVersion) === 1 }) this.updates = updates - return updates } async getPkgs (category: string, query: string, page: number, perPage: number) : Promise { - const pkgs = await this.marketplaceApiService.getMarketplacePkgs({ + const pkgs = await this.marketplaceApi.getMarketplacePkgs({ category: category !== 'all' ? category : undefined, query, page: String(page), @@ -51,7 +49,10 @@ export class MarketplaceService { } async getPkg (id: string, version?: string): Promise { - const pkg = (await this.marketplaceApiService.getMarketplacePkgs({ ids: [{ id, version }]}))[0] + const pkgs = await this.marketplaceApi.getMarketplacePkgs({ + ids: [{ id, version: version || '*' }], + }) + const pkg = pkgs[0] if (pkg) { this.pkgs[id] = pkg } else { @@ -60,7 +61,7 @@ export class MarketplaceService { } async getReleaseNotes (id: string): Promise { - this.releaseNotes[id] = await this.marketplaceApiService.getReleaseNotes({ id }) + this.releaseNotes[id] = await this.marketplaceApi.getReleaseNotes({ id }) } } diff --git a/ui/src/app/pages/notifications/notifications.page.ts b/ui/src/app/pages/notifications/notifications.page.ts index f14657073..0468fab6f 100644 --- a/ui/src/app/pages/notifications/notifications.page.ts +++ b/ui/src/app/pages/notifications/notifications.page.ts @@ -20,7 +20,7 @@ export class NotificationsPage { readonly perPage = 20 constructor ( - private readonly apiService: ApiService, + private readonly embassyApi: ApiService, private readonly loader: LoaderService, private readonly errToast: ErrorToastService, private readonly alertCtrl: AlertController, @@ -48,7 +48,7 @@ export class NotificationsPage { async getNotifications (): Promise { let notifications: ServerNotifications = [] try { - notifications = await this.apiService.getNotifications({ page: this.page, 'per-page': this.perPage }) + notifications = await this.embassyApi.getNotifications({ page: this.page, 'per-page': this.perPage }) this.needInfinite = notifications.length >= this.perPage this.page++ } catch (e) { @@ -65,7 +65,7 @@ export class NotificationsPage { spinner: 'lines', cssClass: 'loader', }).displayDuringP( - this.apiService.deleteNotification({ id }).then(() => { + this.embassyApi.deleteNotification({ id }).then(() => { this.notifications.splice(index, 1) }), ).catch(e => { diff --git a/ui/src/app/pages/server-routes/developer-routes/dev-options/dev-options.page.html b/ui/src/app/pages/server-routes/developer-routes/dev-options/dev-options.page.html index bfd154b86..45cb062a7 100644 --- a/ui/src/app/pages/server-routes/developer-routes/dev-options/dev-options.page.html +++ b/ui/src/app/pages/server-routes/developer-routes/dev-options/dev-options.page.html @@ -7,7 +7,7 @@ - + diff --git a/ui/src/app/pages/server-routes/developer-routes/dev-ssh-keys/ssh.service.ts b/ui/src/app/pages/server-routes/developer-routes/dev-ssh-keys/ssh.service.ts index fff7918e7..ebd200a52 100644 --- a/ui/src/app/pages/server-routes/developer-routes/dev-ssh-keys/ssh.service.ts +++ b/ui/src/app/pages/server-routes/developer-routes/dev-ssh-keys/ssh.service.ts @@ -10,7 +10,7 @@ export class SSHService { private readonly keys$ = new BehaviorSubject({ }) constructor ( - private readonly apiService: ApiService, + private readonly embassyApi: ApiService, ) { } watch$ () { @@ -18,18 +18,18 @@ export class SSHService { } async getKeys (): Promise { - const keys = await this.apiService.getSshKeys({ }) + const keys = await this.embassyApi.getSshKeys({ }) this.keys$.next(keys) } async add (pubkey: string): Promise { - const key = await this.apiService.addSshKey({ pubkey }) + const key = await this.embassyApi.addSshKey({ pubkey }) const keys = this.keys$.getValue() this.keys$.next({ ...keys, ...key }) } async delete (hash: string): Promise { - await this.apiService.deleteSshKey({ hash }) + await this.embassyApi.deleteSshKey({ hash }) const keys = this.keys$.getValue() const filtered = Object.keys(keys) diff --git a/ui/src/app/pages/server-routes/lan/lan.page.ts b/ui/src/app/pages/server-routes/lan/lan.page.ts index f76b2b6e5..64b3ff3fd 100644 --- a/ui/src/app/pages/server-routes/lan/lan.page.ts +++ b/ui/src/app/pages/server-routes/lan/lan.page.ts @@ -26,7 +26,7 @@ export class LANPage { private readonly toastCtrl: ToastController, private readonly config: ConfigService, private readonly loader: LoaderService, - private readonly apiService: ApiService, + private readonly embassyApi: ApiService, private readonly patch: PatchDbService, ) { } @@ -54,7 +54,7 @@ export class LANPage { spinner: 'lines', cssClass: 'loader', }).displayDuringAsync( async () => { - await this.apiService.refreshLan({ }) + await this.embassyApi.refreshLan({ }) }).catch(e => { console.error(e) }) diff --git a/ui/src/app/pages/server-routes/server-backup/server-backup.page.ts b/ui/src/app/pages/server-routes/server-backup/server-backup.page.ts index e0b4b156d..65ff969f5 100644 --- a/ui/src/app/pages/server-routes/server-backup/server-backup.page.ts +++ b/ui/src/app/pages/server-routes/server-backup/server-backup.page.ts @@ -17,7 +17,7 @@ export class ServerBackupPage { constructor ( private readonly modalCtrl: ModalController, - private readonly apiService: ApiService, + private readonly embassyApi: ApiService, private readonly loadingCtrl: LoadingController, ) { } @@ -32,7 +32,7 @@ export class ServerBackupPage { async getExternalDisks (): Promise { try { - this.disks = await this.apiService.getDisks({ }) + this.disks = await this.embassyApi.getDisks({ }) this.allPartitionsMounted = Object.values(this.disks).every(d => Object.values(d.partitions).every(p => p['is-mounted'])) } catch (e) { console.error(e) @@ -70,7 +70,7 @@ export class ServerBackupPage { await loader.present() try { - await this.apiService.createBackup({ logicalname, password }) + await this.embassyApi.createBackup({ logicalname, password }) } catch (e) { console.error(e) this.error = e.message diff --git a/ui/src/app/pages/server-routes/server-logs/server-logs.page.ts b/ui/src/app/pages/server-routes/server-logs/server-logs.page.ts index c5b1b1d1d..7e0970320 100644 --- a/ui/src/app/pages/server-routes/server-logs/server-logs.page.ts +++ b/ui/src/app/pages/server-routes/server-logs/server-logs.page.ts @@ -15,7 +15,7 @@ export class ServerLogsPage { constructor ( private readonly errToast: ErrorToastService, - private readonly apiService: ApiService, + private readonly embassyApi: ApiService, ) { } ngOnInit () { @@ -26,7 +26,7 @@ export class ServerLogsPage { this.logs = '' this.loading = true try { - const logs = await this.apiService.getServerLogs({ }) + const logs = await this.embassyApi.getServerLogs({ }) this.logs = logs.map(l => `${l.timestamp} ${l.log}`).join('\n\n') setTimeout(async () => await this.content.scrollToBottom(100), 200) } catch (e) { diff --git a/ui/src/app/pages/server-routes/server-metrics/server-metrics.page.ts b/ui/src/app/pages/server-routes/server-metrics/server-metrics.page.ts index 2c3056fbe..acee5718e 100644 --- a/ui/src/app/pages/server-routes/server-metrics/server-metrics.page.ts +++ b/ui/src/app/pages/server-routes/server-metrics/server-metrics.page.ts @@ -16,7 +16,7 @@ export class ServerMetricsPage { constructor ( private readonly errToast: ErrorToastService, - private readonly apiService: ApiService, + private readonly embassyApi: ApiService, ) { } ngOnInit () { @@ -41,7 +41,7 @@ export class ServerMetricsPage { async getMetrics (): Promise { try { - this.metrics = await this.apiService.getServerMetrics({ }) + this.metrics = await this.embassyApi.getServerMetrics({ }) } catch (e) { console.error(e) this.errToast.present(e.message) diff --git a/ui/src/app/pages/server-routes/server-show/server-show.page.ts b/ui/src/app/pages/server-routes/server-show/server-show.page.ts index 6e04e424e..9ea458d0d 100644 --- a/ui/src/app/pages/server-routes/server-show/server-show.page.ts +++ b/ui/src/app/pages/server-routes/server-show/server-show.page.ts @@ -16,7 +16,7 @@ export class ServerShowPage { constructor ( private readonly alertCtrl: AlertController, private readonly loader: LoaderService, - private readonly apiService: ApiService, + private readonly embassyApi: ApiService, private readonly navCtrl: NavController, private readonly route: ActivatedRoute, ) { } @@ -73,7 +73,7 @@ export class ServerShowPage { this.loader .of(LoadingSpinner(`Restarting...`)) .displayDuringAsync( async () => { - await this.apiService.restartServer({ }) + await this.embassyApi.restartServer({ }) }) .catch(console.error) } @@ -82,7 +82,7 @@ export class ServerShowPage { this.loader .of(LoadingSpinner(`Shutting down...`)) .displayDuringAsync( async () => { - await this.apiService.shutdownServer({ }) + await this.embassyApi.shutdownServer({ }) }) .catch(console.error) } @@ -91,9 +91,9 @@ export class ServerShowPage { this.settings = { 'Settings': [ { - title: 'Preferences', - icon: 'cog-outline', - action: () => this.navCtrl.navigateForward(['preferences'], { relativeTo: this.route }), + title: 'Privacy and Security', + icon: 'shield-checkmark-outline', + action: () => this.navCtrl.navigateForward(['privacy'], { relativeTo: this.route }), }, { title: 'LAN', diff --git a/ui/src/app/pages/server-routes/wifi/wifi-add/wifi-add.page.ts b/ui/src/app/pages/server-routes/wifi/wifi-add/wifi-add.page.ts index 347abe6b2..955d35df8 100644 --- a/ui/src/app/pages/server-routes/wifi/wifi-add/wifi-add.page.ts +++ b/ui/src/app/pages/server-routes/wifi/wifi-add/wifi-add.page.ts @@ -19,7 +19,7 @@ export class WifiAddPage { constructor ( private readonly navCtrl: NavController, private readonly errToast: ErrorToastService, - private readonly apiService: ApiService, + private readonly embassyApi: ApiService, private readonly loader: LoaderService, private readonly wifiService: WifiService, ) { } @@ -30,7 +30,7 @@ export class WifiAddPage { spinner: 'lines', cssClass: 'loader', }).displayDuringAsync(async () => { - await this.apiService.addWifi({ + await this.embassyApi.addWifi({ ssid: this.ssid, password: this.password, country: this.countryCode, @@ -50,7 +50,7 @@ export class WifiAddPage { spinner: 'lines', cssClass: 'loader', }).displayDuringAsync(async () => { - await this.apiService.addWifi({ + await this.embassyApi.addWifi({ ssid: this.ssid, password: this.password, country: this.countryCode, diff --git a/ui/src/app/pages/server-routes/wifi/wifi.page.ts b/ui/src/app/pages/server-routes/wifi/wifi.page.ts index 133a70f72..b92a3f147 100644 --- a/ui/src/app/pages/server-routes/wifi/wifi.page.ts +++ b/ui/src/app/pages/server-routes/wifi/wifi.page.ts @@ -18,7 +18,7 @@ export class WifiListPage { subs: Subscription[] = [] constructor ( - private readonly apiService: ApiService, + private readonly embassyApi: ApiService, private readonly loader: LoaderService, private readonly errToast: ErrorToastService, private readonly actionCtrl: ActionSheetController, @@ -62,7 +62,7 @@ export class WifiListPage { spinner: 'lines', cssClass: 'loader', }).displayDuringAsync(async () => { - await this.apiService.connectWifi({ ssid }) + await this.embassyApi.connectWifi({ ssid }) this.wifiService.confirmWifi(ssid) }).catch(e => { console.error(e) @@ -76,7 +76,7 @@ export class WifiListPage { spinner: 'lines', cssClass: 'loader', }).displayDuringAsync(async () => { - await this.apiService.deleteWifi({ ssid }) + await this.embassyApi.deleteWifi({ ssid }) }).catch(e => { console.error(e) this.errToast.present(e.message) diff --git a/ui/src/app/pipes/status.pipe.ts b/ui/src/app/pipes/status.pipe.ts deleted file mode 100644 index f9fe7eb5e..000000000 --- a/ui/src/app/pipes/status.pipe.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Pipe, PipeTransform } from '@angular/core' -import { combineLatest, Observable } from 'rxjs' -import { map } from 'rxjs/operators' -import { PatchDbService } from '../services/patch-db/patch-db.service' -import { FEStatus, renderPkgStatus } from '../services/pkg-status-rendering.service' - -@Pipe({ - name: 'status', -}) -export class StatusPipe implements PipeTransform { - - constructor ( - private readonly patch: PatchDbService, - ) { } - - transform (pkgId: string): Observable { - return combineLatest([ - this.patch.watch$('package-data', pkgId, 'state'), - this.patch.watch$('package-data', pkgId, 'installed', 'status'), - ]) - .pipe( - map(([state, status]) => { - return renderPkgStatus(state, status).feStatus - }), - ) - } -} \ No newline at end of file diff --git a/ui/src/app/pipes/ui.pipe.ts b/ui/src/app/pipes/ui.pipe.ts index e2d241700..8c2025a09 100644 --- a/ui/src/app/pipes/ui.pipe.ts +++ b/ui/src/app/pipes/ui.pipe.ts @@ -1,6 +1,6 @@ import { Pipe, PipeTransform } from '@angular/core' -import { PackageDataEntry, Manifest } from '../services/patch-db/data-model' -import { ConfigService, getManifest, hasUi } from '../services/config.service' +import { PackageDataEntry } from '../services/patch-db/data-model' +import { ConfigService, hasUi } from '../services/config.service' @Pipe({ name: 'hasUi', @@ -8,7 +8,7 @@ import { ConfigService, getManifest, hasUi } from '../services/config.service' export class HasUiPipe implements PipeTransform { transform (pkg: PackageDataEntry): boolean { - const interfaces = getManifest(pkg).interfaces + const interfaces = pkg.manifest.interfaces return hasUi(interfaces) } } diff --git a/ui/src/app/services/api/api.fixures.ts b/ui/src/app/services/api/api.fixures.ts index c2cc6315f..70c9d03a8 100644 --- a/ui/src/app/services/api/api.fixures.ts +++ b/ui/src/app/services/api/api.fixures.ts @@ -1,4 +1,4 @@ -import { DockerIoFormat, Manifest, PackageDataEntry, PackageMainStatus, PackageState } from 'src/app/services/patch-db/data-model' +import { DependencyErrorType, DockerIoFormat, Manifest, PackageDataEntry, PackageMainStatus, PackageState } from 'src/app/services/patch-db/data-model' import { MarketplacePkg, Metric, NotificationLevel, RR, ServerNotifications } from './api.types' export module Mock { @@ -1347,56 +1347,56 @@ export module Mock { // 'install-progress': undefined, // } - // export const lnd: PackageDataEntry = { - // state: PackageState.Installed, - // 'static-files': { - // license: 'licenseUrl', // /public/package-data/lnd/0.21.1/LICENSE.md, - // icon: 'assets/img/service-icons/lnd.png', - // instructions: 'instructionsUrl', // /public/package-data/lnd/0.21.1/INSTRUCTIONS.md - // }, - // manifest: MockManifestLnd, - // installed: { - // status: { - // configured: true, - // main: { - // status: PackageMainStatus.Stopped, - // }, - // 'dependency-errors': { - // 'bitcoin-proxy': { - // type: DependencyErrorType.NotInstalled, - // title: Mock.MockManifestBitcoinProxy.title, - // icon: 'assets/img/service-icons/bitcoin-proxy.png', - // }, - // }, - // }, - // 'interface-info': { - // ip: '10.0.0.1', - // addresses: { - // rpc: { - // 'tor-address': 'lnd-rpc-address.onion', - // 'lan-address': 'lnd-rpc-address.local', - // }, - // grpc: { - // 'tor-address': 'lnd-grpc-address.onion', - // 'lan-address': 'lnd-grpc-address.local', - // }, - // }, - // }, - // 'system-pointers': [], - // 'current-dependents': { }, - // 'current-dependencies': { - // 'bitcoind': { - // pointers: [], - // 'health-checks': [], - // }, - // 'bitcoin-proxy': { - // pointers: [], - // 'health-checks': [], - // }, - // }, - // }, - // 'install-progress': undefined, - // } + export const lnd: PackageDataEntry = { + state: PackageState.Installed, + 'static-files': { + license: 'licenseUrl', // /public/package-data/lnd/0.21.1/LICENSE.md, + icon: 'assets/img/service-icons/lnd.png', + instructions: 'instructionsUrl', // /public/package-data/lnd/0.21.1/INSTRUCTIONS.md + }, + manifest: MockManifestLnd, + installed: { + status: { + configured: true, + main: { + status: PackageMainStatus.Stopped, + }, + 'dependency-errors': { + 'bitcoin-proxy': { + type: DependencyErrorType.NotInstalled, + title: Mock.MockManifestBitcoinProxy.title, + icon: 'assets/img/service-icons/bitcoin-proxy.png', + }, + }, + }, + 'interface-info': { + ip: '10.0.0.1', + addresses: { + rpc: { + 'tor-address': 'lnd-rpc-address.onion', + 'lan-address': 'lnd-rpc-address.local', + }, + grpc: { + 'tor-address': 'lnd-grpc-address.onion', + 'lan-address': 'lnd-grpc-address.local', + }, + }, + }, + 'system-pointers': [], + 'current-dependents': { }, + 'current-dependencies': { + 'bitcoind': { + pointers: [], + 'health-checks': [], + }, + 'bitcoin-proxy': { + pointers: [], + 'health-checks': [], + }, + }, + }, + 'install-progress': undefined, + } // export const DbDump: RR.GetDumpRes = { // id: 1, diff --git a/ui/src/app/services/api/embassy/embassy-mock-api.service.ts b/ui/src/app/services/api/embassy/embassy-mock-api.service.ts index f384cf0eb..faca35f04 100644 --- a/ui/src/app/services/api/embassy/embassy-mock-api.service.ts +++ b/ui/src/app/services/api/embassy/embassy-mock-api.service.ts @@ -2,7 +2,7 @@ import { Injectable } from '@angular/core' import { pauseFor } from '../../../util/misc.util' import { ApiService } from './embassy-api.service' import { Operation, PatchOp } from 'patch-db-client' -import { PackageDataEntry, PackageMainStatus, PackageState, ServerStatus } from 'src/app/services/patch-db/data-model' +import { DataModel, InstallProgress, PackageDataEntry, PackageMainStatus, PackageState, ServerStatus } from 'src/app/services/patch-db/data-model' import { RR, WithRevision } from '../api.types' import { parsePropertiesPermissive } from 'src/app/util/properties.util' import { Mock } from '../api.fixures' @@ -325,18 +325,19 @@ export class MockApiService extends ApiService { async installPackageRaw (params: RR.InstallPackageReq): Promise { await pauseFor(2000) + const initialProgress: InstallProgress = { + size: 120, + downloaded: 0, + 'download-complete': false, + validated: 0, + 'validation-complete': false, + unpacked: 0, + 'unpack-complete': false, + } const pkg: PackageDataEntry = { - ...Mock.bitcoinproxy, + ...Mock[params.id], state: PackageState.Installing, - 'install-progress': { - size: 100, - downloaded: 10, - 'download-complete': false, - validated: 1, - 'validation-complete': true, - read: 50, - 'read-complete': false, - }, + 'install-progress': initialProgress, } const patch = [ { @@ -345,7 +346,11 @@ export class MockApiService extends ApiService { value: pkg, }, ] - return this.http.rpcRequest>({ method: 'db.patch', params: { patch } }) + const res = await this.http.rpcRequest>({ method: 'db.patch', params: { patch } }) + setTimeout(async () => { + this.updateProgress(params.id, initialProgress) + }, 1000) + return res } async dryUpdatePackage (params: RR.DryUpdatePackageReq): Promise { @@ -483,4 +488,45 @@ export class MockApiService extends ApiService { await pauseFor(2000) return { } } + + private async updateProgress (id: string, initialProgress: InstallProgress) { + const phases = [ + { progress: 'downloaded', completion: 'download-complete'}, + { progress: 'validated', completion: 'validation-complete'}, + { progress: 'unpacked', completion: 'unpack-complete'}, + ] + for (let phase of phases) { + let i = initialProgress[phase.progress] + console.log('PHASE', phase) + console.log('Initial i', i) + while (i < initialProgress.size) { + console.log(i) + await pauseFor(1000) + i = Math.min(i + 40, initialProgress.size) + initialProgress[phase.progress] = i + if (i === initialProgress.size) { + initialProgress[phase.completion] = true + } + const patch = [ + { + op: PatchOp.REPLACE, + path: `/package-data/${id}/install-progress`, + value: initialProgress, + }, + ] + await this.http.rpcRequest>({ method: 'db.patch', params: { patch } }) + } + } + + setTimeout(() => { + const patch = [ + { + op: PatchOp.REPLACE, + path: `/package-data/${id}/state`, + value: PackageState.Installed, + }, + ] + this.http.rpcRequest>({ method: 'db.patch', params: { patch } }) + }, 1000) + } } diff --git a/ui/src/app/services/api/marketplace/marketplace-api.service.ts b/ui/src/app/services/api/marketplace/marketplace-api.service.ts index eeb7f98b5..e8684bb2e 100644 --- a/ui/src/app/services/api/marketplace/marketplace-api.service.ts +++ b/ui/src/app/services/api/marketplace/marketplace-api.service.ts @@ -1,14 +1,28 @@ import { RR } from '../api.types' import { ConfigService } from '../../config.service' import { PatchDbService } from '../../patch-db/patch-db.service' +import { ServerInfo } from '../../patch-db/data-model' +import { AuthState } from '../../auth.service' +import { takeWhile } from 'rxjs/operators' export abstract class MarketplaceApiService { + private server: ServerInfo constructor ( readonly config: ConfigService, readonly patch: PatchDbService, ) { } + init (auth: AuthState) { + this.patch.watch$('server-info') + .pipe( + takeWhile(() => auth === AuthState.VERIFIED), + ) + .subscribe(server => { + this.server = server + }) + } + abstract getEos (params: RR.GetMarketplaceEOSReq): Promise abstract getMarketplaceData (params: RR.GetMarketplaceDataReq): Promise @@ -20,11 +34,11 @@ export abstract class MarketplaceApiService { abstract getLatestVersion (params: RR.GetLatestVersionReq): Promise getMarketplaceURL (type: 'eos' | 'package', defaultToTor = false): string { - const packageMarketplace = this.patch.data['server-info']['package-marketplace'] + const packageMarketplace = this.server['package-marketplace'] if (defaultToTor && !packageMarketplace) { return this.config.start9Marketplace.tor } - const eosMarketplace = this.patch.data['server-info']['eos-marketplace'] || this.config.start9Marketplace.clearnet + const eosMarketplace = this.server['eos-marketplace'] || this.config.start9Marketplace.clearnet if (type === 'eos') { return eosMarketplace } else { diff --git a/ui/src/app/services/auth.service.ts b/ui/src/app/services/auth.service.ts index 15be57add..12136c207 100644 --- a/ui/src/app/services/auth.service.ts +++ b/ui/src/app/services/auth.service.ts @@ -17,7 +17,7 @@ export class AuthService { private readonly authState$: BehaviorSubject = new BehaviorSubject(AuthState.INITIALIZING) constructor ( - private readonly api: ApiService, + private readonly embassyApi: ApiService, private readonly storage: Storage, ) { } @@ -31,7 +31,7 @@ export class AuthService { } async login (password: string): Promise { - await this.api.login({ password }) + await this.embassyApi.login({ password }) await this.storage.set(this.LOGGED_IN_KEY, true) this.authState$.next(AuthState.VERIFIED) } diff --git a/ui/src/app/services/config.service.ts b/ui/src/app/services/config.service.ts index 4f8dd8ed2..1f2727448 100644 --- a/ui/src/app/services/config.service.ts +++ b/ui/src/app/services/config.service.ts @@ -101,13 +101,6 @@ export function hasUi (interfaces: { [id: string]: InterfaceDef }): boolean { return hasTorUi(interfaces) || hasLanUi(interfaces) } -export function getManifest (pkg: PackageDataEntry): Manifest { - if (pkg.state === PackageState.Installed) { - return pkg.manifest - } - return pkg['temp-manifest'] -} - function removeProtocol (str: string): string { if (str.startsWith('http://')) return str.slice(7) if (str.startsWith('https://')) return str.slice(8) diff --git a/ui/src/app/services/connection.service.ts b/ui/src/app/services/connection.service.ts index 351e0a2ff..cc2b35454 100644 --- a/ui/src/app/services/connection.service.ts +++ b/ui/src/app/services/connection.service.ts @@ -2,8 +2,9 @@ import { Injectable } from '@angular/core' import { BehaviorSubject, combineLatest, fromEvent, merge, Subscription } from 'rxjs' import { ConnectionStatus, PatchDbService } from './patch-db/patch-db.service' import { HttpService, Method } from './http.service' -import { distinctUntilChanged } from 'rxjs/operators' +import { distinctUntilChanged, takeWhile } from 'rxjs/operators' import { ConfigService } from './config.service' +import { AuthState } from './auth.service' @Injectable({ providedIn: 'root', @@ -23,16 +24,35 @@ export class ConnectionService { return this.connectionFailure$.asObservable() } - start () { + start (auth: AuthState) { this.subs = [ merge(fromEvent(window, 'online'), fromEvent(window, 'offline')) + .pipe( + takeWhile(() => auth === AuthState.VERIFIED), + ) .subscribe(event => { this.networkState$.next(event.type === 'online') }), - combineLatest([this.networkState$.pipe(distinctUntilChanged()), this.patch.watchConnection$().pipe(distinctUntilChanged())]) - .subscribe(async ([network, connectionStatus]) => { - const addrs = this.patch.data['server-info']['connection-addresses'] + combineLatest([ + // 1 + this.networkState$ + .pipe( + distinctUntilChanged(), + ), + // 2 + this.patch.watchConnection$() + .pipe( + distinctUntilChanged(), + ), + // 3 + this.patch.watch$('server-info', 'connection-addresses') + .pipe( + takeWhile(() => auth === AuthState.VERIFIED), + distinctUntilChanged(), + ), + ]) + .subscribe(async ([network, connectionStatus, addrs]) => { if (connectionStatus !== ConnectionStatus.Disconnected) { this.connectionFailure$.next(ConnectionFailure.None) } else if (!network) { @@ -61,13 +81,6 @@ export class ConnectionService { ] } - stop () { - this.subs.forEach(sub => { - sub.unsubscribe() - }) - this.subs = [] - } - private async testAddrs (addrs: string[]): Promise { if (!addrs.length) return true diff --git a/ui/src/app/services/http.service.ts b/ui/src/app/services/http.service.ts index e7c554b99..63a9fbb8c 100644 --- a/ui/src/app/services/http.service.ts +++ b/ui/src/app/services/http.service.ts @@ -70,6 +70,12 @@ export class HttpService { this.fullUrl + httpOpts.url : httpOpts.url + Object.keys(httpOpts.params).forEach(key => { + if (httpOpts.params[key] === undefined) { + delete httpOpts.params[key] + } + }) + return { observe: 'events', responseType: 'json', diff --git a/ui/src/app/services/patch-db/data-model.ts b/ui/src/app/services/patch-db/data-model.ts index 63b1f1e98..05f66bbe2 100644 --- a/ui/src/app/services/patch-db/data-model.ts +++ b/ui/src/app/services/patch-db/data-model.ts @@ -62,8 +62,8 @@ export interface InstallProgress { 'download-complete': boolean validated: number 'validation-complete': boolean - read: number - 'read-complete': boolean + unpacked: number + 'unpack-complete': boolean } export interface InstalledPackageDataEntry { diff --git a/ui/src/app/services/patch-db/patch-db.factory.ts b/ui/src/app/services/patch-db/patch-db.factory.ts index 068a0fd25..c58ce7f2b 100644 --- a/ui/src/app/services/patch-db/patch-db.factory.ts +++ b/ui/src/app/services/patch-db/patch-db.factory.ts @@ -8,7 +8,7 @@ import { ApiService } from 'src/app/services/api/embassy/embassy-api.service' export function PatchDbServiceFactory ( config: ConfigService, bootstrapper: LocalStorageBootstrap, - apiService: ApiService, + embassyApi: ApiService, ): PatchDbService { const { mocks, patchDb: { poll }, isConsulate } = config @@ -17,13 +17,13 @@ export function PatchDbServiceFactory ( if (mocks.enabled) { if (mocks.connection === 'poll') { - source = new PollSource({ ...poll }, apiService) + source = new PollSource({ ...poll }, embassyApi) } else { source = new WebsocketSource(`ws://localhost:${config.mocks.wsPort}/db`) } } else { if (isConsulate) { - source = new PollSource({ ...poll }, apiService) + source = new PollSource({ ...poll }, embassyApi) } else { const protocol = window.location.protocol === 'http:' ? 'ws' : 'wss' const host = window.location.host @@ -31,5 +31,5 @@ export function PatchDbServiceFactory ( } } - return new PatchDbService(source, apiService, bootstrapper) + return new PatchDbService(source, embassyApi, bootstrapper) } \ No newline at end of file diff --git a/ui/src/app/services/patch-db/patch-db.service.ts b/ui/src/app/services/patch-db/patch-db.service.ts index a703ee08d..47649cadf 100644 --- a/ui/src/app/services/patch-db/patch-db.service.ts +++ b/ui/src/app/services/patch-db/patch-db.service.ts @@ -20,10 +20,11 @@ export enum ConnectionStatus { }) export class PatchDbService { connectionStatus$ = new BehaviorSubject(ConnectionStatus.Initializing) - data: DataModel private patchDb: PatchDB private patchSub: Subscription + get data () { return this.patchDb.store.cache.data } + constructor ( @Inject(PATCH_SOURCE) private readonly source: Source, @Inject(PATCH_HTTP) private readonly http: ApiService, @@ -33,7 +34,6 @@ export class PatchDbService { async init (): Promise { const cache = await this.bootstrapper.init() this.patchDb = new PatchDB([this.source, this.http], this.http, cache) - this.data = this.patchDb.store.cache.data } start (): void { @@ -44,7 +44,7 @@ export class PatchDbService { .pipe(debounceTime(500)) .subscribe({ next: cache => { - console.log('saving cacheee: ', cache) + console.log('saving cacheee: ', JSON.parse(JSON.stringify(cache))) this.connectionStatus$.next(ConnectionStatus.Connected) this.bootstrapper.update(cache) }, @@ -82,8 +82,9 @@ export class PatchDbService { watch$: Store['watch$'] = (...args: (string | number)[]): Observable => { console.log('WATCHING', ...args) - return this.patchDb.store.watch$(...(args as [])).pipe( - tap(cache => console.log('CHANGE IN STORE', cache)), + return this.patchDb.store.watch$(...(args as [])) + .pipe( + tap(data => console.log('CHANGE IN STORE', data, ...args)), catchError(e => { console.error(e) return of(e.message) diff --git a/ui/src/app/services/server-config.service.ts b/ui/src/app/services/server-config.service.ts index 8ef590047..302e56d72 100644 --- a/ui/src/app/services/server-config.service.ts +++ b/ui/src/app/services/server-config.service.ts @@ -13,7 +13,7 @@ export class ServerConfigService { constructor ( private readonly trackingModalCtrl: TrackingModalController, - private readonly apiService: ApiService, + private readonly embassyApi: ApiService, private readonly sshService: SSHService, ) { } @@ -35,19 +35,19 @@ export class ServerConfigService { saveFns: { [key: string]: (val: any) => Promise } = { autoCheckUpdates: async (value: boolean) => { - return this.apiService.setDbValue({ pointer: 'ui/auto-check-updates', value }) + return this.embassyApi.setDbValue({ pointer: 'ui/auto-check-updates', value }) }, ssh: async (pubkey: string) => { return this.sshService.add(pubkey) }, eosMarketplace: async (enabled: boolean) => { - return this.apiService.setEosMarketplace(enabled) + return this.embassyApi.setEosMarketplace(enabled) }, // packageMarketplace: async (url: string) => { - // return this.apiService.setPackageMarketplace({ url }) + // return this.embassyApi.setPackageMarketplace({ url }) // }, // password: async (password: string) => { - // return this.apiService.updatePassword({ password }) + // return this.embassyApi.updatePassword({ password }) // }, } } diff --git a/ui/src/app/services/startup-alerts.service.ts b/ui/src/app/services/startup-alerts.service.ts index 9e9128103..64af9f4c3 100644 --- a/ui/src/app/services/startup-alerts.service.ts +++ b/ui/src/app/services/startup-alerts.service.ts @@ -5,17 +5,21 @@ import { WizardBaker } from '../components/install-wizard/prebaked-wizards' import { OSWelcomePage } from '../modals/os-welcome/os-welcome.page' import { displayEmver } from '../pipes/emver.pipe' import { RR } from './api/api.types' -import { PatchDbService } from './patch-db/patch-db.service' import { ConfigService } from './config.service' import { Emver } from './emver.service' import { MarketplaceService } from '../pages/marketplace-routes/marketplace.service' import { MarketplaceApiService } from './api/marketplace/marketplace-api.service' +import { DataModel, PackageDataEntry } from './patch-db/data-model' +import { PatchDbService } from './patch-db/patch-db.service' +import { filter, take } from 'rxjs/operators' +import { isEmptyObject } from '../util/misc.util' @Injectable({ providedIn: 'root', }) export class StartupAlertsService { private checks: Check[] + data: DataModel constructor ( private readonly alertCtrl: AlertController, @@ -57,7 +61,14 @@ export class StartupAlertsService { // Each promise fires more or less concurrently, so each c.check(server) is run concurrently // Then, since we await previousDisplay before c.display(res), each promise executing gets hung awaiting the display of the previous run async runChecks (): Promise { - await this.checks + this.patch.watch$() + .pipe( + filter(data => !isEmptyObject(data)), + take(1), + ) + .subscribe(async data => { + this.data = data + await this.checks .filter(c => !c.hasRun && c.shouldRun()) // returning true in the below block means to continue to next modal // returning false means to skip all subsequent modals @@ -76,18 +87,19 @@ export class StartupAlertsService { if (!checkRes) return true if (displayRes) return c.display(checkRes) }, Promise.resolve(true)) + }) } private shouldRunOsWelcome (): boolean { - return this.patch.data.ui['welcome-ack'] !== this.config.version + return this.data.ui['welcome-ack'] !== this.config.version } private shouldRunOsUpdateCheck (): boolean { - return this.patch.data.ui['auto-check-updates'] + return this.data.ui['auto-check-updates'] } private shouldRunAppsCheck (): boolean { - return this.patch.data.ui['auto-check-updates'] + return this.data.ui['auto-check-updates'] } private async osUpdateCheck (): Promise { @@ -101,8 +113,8 @@ export class StartupAlertsService { } private async appsCheck (): Promise { - const updates = await this.marketplaceService.getUpdates(this.patch.data['package-data']) - return !!updates.length + await this.marketplaceService.getUpdates(this.data['package-data']) + return !!this.marketplaceService.updates.length } private async displayOsWelcome (): Promise {