mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 12:11:56 +00:00
more efficient subscriptions, more styling
This commit is contained in:
committed by
Aiden McClelland
parent
330d5a08af
commit
da3aa0a2a7
@@ -1,9 +1,9 @@
|
|||||||
<ion-app>
|
<ion-app>
|
||||||
<ion-split-pane *ngIf="patch.patchDb?.store" [disabled]="!showMenu" (ionSplitPaneVisible)="splitPaneVisible($event)" contentId="main-content">
|
<ion-split-pane [disabled]="!showMenu" (ionSplitPaneVisible)="splitPaneVisible($event)" contentId="main-content">
|
||||||
<ion-menu contentId="main-content" type="overlay">
|
<ion-menu contentId="main-content" type="overlay">
|
||||||
<ion-header>
|
<ion-header>
|
||||||
<ion-toolbar style="--background: var(--ion-background-color);">
|
<ion-toolbar style="--background: var(--ion-background-color);">
|
||||||
<ion-title *ngIf="patch.watch$('ui', 'server-name') | ngrxPush as name; else dots">{{ name }}</ion-title>
|
<ion-title *ngIf="serverName; else dots">{{ serverName }}</ion-title>
|
||||||
<ng-template #dots>
|
<ng-template #dots>
|
||||||
<ion-title><ion-spinner name="dots" color="warning"></ion-spinner></ion-title>
|
<ion-title><ion-spinner name="dots" color="warning"></ion-spinner></ion-title>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
>
|
>
|
||||||
<ion-icon slot="start" [name]="page.icon"></ion-icon>
|
<ion-icon slot="start" [name]="page.icon"></ion-icon>
|
||||||
<ion-label style="font-family: 'Montserrat';">{{ page.title }}</ion-label>
|
<ion-label style="font-family: 'Montserrat';">{{ page.title }}</ion-label>
|
||||||
<ion-badge *ngIf="page.url === '/notifications' && (patch.watch$('server-info', 'unread-notification-count') | ngrxPush) as badge" color="danger" style="margin-right: 3%;" [class.selected-badge]="selectedIndex == i">{{ badge }}</ion-badge>
|
<ion-badge *ngIf="page.url === '/notifications' && unreadCount" color="danger" style="margin-right: 3%;" [class.selected-badge]="selectedIndex == i">{{ unreadCount }}</ion-badge>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ion-menu-toggle>
|
</ion-menu-toggle>
|
||||||
</ion-list>
|
</ion-list>
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { PatchDbModel } from './models/patch-db/patch-db-model'
|
|||||||
import { HttpService } from './services/http.service'
|
import { HttpService } from './services/http.service'
|
||||||
import { ServerStatus } from './models/patch-db/data-model'
|
import { ServerStatus } from './models/patch-db/data-model'
|
||||||
import { ConnectionFailure, ConnectionService } from './services/connection.service'
|
import { ConnectionFailure, ConnectionService } from './services/connection.service'
|
||||||
|
import { combineLatest, merge } from 'rxjs'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
@@ -23,8 +24,9 @@ export class AppComponent {
|
|||||||
ServerStatus = ServerStatus
|
ServerStatus = ServerStatus
|
||||||
showMenu = false
|
showMenu = false
|
||||||
selectedIndex = 0
|
selectedIndex = 0
|
||||||
untilLoaded = true
|
|
||||||
offlineToast: HTMLIonToastElement
|
offlineToast: HTMLIonToastElement
|
||||||
|
serverName: string
|
||||||
|
unreadCount: number
|
||||||
appPages = [
|
appPages = [
|
||||||
{
|
{
|
||||||
title: 'Services',
|
title: 'Services',
|
||||||
@@ -59,8 +61,8 @@ export class AppComponent {
|
|||||||
private readonly emver: Emver,
|
private readonly emver: Emver,
|
||||||
private readonly connectionService: ConnectionService,
|
private readonly connectionService: ConnectionService,
|
||||||
private readonly toastCtrl: ToastController,
|
private readonly toastCtrl: ToastController,
|
||||||
|
private readonly patch: PatchDbModel,
|
||||||
readonly splitPane: SplitPaneTracker,
|
readonly splitPane: SplitPaneTracker,
|
||||||
readonly patch: PatchDbModel,
|
|
||||||
) {
|
) {
|
||||||
// set dark theme
|
// set dark theme
|
||||||
document.body.classList.toggle('dark', true)
|
document.body.classList.toggle('dark', true)
|
||||||
@@ -83,8 +85,10 @@ export class AppComponent {
|
|||||||
this.http.authReqEnabled = true
|
this.http.authReqEnabled = true
|
||||||
this.showMenu = true
|
this.showMenu = true
|
||||||
this.patch.start()
|
this.patch.start()
|
||||||
|
// watch patch DB to display name and unread count
|
||||||
|
this.watchPatch()
|
||||||
this.connectionService.start()
|
this.connectionService.start()
|
||||||
// watch network
|
// watch connection to display connectivity issues
|
||||||
this.watchConnection(auth)
|
this.watchConnection(auth)
|
||||||
// watch router to highlight selected menu item
|
// watch router to highlight selected menu item
|
||||||
this.watchRouter(auth)
|
this.watchRouter(auth)
|
||||||
@@ -108,6 +112,17 @@ export class AppComponent {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
watchPatch (): void {
|
||||||
|
combineLatest([
|
||||||
|
this.patch.watch$('ui', 'server-name'),
|
||||||
|
this.patch.watch$('server-info', 'unread-notification-count'),
|
||||||
|
])
|
||||||
|
.subscribe(([name, unread]) => {
|
||||||
|
this.serverName = name
|
||||||
|
this.unreadCount = unread
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
private watchConnection (auth: AuthState): void {
|
private watchConnection (auth: AuthState): void {
|
||||||
this.connectionService.watch$()
|
this.connectionService.watch$()
|
||||||
.pipe(
|
.pipe(
|
||||||
@@ -265,7 +280,7 @@ export class AppComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
splitPaneVisible (e: any) {
|
splitPaneVisible (e: any) {
|
||||||
this.splitPane.menuFixedOpenOnLeft$.next(e.detail.visible)
|
this.splitPane.sidebarOpen$.next(e.detail.visible)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<div style="position: relative; margin-right: 1vh;">
|
<div style="position: relative; margin-right: 1vh;">
|
||||||
<ion-badge mode="md" class="md-badge" *ngIf="(badge$ | ngrxPush) && !(menuFixedOpen$ | ngrxPush)" color="danger">{{ badge$ | ngrxPush }}</ion-badge>
|
<ion-badge mode="md" class="md-badge" *ngIf="unreadCount && !sidebarOpen" color="danger">{{ unreadCount }}</ion-badge>
|
||||||
<ion-menu-button color="dark"></ion-menu-button>
|
<ion-menu-button color="dark"></ion-menu-button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Component } from '@angular/core'
|
import { Component } from '@angular/core'
|
||||||
import { Observable } from 'rxjs'
|
|
||||||
import { SplitPaneTracker } from 'src/app/services/split-pane.service'
|
import { SplitPaneTracker } from 'src/app/services/split-pane.service'
|
||||||
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
|
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
|
||||||
|
import { combineLatest, Subscription } from 'rxjs'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'badge-menu-button',
|
selector: 'badge-menu-button',
|
||||||
@@ -10,14 +10,30 @@ import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
|
|||||||
})
|
})
|
||||||
|
|
||||||
export class BadgeMenuComponent {
|
export class BadgeMenuComponent {
|
||||||
badge$: Observable<number>
|
unreadCount: number
|
||||||
menuFixedOpen$: Observable<boolean>
|
sidebarOpen: boolean
|
||||||
|
|
||||||
|
subs: Subscription[] = []
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private readonly splitPane: SplitPaneTracker,
|
private readonly splitPane: SplitPaneTracker,
|
||||||
private readonly patch: PatchDbModel,
|
private readonly patch: PatchDbModel,
|
||||||
) {
|
) { }
|
||||||
this.menuFixedOpen$ = this.splitPane.menuFixedOpenOnLeft$.asObservable()
|
|
||||||
this.badge$ = this.patch.watch$('server-info', 'unread-notification-count')
|
ngOnInit () {
|
||||||
|
this.subs = [
|
||||||
|
combineLatest([
|
||||||
|
this.patch.watch$('server-info', 'unread-notification-count'),
|
||||||
|
this.splitPane.sidebarOpen$,
|
||||||
|
])
|
||||||
|
.subscribe(([unread, menu]) => {
|
||||||
|
this.unreadCount = unread
|
||||||
|
this.sidebarOpen = menu
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy () {
|
||||||
|
this.subs.forEach(sub => sub.unsubscribe())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
<ion-content>
|
<ion-content>
|
||||||
<ion-slides *ngIf="!(error$ | ngrxPush)" id="slide-show" style="--bullet-background: white" pager="false">
|
<ion-slides *ngIf="!error" id="slide-show" style="--bullet-background: white" pager="false">
|
||||||
<ion-slide *ngFor="let def of params.slideDefinitions">
|
<ion-slide *ngFor="let def of params.slideDefinitions">
|
||||||
<!-- We can pass [transitions]="transitions" into the component if logic within the component needs to trigger a transition (not just bottom bar) -->
|
<!-- We can pass [transitions]="transitions" into the component if logic within the component needs to trigger a transition (not just bottom bar) -->
|
||||||
<dependencies #components *ngIf="def.slide.selector === 'dependencies'" [params]="def.slide.params"></dependencies>
|
<dependencies #components *ngIf="def.slide.selector === 'dependencies'" [params]="def.slide.params"></dependencies>
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
</ion-slides>
|
</ion-slides>
|
||||||
|
|
||||||
|
|
||||||
<div *ngIf="error$ | ngrxPush as error" class="slide-content">
|
<div *ngIf="error" class="slide-content">
|
||||||
<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="danger" style="font-size: xx-large; font-weight: bold;">
|
<ion-label color="danger" style="font-size: xx-large; font-weight: bold;">
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
|
|
||||||
<ion-footer>
|
<ion-footer>
|
||||||
<ion-toolbar style="padding: 8px;">
|
<ion-toolbar style="padding: 8px;">
|
||||||
<ng-container *ngIf="!(initializing$ | ngrxPush) && !(error$ | ngrxPush) && { loading: currentSlideloading$ | ngrxPush, bar: currentBottomBar} as v">
|
<ng-container *ngIf="!initializing && !error">
|
||||||
|
|
||||||
<!-- cancel button if loading/not loading -->
|
<!-- cancel button if loading/not loading -->
|
||||||
<ion-button slot="start" *ngIf="v.loading && v.bar.cancel.whileLoading as cancel" (click)="transitions.cancel()" class="toolbar-button" fill="outline" color="medium">
|
<ion-button slot="start" *ngIf="v.loading && v.bar.cancel.whileLoading as cancel" (click)="transitions.cancel()" class="toolbar-button" fill="outline" color="medium">
|
||||||
@@ -58,7 +58,7 @@
|
|||||||
</ion-button>
|
</ion-button>
|
||||||
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container *ngIf="error$ | ngrxPush">
|
<ng-container *ngIf="error">
|
||||||
<ion-button slot="start" (click)="transitions.final()" style="text-transform: capitalize; font-weight: bolder;" color="danger">Dismiss</ion-button>
|
<ion-button slot="start" (click)="transitions.final()" style="text-transform: capitalize; font-weight: bolder;" color="danger">Dismiss</ion-button>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { Component, Input, NgZone, QueryList, ViewChild, ViewChildren } from '@angular/core'
|
import { Component, Input, NgZone, QueryList, ViewChild, ViewChildren } from '@angular/core'
|
||||||
import { IonContent, IonSlides, ModalController } from '@ionic/angular'
|
import { IonContent, IonSlides, ModalController } from '@ionic/angular'
|
||||||
import { BehaviorSubject } from 'rxjs'
|
|
||||||
import { capitalizeFirstLetter, pauseFor } from 'src/app/util/misc.util'
|
import { capitalizeFirstLetter, pauseFor } from 'src/app/util/misc.util'
|
||||||
import { CompleteComponent } from './complete/complete.component'
|
import { CompleteComponent } from './complete/complete.component'
|
||||||
import { DependentsComponent } from './dependents/dependents.component'
|
import { DependentsComponent } from './dependents/dependents.component'
|
||||||
@@ -39,8 +38,8 @@ export class InstallWizardComponent {
|
|||||||
return this.params.slideDefinitions[this.slideIndex].bottomBar
|
return this.params.slideDefinitions[this.slideIndex].bottomBar
|
||||||
}
|
}
|
||||||
|
|
||||||
initializing$ = new BehaviorSubject(true)
|
initializing = true
|
||||||
error$ = new BehaviorSubject(undefined)
|
error = ''
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private readonly modalController: ModalController,
|
private readonly modalController: ModalController,
|
||||||
@@ -54,7 +53,7 @@ export class InstallWizardComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ionViewDidEnter () {
|
ionViewDidEnter () {
|
||||||
this.initializing$.next(false)
|
this.initializing = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// process bottom bar buttons
|
// process bottom bar buttons
|
||||||
@@ -62,7 +61,7 @@ export class InstallWizardComponent {
|
|||||||
const i = info as { next?: any, error?: Error, cancelled?: true, final?: true }
|
const i = info as { next?: any, error?: Error, cancelled?: true, final?: true }
|
||||||
if (i.cancelled) this.currentSlide.cancel$.next()
|
if (i.cancelled) this.currentSlide.cancel$.next()
|
||||||
if (i.final || i.cancelled) return this.modalController.dismiss(i)
|
if (i.final || i.cancelled) return this.modalController.dismiss(i)
|
||||||
if (i.error) return this.error$.next(capitalizeFirstLetter(i.error.message))
|
if (i.error) return this.error = capitalizeFirstLetter(i.error.message)
|
||||||
|
|
||||||
this.moveToNextSlide(i.next)
|
this.moveToNextSlide(i.next)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,153 +0,0 @@
|
|||||||
// import { MapSubject, Delta, Update } from '../util/map-subject.util'
|
|
||||||
// import { diff, partitionArray } from '../util/misc.util'
|
|
||||||
// import { Injectable } from '@angular/core'
|
|
||||||
// import { merge, Observable, of } from 'rxjs'
|
|
||||||
// import { filter, throttleTime, delay, pairwise, mapTo, take } from 'rxjs/operators'
|
|
||||||
// import { Storage } from '@ionic/storage'
|
|
||||||
// import { StorageKeys } from './storage-keys'
|
|
||||||
// import { AppInstalledFull, AppInstalledPreview } from './app-types'
|
|
||||||
|
|
||||||
// @Injectable({
|
|
||||||
// providedIn: 'root',
|
|
||||||
// })
|
|
||||||
// export class AppModel extends MapSubject<AppInstalledFull> {
|
|
||||||
// // hasLoaded tells us if we've successfully queried apps from api or storage, even if there are none.
|
|
||||||
// hasLoaded = false
|
|
||||||
// lastUpdatedAt: { [id: string]: Date } = { }
|
|
||||||
// constructor (private readonly storage: Storage) {
|
|
||||||
// super()
|
|
||||||
// // 500ms after first delta, will save to db. Subsequent deltas are ignored for those 500ms.
|
|
||||||
// // Process continues as long as deltas fire.
|
|
||||||
// this.watchDelta().pipe(throttleTime(200), delay(200)).subscribe(() => {
|
|
||||||
// this.commitCache()
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
|
|
||||||
// update (newValues: Update<AppInstalledFull>, timestamp: Date = new Date()): void {
|
|
||||||
// this.lastUpdatedAt[newValues.id] = this.lastUpdatedAt[newValues.id] || timestamp
|
|
||||||
// if (this.lastUpdatedAt[newValues.id] > timestamp) {
|
|
||||||
// return
|
|
||||||
// } else {
|
|
||||||
// super.update(newValues)
|
|
||||||
// this.lastUpdatedAt[newValues.id] = timestamp
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // client fxns
|
|
||||||
// watchDelta (filterFor?: Delta<AppInstalledFull>['action']): Observable<Delta<AppInstalledFull>> {
|
|
||||||
// return filterFor
|
|
||||||
// ? this.$delta$.pipe(filter(d => d.action === filterFor))
|
|
||||||
// : this.$delta$.asObservable()
|
|
||||||
// }
|
|
||||||
|
|
||||||
// watch (appId: string) : PropertySubject<AppInstalledFull> {
|
|
||||||
// const toReturn = super.watch(appId)
|
|
||||||
// if (!toReturn) throw new Error(`Expected Service ${appId} but not found.`)
|
|
||||||
// return toReturn
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // when an app is installing
|
|
||||||
// watchForInstallation (appId: string): Observable<string | undefined> {
|
|
||||||
// const toWatch = super.watch(appId)
|
|
||||||
// if (!toWatch) return of(undefined)
|
|
||||||
|
|
||||||
// return toWatch.status.pipe(
|
|
||||||
// filter(s => s !== AppStatus.UNREACHABLE && s !== AppStatus.UNKNOWN),
|
|
||||||
// pairwise(),
|
|
||||||
// filter( ([old, _]) => old === AppStatus.INSTALLING ),
|
|
||||||
// take(1),
|
|
||||||
// mapTo(appId),
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // TODO: EJECT-DISKS: we can use this to watch for an app completing its backup process.
|
|
||||||
// watchForBackup (appId: string): Observable<string | undefined> {
|
|
||||||
// const toWatch = super.watch(appId)
|
|
||||||
// if (!toWatch) return of(undefined)
|
|
||||||
|
|
||||||
// return toWatch.status.pipe(
|
|
||||||
// filter(s => s !== AppStatus.UNREACHABLE && s !== AppStatus.UNKNOWN),
|
|
||||||
// pairwise(),
|
|
||||||
// filter( ([old, _]) => old === AppStatus.CREATING_BACKUP),
|
|
||||||
// take(1),
|
|
||||||
// mapTo(appId),
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
|
|
||||||
// watchForInstallations (appIds: { id: string }[]): Observable<string> {
|
|
||||||
// return merge(...appIds.map(({ id }) => this.watchForInstallation(id))).pipe(
|
|
||||||
// filter(t => !!t),
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // cache mgmt
|
|
||||||
// clear (): void {
|
|
||||||
// this.ids.forEach(id => {
|
|
||||||
// complete(this.contents[id] || { } as PropertySubject<any>)
|
|
||||||
// delete this.contents[id]
|
|
||||||
// })
|
|
||||||
// this.hasLoaded = false
|
|
||||||
// this.contents = { }
|
|
||||||
// this.lastUpdatedAt = { }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// private commitCache (): Promise<void> {
|
|
||||||
// return this.storage.set(StorageKeys.APPS_CACHE_KEY, this.all || [])
|
|
||||||
// }
|
|
||||||
|
|
||||||
// async restoreCache (): Promise<void> {
|
|
||||||
// const stored = await this.storage.get(StorageKeys.APPS_CACHE_KEY)
|
|
||||||
// console.log(`restored app cache`, stored)
|
|
||||||
// if (stored) this.hasLoaded = true
|
|
||||||
// return (stored || []).map(c => this.add({ ...emptyAppInstalledFull(), ...c, status: AppStatus.UNKNOWN }))
|
|
||||||
// }
|
|
||||||
|
|
||||||
// upsertAppFull (app: AppInstalledFull): void {
|
|
||||||
// this.update(app)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // synchronizers
|
|
||||||
// upsertApps (apps: AppInstalledPreview[], timestamp: Date): void {
|
|
||||||
// const [updates, creates] = partitionArray(apps, a => !!this.contents[a.id])
|
|
||||||
// updates.map(u => this.update(u, timestamp))
|
|
||||||
// creates.map(c => this.add({ ...emptyAppInstalledFull(), ...c }))
|
|
||||||
// }
|
|
||||||
|
|
||||||
// syncCache (upToDateApps : AppInstalledPreview[], timestamp: Date) {
|
|
||||||
// this.hasLoaded = true
|
|
||||||
// this.deleteNonexistentApps(upToDateApps)
|
|
||||||
// this.upsertApps(upToDateApps, timestamp)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// private deleteNonexistentApps (apps: AppInstalledPreview[]): void {
|
|
||||||
// const currentAppIds = apps.map(a => a.id)
|
|
||||||
// const previousAppIds = Object.keys(this.contents)
|
|
||||||
// const appsToDelete = diff(previousAppIds, currentAppIds)
|
|
||||||
// appsToDelete.map(appId => this.delete(appId))
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // server state change
|
|
||||||
// markAppsUnreachable (): void {
|
|
||||||
// this.updateAllApps({ status: AppStatus.UNREACHABLE })
|
|
||||||
// }
|
|
||||||
|
|
||||||
// markAppsUnknown (): void {
|
|
||||||
// this.updateAllApps({ status: AppStatus.UNKNOWN })
|
|
||||||
// }
|
|
||||||
|
|
||||||
// private updateAllApps (uniformUpdate: Partial<AppInstalledFull>) {
|
|
||||||
// this.ids.map(id => {
|
|
||||||
// this.update(Object.assign(uniformUpdate, { id }))
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// function emptyAppInstalledFull (): Omit<AppInstalledFull, keyof AppInstalledPreview> {
|
|
||||||
// return {
|
|
||||||
// instructions: null,
|
|
||||||
// lastBackup: null,
|
|
||||||
// configuredRequirements: null,
|
|
||||||
// hasFetchedFull: false,
|
|
||||||
// actions: [],
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
@@ -3,11 +3,7 @@ import { ConfigSpec } from 'src/app/pkg-config/config-types'
|
|||||||
export interface DataModel {
|
export interface DataModel {
|
||||||
'server-info': ServerInfo
|
'server-info': ServerInfo
|
||||||
'package-data': { [id: string]: PackageDataEntry }
|
'package-data': { [id: string]: PackageDataEntry }
|
||||||
ui: {
|
ui: UIData
|
||||||
'server-name': string
|
|
||||||
'welcome-ack': string
|
|
||||||
'auto-check-updates': boolean
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ServerInfo {
|
export interface ServerInfo {
|
||||||
@@ -381,3 +377,9 @@ export interface InterfaceInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type URL = string
|
export type URL = string
|
||||||
|
|
||||||
|
export interface UIData {
|
||||||
|
'server-name': string
|
||||||
|
'welcome-ack': string
|
||||||
|
'auto-check-updates': boolean
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { EmverComparesPipe, EmverSatisfiesPipe, EmverDisplayPipe } from '../pipe
|
|||||||
import { IncludesPipe } from '../pipes/includes.pipe'
|
import { IncludesPipe } from '../pipes/includes.pipe'
|
||||||
import { TypeofPipe } from '../pipes/typeof.pipe'
|
import { TypeofPipe } from '../pipes/typeof.pipe'
|
||||||
import { MarkdownPipe } from '../pipes/markdown.pipe'
|
import { MarkdownPipe } from '../pipes/markdown.pipe'
|
||||||
// import { InstalledLatestComparisonPipe, InstalledViewingComparisonPipe } from '../pipes/installed-latest-comparison.pipe'
|
|
||||||
import { AnnotationStatusPipe } from '../pipes/annotation-status.pipe'
|
import { AnnotationStatusPipe } from '../pipes/annotation-status.pipe'
|
||||||
import { TruncateCenterPipe, TruncateEndPipe } from '../pipes/truncate.pipe'
|
import { TruncateCenterPipe, TruncateEndPipe } from '../pipes/truncate.pipe'
|
||||||
import { MaskPipe } from '../pipes/mask.pipe'
|
import { MaskPipe } from '../pipes/mask.pipe'
|
||||||
@@ -21,8 +20,6 @@ import { ReactiveComponentModule } from '@ngrx/component'
|
|||||||
TypeofPipe,
|
TypeofPipe,
|
||||||
IncludesPipe,
|
IncludesPipe,
|
||||||
MarkdownPipe,
|
MarkdownPipe,
|
||||||
// InstalledLatestComparisonPipe,
|
|
||||||
// InstalledViewingComparisonPipe,
|
|
||||||
AnnotationStatusPipe,
|
AnnotationStatusPipe,
|
||||||
TruncateCenterPipe,
|
TruncateCenterPipe,
|
||||||
TruncateEndPipe,
|
TruncateEndPipe,
|
||||||
@@ -45,8 +42,6 @@ import { ReactiveComponentModule } from '@ngrx/component'
|
|||||||
TypeofPipe,
|
TypeofPipe,
|
||||||
IncludesPipe,
|
IncludesPipe,
|
||||||
MarkdownPipe,
|
MarkdownPipe,
|
||||||
// InstalledLatestComparisonPipe,
|
|
||||||
// InstalledViewingComparisonPipe,
|
|
||||||
AnnotationStatusPipe,
|
AnnotationStatusPipe,
|
||||||
TruncateEndPipe,
|
TruncateEndPipe,
|
||||||
TruncateCenterPipe,
|
TruncateCenterPipe,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
<ion-content class="ion-padding-top">
|
<ion-content class="ion-padding-top">
|
||||||
<ng-container *ngIf="patch.watch$('package-data', pkgId, 'installed') | ngrxPush as installed">
|
<ng-container *ngIf="installed">
|
||||||
<ion-item-group>
|
<ion-item-group>
|
||||||
<ion-item button *ngFor="let action of installed.manifest.actions | keyvalue: asIsOrder" (click)="handleAction(installed, action)" >
|
<ion-item button *ngFor="let action of installed.manifest.actions | keyvalue: asIsOrder" (click)="handleAction(installed, action)" >
|
||||||
<ion-label class="ion-text-wrap">
|
<ion-label class="ion-text-wrap">
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
import { Component } from '@angular/core'
|
import { Component, ViewChild } from '@angular/core'
|
||||||
import { ActivatedRoute } from '@angular/router'
|
import { ActivatedRoute } from '@angular/router'
|
||||||
import { ApiService } from 'src/app/services/api/api.service'
|
import { ApiService } from 'src/app/services/api/api.service'
|
||||||
import { AlertController, ModalController, NavController } from '@ionic/angular'
|
import { AlertController, IonContent, ModalController, NavController } from '@ionic/angular'
|
||||||
import { LoaderService } from 'src/app/services/loader.service'
|
import { LoaderService } from 'src/app/services/loader.service'
|
||||||
import { HttpErrorResponse } from '@angular/common/http'
|
import { HttpErrorResponse } from '@angular/common/http'
|
||||||
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
|
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
|
||||||
import { Action, InstalledPackageDataEntry, Manifest, PackageMainStatus } from 'src/app/models/patch-db/data-model'
|
import { Action, InstalledPackageDataEntry, Manifest, PackageMainStatus } from 'src/app/models/patch-db/data-model'
|
||||||
import { wizardModal } from 'src/app/components/install-wizard/install-wizard.component'
|
import { wizardModal } from 'src/app/components/install-wizard/install-wizard.component'
|
||||||
import { WizardBaker } from 'src/app/components/install-wizard/prebaked-wizards'
|
import { WizardBaker } from 'src/app/components/install-wizard/prebaked-wizards'
|
||||||
|
import { Subscription } from 'rxjs'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-actions',
|
selector: 'app-actions',
|
||||||
@@ -15,7 +16,10 @@ import { WizardBaker } from 'src/app/components/install-wizard/prebaked-wizards'
|
|||||||
styleUrls: ['./app-actions.page.scss'],
|
styleUrls: ['./app-actions.page.scss'],
|
||||||
})
|
})
|
||||||
export class AppActionsPage {
|
export class AppActionsPage {
|
||||||
pkgId: string
|
installed: InstalledPackageDataEntry
|
||||||
|
|
||||||
|
subs: Subscription[] = []
|
||||||
|
@ViewChild(IonContent) content: IonContent
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private readonly route: ActivatedRoute,
|
private readonly route: ActivatedRoute,
|
||||||
@@ -25,11 +29,26 @@ export class AppActionsPage {
|
|||||||
private readonly loaderService: LoaderService,
|
private readonly loaderService: LoaderService,
|
||||||
private readonly wizardBaker: WizardBaker,
|
private readonly wizardBaker: WizardBaker,
|
||||||
private readonly navCtrl: NavController,
|
private readonly navCtrl: NavController,
|
||||||
public readonly patch: PatchDbModel,
|
private readonly patch: PatchDbModel,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
ngOnInit () {
|
ngOnInit () {
|
||||||
this.pkgId = this.route.snapshot.paramMap.get('pkgId')
|
const pkgId = this.route.snapshot.paramMap.get('pkgId')
|
||||||
|
|
||||||
|
this.subs = [
|
||||||
|
this.patch.watch$('package-data', pkgId, 'installed')
|
||||||
|
.subscribe(installed => {
|
||||||
|
this.installed = installed
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
ngAfterViewInit () {
|
||||||
|
this.content.scrollToPoint(undefined, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy () {
|
||||||
|
this.subs.forEach(sub => sub.unsubscribe())
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleAction (pkg: InstalledPackageDataEntry, action: { key: string, value: Action }) {
|
async handleAction (pkg: InstalledPackageDataEntry, action: { key: string, value: Action }) {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<ion-content class="ion-padding-top">
|
<ion-content class="ion-padding-top">
|
||||||
|
|
||||||
<!-- loading -->
|
<!-- loading -->
|
||||||
<ion-grid *ngIf="loadingText$ | ngrxPush as loadingText; else loaded" style="height: 100%;">
|
<ion-grid *ngIf="loadingText; else loaded" style="height: 100%;">
|
||||||
<ion-row class="ion-align-items-center ion-text-center" style="height: 100%;">
|
<ion-row class="ion-align-items-center ion-text-center" style="height: 100%;">
|
||||||
<ion-col>
|
<ion-col>
|
||||||
<ion-spinner name="lines" color="warning"></ion-spinner>
|
<ion-spinner name="lines" color="warning"></ion-spinner>
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { Component } from '@angular/core'
|
import { Component, ViewChild } from '@angular/core'
|
||||||
import { NavController, AlertController, ModalController, PopoverController } from '@ionic/angular'
|
import { NavController, AlertController, ModalController, PopoverController, IonContent } from '@ionic/angular'
|
||||||
import { ActivatedRoute } from '@angular/router'
|
import { ActivatedRoute } from '@angular/router'
|
||||||
import { ApiService } from 'src/app/services/api/api.service'
|
import { ApiService } from 'src/app/services/api/api.service'
|
||||||
import { isEmptyObject } from 'src/app/util/misc.util'
|
import { isEmptyObject } from 'src/app/util/misc.util'
|
||||||
import { LoaderService } from 'src/app/services/loader.service'
|
import { LoaderService } from 'src/app/services/loader.service'
|
||||||
import { TrackingModalController } from 'src/app/services/tracking-modal-controller.service'
|
import { TrackingModalController } from 'src/app/services/tracking-modal-controller.service'
|
||||||
import { BehaviorSubject, from, fromEvent, of, Subscription } from 'rxjs'
|
import { from, fromEvent, of, Subscription } from 'rxjs'
|
||||||
import { catchError, concatMap, map, take, tap } from 'rxjs/operators'
|
import { catchError, concatMap, map, take, 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'
|
||||||
@@ -26,7 +26,7 @@ export class AppConfigPage {
|
|||||||
{ title: string, description: string, buttonText: string }
|
{ title: string, description: string, buttonText: string }
|
||||||
}
|
}
|
||||||
|
|
||||||
loadingText$ = new BehaviorSubject(undefined)
|
loadingText: string | undefined
|
||||||
|
|
||||||
pkg: InstalledPackageDataEntry
|
pkg: InstalledPackageDataEntry
|
||||||
hasConfig = false
|
hasConfig = false
|
||||||
@@ -45,7 +45,8 @@ export class AppConfigPage {
|
|||||||
spec: ConfigSpec
|
spec: ConfigSpec
|
||||||
config: object
|
config: object
|
||||||
|
|
||||||
subs: Subscription[]
|
@ViewChild(IonContent) content: IonContent
|
||||||
|
subs: Subscription[] = []
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private readonly navCtrl: NavController,
|
private readonly navCtrl: NavController,
|
||||||
@@ -84,47 +85,50 @@ export class AppConfigPage {
|
|||||||
this.navCtrl.back()
|
this.navCtrl.back()
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
]
|
this.patch.watch$('package-data', pkgId, 'installed')
|
||||||
|
.pipe(
|
||||||
this.patch.watch$('package-data', pkgId, 'installed')
|
tap(pkg => this.pkg = pkg),
|
||||||
.pipe(
|
tap(() => this.loadingText = 'Fetching config spec...'),
|
||||||
tap(pkg => this.pkg = pkg),
|
concatMap(() => this.apiService.getPackageConfig({ id: pkgId })),
|
||||||
tap(() => this.loadingText$.next(`Fetching config spec...`)),
|
concatMap(({ spec, config }) => {
|
||||||
concatMap(() => this.apiService.getPackageConfig({ id: pkgId })),
|
const rec = history.state && history.state.configRecommendation as Recommendation
|
||||||
concatMap(({ spec, config }) => {
|
if (rec) {
|
||||||
const rec = history.state && history.state.configRecommendation as Recommendation
|
this.loadingText = `Setting properties to accommodate ${rec.dependentTitle}...`
|
||||||
if (rec) {
|
return from(this.apiService.dryConfigureDependency({ 'dependency-id': pkgId, 'dependent-id': rec.dependentId }))
|
||||||
this.loadingText$.next(`Setting properties to accommodate ${rec.dependentTitle}...`)
|
.pipe(
|
||||||
return from(this.apiService.dryConfigureDependency({ 'dependency-id': pkgId, 'dependent-id': rec.dependentId }))
|
map(res => ({
|
||||||
.pipe(
|
spec,
|
||||||
map(res => ({
|
config,
|
||||||
spec,
|
dependencyConfig: res,
|
||||||
config,
|
})),
|
||||||
dependencyConfig: res,
|
tap(() => this.rec = rec),
|
||||||
})),
|
catchError(e => {
|
||||||
tap(() => this.rec = rec),
|
this.error = { text: `Could not set properties to accommodate ${rec.dependentTitle}: ${e.message}`, moreInfo: {
|
||||||
catchError(e => {
|
title: `${rec.dependentTitle} requires the following:`,
|
||||||
this.error = { text: `Could not set properties to accommodate ${rec.dependentTitle}: ${e.message}`, moreInfo: {
|
description: rec.description,
|
||||||
title: `${rec.dependentTitle} requires the following:`,
|
buttonText: 'Configure Manually',
|
||||||
description: rec.description,
|
} }
|
||||||
buttonText: 'Configure Manually',
|
return of({ spec, config, dependencyConfig: null })
|
||||||
} }
|
}),
|
||||||
return of({ spec, config, dependencyConfig: null })
|
)
|
||||||
}),
|
} else {
|
||||||
)
|
return of({ spec, config, dependencyConfig: null })
|
||||||
} else {
|
}
|
||||||
return of({ spec, config, dependencyConfig: null })
|
}),
|
||||||
}
|
map(({ spec, config, dependencyConfig }) => this.setConfig(spec, config, dependencyConfig)),
|
||||||
|
tap(() => this.loadingText = undefined),
|
||||||
|
take(1),
|
||||||
|
).subscribe({
|
||||||
|
error: e => {
|
||||||
|
console.error(e.message)
|
||||||
|
this.error = { text: e.message }
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
map(({ spec, config, dependencyConfig }) => this.setConfig(spec, config, dependencyConfig)),
|
]
|
||||||
tap(() => this.loadingText$.next(undefined)),
|
}
|
||||||
take(1),
|
|
||||||
).subscribe({
|
ngAfterViewInit () {
|
||||||
error: e => {
|
this.content.scrollToPoint(undefined, 1)
|
||||||
console.error(e.message)
|
|
||||||
this.error = { text: e.message }
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy () {
|
ngOnDestroy () {
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { Component } from '@angular/core'
|
import { Component, ViewChild } from '@angular/core'
|
||||||
import { ActivatedRoute } from '@angular/router'
|
import { ActivatedRoute } from '@angular/router'
|
||||||
|
import { IonContent } from '@ionic/angular'
|
||||||
|
import { Subscription } from 'rxjs'
|
||||||
import { concatMap, take, tap } from 'rxjs/operators'
|
import { concatMap, take, tap } from 'rxjs/operators'
|
||||||
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
|
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
|
||||||
import { ApiService } from 'src/app/services/api/api.service'
|
import { ApiService } from 'src/app/services/api/api.service'
|
||||||
import { Method } from 'src/app/services/http.service'
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-instructions',
|
selector: 'app-instructions',
|
||||||
@@ -15,6 +16,9 @@ export class AppInstructionsPage {
|
|||||||
loading = true
|
loading = true
|
||||||
error = ''
|
error = ''
|
||||||
|
|
||||||
|
@ViewChild(IonContent) content: IonContent
|
||||||
|
subs: Subscription[] = []
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private readonly route: ActivatedRoute,
|
private readonly route: ActivatedRoute,
|
||||||
private readonly apiService: ApiService,
|
private readonly apiService: ApiService,
|
||||||
@@ -27,7 +31,6 @@ export class AppInstructionsPage {
|
|||||||
.pipe(
|
.pipe(
|
||||||
concatMap(pkg => this.apiService.getStatic(pkg['static-files'].instructions)),
|
concatMap(pkg => this.apiService.getStatic(pkg['static-files'].instructions)),
|
||||||
tap(instructions => {
|
tap(instructions => {
|
||||||
console.log(instructions)
|
|
||||||
this.instructions = instructions
|
this.instructions = instructions
|
||||||
}),
|
}),
|
||||||
take(1),
|
take(1),
|
||||||
@@ -41,4 +44,12 @@ export class AppInstructionsPage {
|
|||||||
() => console.log('COMPLETE'),
|
() => console.log('COMPLETE'),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngAfterViewInit () {
|
||||||
|
this.content.scrollToPoint(undefined, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy () {
|
||||||
|
this.subs.forEach(sub => sub.unsubscribe())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
<ion-content class="ion-padding-top">
|
<ion-content class="ion-padding-top">
|
||||||
<ng-container *ngIf="patch.watch$('package-data', pkgId) | ngrxPush as pkg">
|
<ng-container *ngIf="pkg">
|
||||||
|
|
||||||
<ion-card style="margin-bottom: 16px;" *ngFor="let interface of pkg.installed.manifest.interfaces | keyvalue: asIsOrder">
|
<ion-card style="margin-bottom: 16px;" *ngFor="let interface of pkg.installed.manifest.interfaces | keyvalue: asIsOrder">
|
||||||
<ion-card-header>
|
<ion-card-header>
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { Component, ViewChild } from '@angular/core'
|
import { Component, ViewChild } from '@angular/core'
|
||||||
import { ActivatedRoute } from '@angular/router'
|
import { ActivatedRoute } from '@angular/router'
|
||||||
import { IonContent, ToastController } from '@ionic/angular'
|
import { IonContent, ToastController } from '@ionic/angular'
|
||||||
import { InstalledPackageDataEntry } from 'src/app/models/patch-db/data-model'
|
import { Subscription } from 'rxjs'
|
||||||
|
import { InstalledPackageDataEntry, PackageDataEntry } from 'src/app/models/patch-db/data-model'
|
||||||
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
|
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
|
||||||
import { ConfigService } from 'src/app/services/config.service'
|
import { ConfigService } from 'src/app/services/config.service'
|
||||||
import { copyToClipboard } from 'src/app/util/web.util'
|
import { copyToClipboard } from 'src/app/util/web.util'
|
||||||
@@ -12,25 +13,33 @@ import { copyToClipboard } from 'src/app/util/web.util'
|
|||||||
styleUrls: ['./app-Interfaces.page.scss'],
|
styleUrls: ['./app-Interfaces.page.scss'],
|
||||||
})
|
})
|
||||||
export class AppInterfacesPage {
|
export class AppInterfacesPage {
|
||||||
pkgId: string
|
pkg: PackageDataEntry
|
||||||
|
|
||||||
@ViewChild(IonContent) content: IonContent
|
@ViewChild(IonContent) content: IonContent
|
||||||
|
subs: Subscription[] = []
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private readonly route: ActivatedRoute,
|
private readonly route: ActivatedRoute,
|
||||||
private readonly toastCtrl: ToastController,
|
private readonly toastCtrl: ToastController,
|
||||||
private readonly config: ConfigService,
|
private readonly config: ConfigService,
|
||||||
public readonly patch: PatchDbModel,
|
private readonly patch: PatchDbModel,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
ngOnInit () {
|
ngOnInit () {
|
||||||
this.pkgId = this.route.snapshot.paramMap.get('pkgId')
|
const pkgId = this.route.snapshot.paramMap.get('pkgId')
|
||||||
|
this.subs = [
|
||||||
|
this.patch.watch$('package-data', pkgId).subscribe(pkg => this.pkg = pkg),
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
async ngAfterViewInit () {
|
ngAfterViewInit () {
|
||||||
this.content.scrollToPoint(undefined, 1)
|
this.content.scrollToPoint(undefined, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnDestroy () {
|
||||||
|
this.subs.forEach(sub => sub.unsubscribe())
|
||||||
|
}
|
||||||
|
|
||||||
async copy (address: string): Promise<void> {
|
async copy (address: string): Promise<void> {
|
||||||
let message = ''
|
let message = ''
|
||||||
await copyToClipboard(address || '')
|
await copyToClipboard(address || '')
|
||||||
|
|||||||
@@ -39,7 +39,7 @@
|
|||||||
<img class="bulb-off" *ngIf="pkg.value | displayBulb: 'off' : connected" src="assets/img/off-bulb.png"/>
|
<img class="bulb-off" *ngIf="pkg.value | displayBulb: 'off' : connected" src="assets/img/off-bulb.png"/>
|
||||||
|
|
||||||
<ion-card-header>
|
<ion-card-header>
|
||||||
<status *ngIf="connected" [pkg]="pkg.value" size="calc(4px + .7vw)" weight="bold"></status>
|
<status *ngIf="connected" [pkg]="pkg.value" size="calc(8px + .4vw)" weight="bold"></status>
|
||||||
<ion-card-title>{{ (pkg.value | manifest).title }}</ion-card-title>
|
<ion-card-title>{{ (pkg.value | manifest).title }}</ion-card-title>
|
||||||
</ion-card-header>
|
</ion-card-header>
|
||||||
</ion-card>
|
</ion-card>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
ion-card-title {
|
ion-card-title {
|
||||||
font-size: calc(8px + .7vw);
|
font-size: calc(10px + .4vw);
|
||||||
color: white;
|
color: white;
|
||||||
margin: 10px 0;
|
margin: 10px 0;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|||||||
@@ -5,8 +5,8 @@
|
|||||||
</ion-buttons>
|
</ion-buttons>
|
||||||
<ion-title>Logs</ion-title>
|
<ion-title>Logs</ion-title>
|
||||||
<ion-buttons slot="end" class="ion-padding-end">
|
<ion-buttons slot="end" class="ion-padding-end">
|
||||||
<ion-button (click)="getLogs()" color="primary">
|
<ion-button (click)="getLogs()">
|
||||||
<ion-icon name="refresh-outline"></ion-icon>
|
<ion-icon slot="icon-only" name="refresh-outline"></ion-icon>
|
||||||
</ion-button>
|
</ion-button>
|
||||||
</ion-buttons>
|
</ion-buttons>
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { Component } from '@angular/core'
|
import { Component, ViewChild } from '@angular/core'
|
||||||
import { ActivatedRoute } from '@angular/router'
|
import { ActivatedRoute } from '@angular/router'
|
||||||
import { Subscription } from 'rxjs'
|
import { Subscription } from 'rxjs'
|
||||||
import { PackageDataEntry } from 'src/app/models/patch-db/data-model'
|
import { PackageDataEntry } from 'src/app/models/patch-db/data-model'
|
||||||
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
|
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
|
||||||
import { getManifest } from 'src/app/services/config.service'
|
import { getManifest } from 'src/app/services/config.service'
|
||||||
import * as JsonPointer from 'json-pointer'
|
import * as JsonPointer from 'json-pointer'
|
||||||
|
import { IonContent } from '@ionic/angular'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-manifest',
|
selector: 'app-manifest',
|
||||||
@@ -12,23 +13,24 @@ import * as JsonPointer from 'json-pointer'
|
|||||||
styleUrls: ['./app-manifest.page.scss'],
|
styleUrls: ['./app-manifest.page.scss'],
|
||||||
})
|
})
|
||||||
export class AppManifestPage {
|
export class AppManifestPage {
|
||||||
pkgId: string
|
|
||||||
pkg: PackageDataEntry
|
pkg: PackageDataEntry
|
||||||
pointer: string
|
pointer: string
|
||||||
node: object
|
node: object
|
||||||
subs: Subscription[]
|
|
||||||
segmentValue: 'formatted' | 'raw' = 'formatted'
|
segmentValue: 'formatted' | 'raw' = 'formatted'
|
||||||
|
|
||||||
|
@ViewChild(IonContent) content: IonContent
|
||||||
|
subs: Subscription[] = []
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private readonly route: ActivatedRoute,
|
private readonly route: ActivatedRoute,
|
||||||
private readonly patch: PatchDbModel,
|
private readonly patch: PatchDbModel,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
ngOnInit () {
|
ngOnInit () {
|
||||||
this.pkgId = this.route.snapshot.paramMap.get('pkgId')
|
const pkgId = this.route.snapshot.paramMap.get('pkgId')
|
||||||
|
|
||||||
this.subs = [
|
this.subs = [
|
||||||
this.patch.watch$('package-data', this.pkgId)
|
this.patch.watch$('package-data', pkgId)
|
||||||
.subscribe(pkg => {
|
.subscribe(pkg => {
|
||||||
this.pkg = pkg
|
this.pkg = pkg
|
||||||
this.setNode()
|
this.setNode()
|
||||||
@@ -38,6 +40,10 @@ export class AppManifestPage {
|
|||||||
this.setNode()
|
this.setNode()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngAfterViewInit () {
|
||||||
|
this.content.scrollToPoint(undefined, 1)
|
||||||
|
}
|
||||||
|
|
||||||
ngOnDestroy () {
|
ngOnDestroy () {
|
||||||
this.subs.forEach(sub => sub.unsubscribe())
|
this.subs.forEach(sub => sub.unsubscribe())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Component } from '@angular/core'
|
import { Component, ViewChild } from '@angular/core'
|
||||||
import { ActivatedRoute } from '@angular/router'
|
import { ActivatedRoute } from '@angular/router'
|
||||||
|
import { IonContent } from '@ionic/angular'
|
||||||
import { Subscription } from 'rxjs'
|
import { Subscription } from 'rxjs'
|
||||||
import { PackageDataEntry } from 'src/app/models/patch-db/data-model'
|
import { PackageDataEntry } from 'src/app/models/patch-db/data-model'
|
||||||
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
|
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
|
||||||
@@ -10,9 +11,10 @@ import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
|
|||||||
styleUrls: ['./app-metrics.page.scss'],
|
styleUrls: ['./app-metrics.page.scss'],
|
||||||
})
|
})
|
||||||
export class AppMetricsPage {
|
export class AppMetricsPage {
|
||||||
pkgId: string
|
|
||||||
pkg: PackageDataEntry
|
pkg: PackageDataEntry
|
||||||
subs: Subscription[]
|
|
||||||
|
@ViewChild(IonContent) content: IonContent
|
||||||
|
subs: Subscription[] = []
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private readonly route: ActivatedRoute,
|
private readonly route: ActivatedRoute,
|
||||||
@@ -20,16 +22,20 @@ export class AppMetricsPage {
|
|||||||
) { }
|
) { }
|
||||||
|
|
||||||
ngOnInit () {
|
ngOnInit () {
|
||||||
this.pkgId = this.route.snapshot.paramMap.get('pkgId')
|
const pkgId = this.route.snapshot.paramMap.get('pkgId')
|
||||||
|
|
||||||
this.subs = [
|
this.subs = [
|
||||||
this.patch.watch$('package-data', this.pkgId)
|
this.patch.watch$('package-data', pkgId)
|
||||||
.subscribe(pkg => {
|
.subscribe(pkg => {
|
||||||
this.pkg = pkg
|
this.pkg = pkg
|
||||||
}),
|
}),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngAfterViewInit () {
|
||||||
|
this.content.scrollToPoint(undefined, 1)
|
||||||
|
}
|
||||||
|
|
||||||
ngOnDestroy () {
|
ngOnDestroy () {
|
||||||
this.subs.forEach(sub => sub.unsubscribe())
|
this.subs.forEach(sub => sub.unsubscribe())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,11 @@
|
|||||||
<pwa-back-button></pwa-back-button>
|
<pwa-back-button></pwa-back-button>
|
||||||
</ion-buttons>
|
</ion-buttons>
|
||||||
<ion-title>Values</ion-title>
|
<ion-title>Values</ion-title>
|
||||||
|
<ion-buttons slot="end" class="ion-padding-end">
|
||||||
|
<ion-button (click)="refresh()">
|
||||||
|
<ion-icon slot="icon-only" name="refresh-outline"></ion-icon>
|
||||||
|
</ion-button>
|
||||||
|
</ion-buttons>
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
@@ -11,64 +16,58 @@
|
|||||||
<ion-spinner *ngIf="loading; else loaded" class="center" name="lines" color="warning"></ion-spinner>
|
<ion-spinner *ngIf="loading; else loaded" class="center" name="lines" color="warning"></ion-spinner>
|
||||||
|
|
||||||
<ng-template #loaded>
|
<ng-template #loaded>
|
||||||
<ng-container *ngIf="patch.watch$('package-data', pkgId) | ngrxPush as pkg">
|
<ion-item *ngIf="error" style="margin-bottom: 16px;">
|
||||||
<ion-refresher slot="fixed" (ionRefresh)="doRefresh($event)">
|
<ion-text class="ion-text-wrap" color="danger">{{ error }}</ion-text>
|
||||||
<ion-refresher-content pullingIcon="lines" refreshingSpinner="lines"></ion-refresher-content>
|
</ion-item>
|
||||||
</ion-refresher>
|
|
||||||
|
|
||||||
<ion-item *ngIf="error" style="margin-bottom: 16px;">
|
<!-- not running -->
|
||||||
<ion-text class="ion-text-wrap" color="danger">{{ error }}</ion-text>
|
<ion-item *ngIf="notRunning" class="ion-margin-bottom">
|
||||||
</ion-item>
|
<ion-label class="ion-text-wrap">
|
||||||
|
<p><ion-text color="warning">Service not running. Information on this page could be inaccurate.</ion-text></p>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
<!-- not running -->
|
<!-- no properties -->
|
||||||
<ion-item *ngIf="pkg.installed.status.main.status !== FeStatus.Running" class="ion-margin-bottom">
|
<ion-item *ngIf="properties | empty">
|
||||||
<ion-label class="ion-text-wrap">
|
<ion-label class="ion-text-wrap">
|
||||||
<p><ion-text color="warning">Service not running. Information on this page could be inaccurate.</ion-text></p>
|
<p>No values.</p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<!-- no properties -->
|
<!-- properties -->
|
||||||
<ion-item *ngIf="properties | empty">
|
<ion-item-group *ngIf="!(properties | empty)">
|
||||||
<ion-label class="ion-text-wrap">
|
<div *ngFor="let prop of node | keyvalue: asIsOrder">
|
||||||
<p>No values.</p>
|
<!-- object -->
|
||||||
</ion-label>
|
<ion-item button detail="true" *ngIf="prop.value.type === 'object'" (click)="goToNested(prop.key)">
|
||||||
</ion-item>
|
<ion-button *ngIf="prop.value.description" class="help-button" fill="clear" slot="start" (click)="presentDescription(prop, $event)">
|
||||||
|
<ion-icon size="small" slot="icon-only" name="help-circle-outline"></ion-icon>
|
||||||
<!-- properties -->
|
</ion-button>
|
||||||
<ion-item-group *ngIf="!(properties | empty)">
|
<ion-label class="ion-text-wrap">
|
||||||
<div *ngFor="let prop of node | keyvalue: asIsOrder">
|
<h2>{{ prop.key }}</h2>
|
||||||
<!-- object -->
|
</ion-label>
|
||||||
<ion-item button detail="true" *ngIf="prop.value.type === 'object'" (click)="goToNested(prop.key)">
|
</ion-item>
|
||||||
<ion-button *ngIf="prop.value.description" class="help-button" fill="clear" slot="start" (click)="presentDescription(prop, $event)">
|
<!-- not object -->
|
||||||
<ion-icon size="small" slot="icon-only" name="help-circle-outline"></ion-icon>
|
<ion-item *ngIf="prop.value.type === 'string'">
|
||||||
|
<ion-button *ngIf="prop.value.description" class="help-button" fill="clear" slot="start" (click)="presentDescription(prop, $event)">
|
||||||
|
<ion-icon size="small" slot="icon-only" name="help-circle-outline"></ion-icon>
|
||||||
|
</ion-button>
|
||||||
|
<ion-label class="ion-text-wrap">
|
||||||
|
<h2>{{ prop.key }}</h2>
|
||||||
|
<p>{{ prop.value.masked && !unmasked[prop.key] ? (prop.value.value | mask ) : (prop.value.value | truncateEnd : 100) }}</p>
|
||||||
|
</ion-label>
|
||||||
|
<div slot="end" *ngIf="prop.value.copyable || prop.value.qr">
|
||||||
|
<ion-button *ngIf="prop.value.masked" fill="clear" (click)="toggleMask(prop.key)">
|
||||||
|
<ion-icon slot="icon-only" [name]="unmasked[prop.key] ? 'eye-off-outline' : 'eye-outline'" [color]="unmasked[prop.key] ? 'danger' : 'primary'" size="small"></ion-icon>
|
||||||
</ion-button>
|
</ion-button>
|
||||||
<ion-label class="ion-text-wrap">
|
<ion-button *ngIf="prop.value.qr" fill="clear" (click)="showQR(prop.value.value)">
|
||||||
<h2>{{ prop.key }}</h2>
|
<ion-icon slot="icon-only" name="qr-code-outline" size="small" color="primary"></ion-icon>
|
||||||
</ion-label>
|
|
||||||
</ion-item>
|
|
||||||
<!-- not object -->
|
|
||||||
<ion-item *ngIf="prop.value.type === 'string'">
|
|
||||||
<ion-button *ngIf="prop.value.description" class="help-button" fill="clear" slot="start" (click)="presentDescription(prop, $event)">
|
|
||||||
<ion-icon size="small" slot="icon-only" name="help-circle-outline"></ion-icon>
|
|
||||||
</ion-button>
|
</ion-button>
|
||||||
<ion-label class="ion-text-wrap">
|
<ion-button *ngIf="prop.value.copyable" fill="clear" (click)="copy(prop.value.value)">
|
||||||
<h2>{{ prop.key }}</h2>
|
<ion-icon slot="icon-only" name="copy-outline" size="small" color="primary"></ion-icon>
|
||||||
<p>{{ prop.value.masked && !unmasked[prop.key] ? (prop.value.value | mask ) : (prop.value.value | truncateEnd : 100) }}</p>
|
</ion-button>
|
||||||
</ion-label>
|
</div>
|
||||||
<div slot="end" *ngIf="prop.value.copyable || prop.value.qr">
|
</ion-item>
|
||||||
<ion-button *ngIf="prop.value.masked" fill="clear" (click)="toggleMask(prop.key)">
|
</div>
|
||||||
<ion-icon slot="icon-only" [name]="unmasked[prop.key] ? 'eye-off-outline' : 'eye-outline'" [color]="unmasked[prop.key] ? 'danger' : 'primary'" size="small"></ion-icon>
|
</ion-item-group>
|
||||||
</ion-button>
|
|
||||||
<ion-button *ngIf="prop.value.qr" fill="clear" (click)="showQR(prop.value.value)">
|
|
||||||
<ion-icon slot="icon-only" name="qr-code-outline" size="small" color="primary"></ion-icon>
|
|
||||||
</ion-button>
|
|
||||||
<ion-button *ngIf="prop.value.copyable" fill="clear" (click)="copy(prop.value.value)">
|
|
||||||
<ion-icon slot="icon-only" name="copy-outline" size="small" color="primary"></ion-icon>
|
|
||||||
</ion-button>
|
|
||||||
</div>
|
|
||||||
</ion-item>
|
|
||||||
</div>
|
|
||||||
</ion-item-group>
|
|
||||||
</ng-container>
|
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
@@ -1,14 +1,15 @@
|
|||||||
import { Component } from '@angular/core'
|
import { Component, ViewChild } from '@angular/core'
|
||||||
import { ActivatedRoute } from '@angular/router'
|
import { ActivatedRoute } from '@angular/router'
|
||||||
import { ApiService } from 'src/app/services/api/api.service'
|
import { ApiService } from 'src/app/services/api/api.service'
|
||||||
import { Subscription } from 'rxjs'
|
import { Subscription } from 'rxjs'
|
||||||
import { copyToClipboard } from 'src/app/util/web.util'
|
import { copyToClipboard } from 'src/app/util/web.util'
|
||||||
import { AlertController, NavController, PopoverController, ToastController } from '@ionic/angular'
|
import { AlertController, IonContent, NavController, PopoverController, ToastController } from '@ionic/angular'
|
||||||
import { PackageProperties } from 'src/app/util/properties.util'
|
import { PackageProperties } from 'src/app/util/properties.util'
|
||||||
import { QRComponent } from 'src/app/components/qr/qr.component'
|
import { QRComponent } from 'src/app/components/qr/qr.component'
|
||||||
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
|
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
|
||||||
import * as JsonPointer from 'json-pointer'
|
import * as JsonPointer from 'json-pointer'
|
||||||
import { FEStatus } from 'src/app/services/pkg-status-rendering.service'
|
import { FEStatus } from 'src/app/services/pkg-status-rendering.service'
|
||||||
|
import { PackageMainStatus } from 'src/app/models/patch-db/data-model'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-properties',
|
selector: 'app-properties',
|
||||||
@@ -24,8 +25,10 @@ export class AppPropertiesPage {
|
|||||||
properties: PackageProperties
|
properties: PackageProperties
|
||||||
node: PackageProperties
|
node: PackageProperties
|
||||||
unmasked: { [key: string]: boolean } = { }
|
unmasked: { [key: string]: boolean } = { }
|
||||||
FeStatus = FEStatus
|
running = true
|
||||||
subs: Subscription[]
|
|
||||||
|
@ViewChild(IonContent) content: IonContent
|
||||||
|
subs: Subscription[] = []
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private readonly route: ActivatedRoute,
|
private readonly route: ActivatedRoute,
|
||||||
@@ -48,18 +51,23 @@ export class AppPropertiesPage {
|
|||||||
this.pointer = queryParams['pointer']
|
this.pointer = queryParams['pointer']
|
||||||
this.node = JsonPointer.get(this.properties, this.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
|
||||||
|
}),
|
||||||
]
|
]
|
||||||
|
}
|
||||||
|
|
||||||
this.loading = false
|
ngAfterViewInit () {
|
||||||
|
this.content.scrollToPoint(undefined, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy () {
|
ngOnDestroy () {
|
||||||
this.subs.forEach(sub => sub.unsubscribe())
|
this.subs.forEach(sub => sub.unsubscribe())
|
||||||
}
|
}
|
||||||
|
|
||||||
async doRefresh (event: any) {
|
async refresh () {
|
||||||
await this.getProperties(),
|
await this.getProperties()
|
||||||
event.target.complete()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async presentDescription (property: { key: string, value: PackageProperties[''] }, e: Event) {
|
async presentDescription (property: { key: string, value: PackageProperties[''] }, e: Event) {
|
||||||
@@ -114,12 +122,15 @@ export class AppPropertiesPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async getProperties (): Promise<void> {
|
private async getProperties (): Promise<void> {
|
||||||
|
this.loading = true
|
||||||
try {
|
try {
|
||||||
this.properties = await this.apiService.getPackageProperties({ id: this.pkgId })
|
this.properties = await this.apiService.getPackageProperties({ id: this.pkgId })
|
||||||
this.node = JsonPointer.get(this.properties, this.pointer || '')
|
this.node = JsonPointer.get(this.properties, this.pointer || '')
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
this.error = e.message
|
this.error = e.message
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,15 +4,15 @@
|
|||||||
<pwa-back-button></pwa-back-button>
|
<pwa-back-button></pwa-back-button>
|
||||||
</ion-buttons>
|
</ion-buttons>
|
||||||
<ion-title>Restore Backup</ion-title>
|
<ion-title>Restore Backup</ion-title>
|
||||||
<ion-buttons slot="end">
|
<ion-buttons slot="end" class="ion-padding-end">
|
||||||
<ion-button (click)="doRefresh()" color="primary">
|
<ion-button (click)="refresh()">
|
||||||
<ion-icon slot="icon-only" name="reload-outline"></ion-icon>
|
<ion-icon slot="icon-only" name="refresh-outline"></ion-icon>
|
||||||
</ion-button>
|
</ion-button>
|
||||||
</ion-buttons>
|
</ion-buttons>
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
<ion-content class="ion-padding-top" *ngIf="patch.watch$('package-data', pkgId) | ngrxPush as pkg">
|
<ion-content class="ion-padding-top">
|
||||||
|
|
||||||
<ion-grid *ngIf="loading; else loaded" style="height: 100%;">
|
<ion-grid *ngIf="loading; else loaded" style="height: 100%;">
|
||||||
<ion-row class="ion-align-items-center ion-text-center" style="height: 100%;">
|
<ion-row class="ion-align-items-center ion-text-center" style="height: 100%;">
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
<ion-label class="ion-text-wrap">
|
<ion-label class="ion-text-wrap">
|
||||||
<p><ion-text color="dark">About</ion-text></p>
|
<p><ion-text color="dark">About</ion-text></p>
|
||||||
<p>
|
<p>
|
||||||
Select a location from which to restore {{ pkg.installed.manifest.title }}. This will overwrite all current data.
|
Select a location from which to restore {{ title }}. This will overwrite all current data.
|
||||||
</p>
|
</p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { BackupConfirmationComponent } from 'src/app/modals/backup-confirmation/
|
|||||||
import { DiskInfo, PartitionInfoEntry } from 'src/app/services/api/api-types'
|
import { DiskInfo, PartitionInfoEntry } from 'src/app/services/api/api-types'
|
||||||
import { ActivatedRoute } from '@angular/router'
|
import { ActivatedRoute } from '@angular/router'
|
||||||
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
|
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
|
||||||
|
import { Subscription } from 'rxjs'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-restore',
|
selector: 'app-restore',
|
||||||
@@ -14,30 +15,44 @@ import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
|
|||||||
export class AppRestorePage {
|
export class AppRestorePage {
|
||||||
disks: DiskInfo
|
disks: DiskInfo
|
||||||
pkgId: string
|
pkgId: string
|
||||||
|
title: string
|
||||||
loading = true
|
loading = true
|
||||||
error: string
|
error: string
|
||||||
allPartitionsMounted: boolean
|
allPartitionsMounted: boolean
|
||||||
|
|
||||||
@ViewChild(IonContent) content: IonContent
|
@ViewChild(IonContent) content: IonContent
|
||||||
|
subs: Subscription[] = []
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private readonly route: ActivatedRoute,
|
private readonly route: ActivatedRoute,
|
||||||
private readonly modalCtrl: ModalController,
|
private readonly modalCtrl: ModalController,
|
||||||
private readonly apiService: ApiService,
|
private readonly apiService: ApiService,
|
||||||
private readonly loadingCtrl: LoadingController,
|
private readonly loadingCtrl: LoadingController,
|
||||||
public readonly patch: PatchDbModel,
|
private readonly patch: PatchDbModel,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
ngOnInit () {
|
ngOnInit () {
|
||||||
this.pkgId = this.route.snapshot.paramMap.get('pkgId')
|
this.pkgId = this.route.snapshot.paramMap.get('pkgId')
|
||||||
|
|
||||||
|
this.subs = [
|
||||||
|
this.patch.watch$('package-data', this.pkgId, 'installed', 'manifest', 'title')
|
||||||
|
.subscribe(title => {
|
||||||
|
this.title = title
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
|
||||||
this.getExternalDisks()
|
this.getExternalDisks()
|
||||||
}
|
}
|
||||||
|
|
||||||
async ngAfterViewInit () {
|
ngAfterViewInit () {
|
||||||
this.content.scrollToPoint(undefined, 1)
|
this.content.scrollToPoint(undefined, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
async doRefresh () {
|
ngOnDestroy () {
|
||||||
|
this.subs.forEach(sub => sub.unsubscribe())
|
||||||
|
}
|
||||||
|
|
||||||
|
async refresh () {
|
||||||
this.loading = true
|
this.loading = true
|
||||||
await this.getExternalDisks()
|
await this.getExternalDisks()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,41 +80,38 @@
|
|||||||
<ion-item-group class="ion-padding-bottom">
|
<ion-item-group class="ion-padding-bottom">
|
||||||
<!-- dependencies -->
|
<!-- dependencies -->
|
||||||
<ng-container *ngIf="!(pkg.installed['current-dependencies'] | empty)">
|
<ng-container *ngIf="!(pkg.installed['current-dependencies'] | empty)">
|
||||||
<ion-card id="dependencies" class="dep-card">
|
<ion-item-divider id="dependencies">Dependencies</ion-item-divider>
|
||||||
<ion-card-header>
|
<!-- A current-dependency is a subset of the manifest.dependencies that is currently required as determined by the service config. -->
|
||||||
<ion-card-title>Dependencies</ion-card-title>
|
<ion-grid>
|
||||||
</ion-card-header>
|
<ion-row>
|
||||||
<ion-card-content>
|
<ion-col *ngFor="let dep of pkg.installed['current-dependencies'] | keyvalue" sizeSm="12" sizeMd="6">
|
||||||
<!-- A current-dependency is a subset of the manifest.dependencies that is currently required as determined by the service config. -->
|
<ion-item *ngrxLet="patch.watch$('package-data', dep.key) as localDep">
|
||||||
<div *ngFor="let dep of pkg.installed['current-dependencies'] | keyvalue">
|
<ion-thumbnail slot="start">
|
||||||
<ion-item *ngrxLet="patch.watch$('package-data', dep.key) as localDep" class="dependency-item">
|
|
||||||
<ion-avatar slot="start" style="position: relative; height: 5vh; width: 5vh; margin: 0px;">
|
|
||||||
<div class="dep-badge" [class]="pkg.installed.status['dependency-errors'][dep.key] ? 'dep-issue' : 'dep-sat'"></div>
|
|
||||||
<img [src]="localDep ? localDep['static-files'].icon : pkg.installed.status['dependency-errors'][dep.key]?.icon" />
|
<img [src]="localDep ? localDep['static-files'].icon : pkg.installed.status['dependency-errors'][dep.key]?.icon" />
|
||||||
</ion-avatar>
|
</ion-thumbnail>
|
||||||
<ion-label class="ion-text-wrap" style="padding: 1vh; padding-left: 2vh">
|
<ion-label class="ion-text-wrap">
|
||||||
<h4 style="font-family: 'Montserrat'">{{ localDep ? (localDep | manifest).title : pkg.installed.status['dependency-errors'][dep.key]?.title }}</h4>
|
<h2 style="font-family: 'Montserrat'">{{ localDep ? (localDep | manifest).title : pkg.installed.status['dependency-errors'][dep.key]?.title }}</h2>
|
||||||
<p style="font-size: small">{{ manifest.dependencies[dep.key].version | displayEmver }}</p>
|
<p>{{ manifest.dependencies[dep.key].version | displayEmver }}</p>
|
||||||
<p style="padding-top: 2px; position: relative; font-style: italic; font-size: smaller"><ion-text [color]="pkg.installed.status['dependency-errors'][dep.key] ? 'warning' : 'success'">{{ pkg.installed.status['dependency-errors'][dep.key] ? pkg.installed.status['dependency-errors'][dep.key].type : 'satisfied' }}</ion-text></p>
|
<p><ion-text [color]="pkg.installed.status['dependency-errors'][dep.key] ? 'warning' : 'success'">{{ pkg.installed.status['dependency-errors'][dep.key] ? pkg.installed.status['dependency-errors'][dep.key].type : 'satisfied' }}</ion-text></p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
|
|
||||||
<ion-button *ngIf="!pkg.installed.status['dependency-errors'][dep.key] || (pkg.installed.status['dependency-errors'][dep.key] && [DependencyErrorType.InterfaceHealthChecksFailed, DependencyErrorType.HealthChecksFailed] | includes : pkg.installed.status['dependency-errors'][dep.key].type)" slot="end" size="small" [routerLink]="['/services', dep.key]" color="primary" fill="outline" style="font-size: x-small">
|
<ion-button *ngIf="!pkg.installed.status['dependency-errors'][dep.key] || (pkg.installed.status['dependency-errors'][dep.key] && [DependencyErrorType.InterfaceHealthChecksFailed, DependencyErrorType.HealthChecksFailed] | includes : pkg.installed.status['dependency-errors'][dep.key].type)" slot="end" size="small" color="dark" [routerLink]="['/services', dep.key]" fill="outline">
|
||||||
View
|
View
|
||||||
</ion-button>
|
</ion-button>
|
||||||
|
|
||||||
<ng-container *ngIf="pkg.installed.status['dependency-errors'][dep.key]">
|
<ng-container *ngIf="pkg.installed.status['dependency-errors'][dep.key]">
|
||||||
<ion-button *ngIf="!localDep" slot="end" size="small" (click)="fixDep('install', dep.key)" color="primary" fill="outline" style="font-size: x-small">
|
<ion-button *ngIf="!localDep" slot="end" size="small" color="dark" (click)="fixDep('install', dep.key)" fill="outline">
|
||||||
Install
|
Install
|
||||||
</ion-button>
|
</ion-button>
|
||||||
|
|
||||||
<ng-container *ngIf="localDep && localDep.state === PackageState.Installed">
|
<ng-container *ngIf="localDep && localDep.state === PackageState.Installed">
|
||||||
<ion-button *ngIf="pkg.installed.status['dependency-errors'][dep.key].type === DependencyErrorType.NotRunning" slot="end" size="small" [routerLink]="['/services', dep.key]" color="primary" fill="outline" style="font-size: x-small">
|
<ion-button *ngIf="pkg.installed.status['dependency-errors'][dep.key].type === DependencyErrorType.NotRunning" slot="end" size="small" color="dark" [routerLink]="['/services', dep.key]" fill="outline">
|
||||||
Start
|
Start
|
||||||
</ion-button>
|
</ion-button>
|
||||||
<ion-button *ngIf="pkg.installed.status['dependency-errors'][dep.key].type === DependencyErrorType.IncorrectVersion" slot="end" size="small" (click)="fixDep('update', dep.key)" color="primary" fill="outline" style="font-size: x-small">
|
<ion-button *ngIf="pkg.installed.status['dependency-errors'][dep.key].type === DependencyErrorType.IncorrectVersion" slot="end" size="small" color="dark" (click)="fixDep('update', dep.key)" fill="outline">
|
||||||
Update
|
Update
|
||||||
</ion-button>
|
</ion-button>
|
||||||
<ion-button *ngIf="pkg.installed.status['dependency-errors'][dep.key].type === DependencyErrorType.ConfigUnsatisfied" slot="end" size="small" (click)="fixDep('configure', dep.key)" color="primary" fill="outline" style="font-size: x-small">
|
<ion-button *ngIf="pkg.installed.status['dependency-errors'][dep.key].type === DependencyErrorType.ConfigUnsatisfied" slot="end" size="small" color="dark" (click)="fixDep('configure', dep.key)" fill="outline">
|
||||||
Configure
|
Configure
|
||||||
</ion-button>
|
</ion-button>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
@@ -122,12 +119,11 @@
|
|||||||
<div *ngIf="localDep && localDep.state !== PackageState.Installed" slot="end" class="spinner">
|
<div *ngIf="localDep && localDep.state !== PackageState.Installed" slot="end" class="spinner">
|
||||||
<ion-spinner [color]="localDep.state === PackageState.Removing ? 'danger' : 'primary'" style="height: 3vh; width: 3vh" name="dots"></ion-spinner>
|
<ion-spinner [color]="localDep.state === PackageState.Removing ? 'danger' : 'primary'" style="height: 3vh; width: 3vh" name="dots"></ion-spinner>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</div>
|
</ion-col>
|
||||||
</ion-card-content>
|
</ion-row>
|
||||||
</ion-card>
|
</ion-grid>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ion-item-group>
|
</ion-item-group>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|||||||
@@ -40,13 +40,6 @@
|
|||||||
border-color: #404040;
|
border-color: #404040;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dep-badge {
|
|
||||||
position: absolute; width: 2.5vh;
|
|
||||||
height: 2.5vh;
|
|
||||||
border-radius: 50px;
|
|
||||||
left: -1vh;
|
|
||||||
top: -1vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dep-issue {
|
.dep-issue {
|
||||||
background: radial-gradient(var(--ion-color-warning) 40%, transparent)
|
background: radial-gradient(var(--ion-color-warning) 40%, transparent)
|
||||||
@@ -55,3 +48,9 @@
|
|||||||
.dep-sat {
|
.dep-sat {
|
||||||
background: radial-gradient(var(--ion-color-success) 40%, transparent)
|
background: radial-gradient(var(--ion-color-success) 40%, transparent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ion-item-divider {
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin-top: 22px;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
@@ -27,14 +27,12 @@ export class AppShowPage {
|
|||||||
buttons: Button[] = []
|
buttons: Button[] = []
|
||||||
manifest: Manifest = { } as Manifest
|
manifest: Manifest = { } as Manifest
|
||||||
connected: boolean
|
connected: boolean
|
||||||
|
|
||||||
subs: Subscription[] = []
|
|
||||||
|
|
||||||
FeStatus = FEStatus
|
FeStatus = FEStatus
|
||||||
PackageState = PackageState
|
PackageState = PackageState
|
||||||
DependencyErrorType = DependencyErrorType
|
DependencyErrorType = DependencyErrorType
|
||||||
|
|
||||||
@ViewChild(IonContent) content: IonContent
|
@ViewChild(IonContent) content: IonContent
|
||||||
|
subs: Subscription[] = []
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private readonly alertCtrl: AlertController,
|
private readonly alertCtrl: AlertController,
|
||||||
@@ -61,7 +59,7 @@ export class AppShowPage {
|
|||||||
this.setButtons()
|
this.setButtons()
|
||||||
}
|
}
|
||||||
|
|
||||||
async ngAfterViewInit () {
|
ngAfterViewInit () {
|
||||||
this.content.scrollToPoint(undefined, 1)
|
this.content.scrollToPoint(undefined, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
<ion-content class="ion-padding-top" *ngrxLet="patch.watch$('package-data') as installedPkgs">
|
<ion-content class="ion-padding-top">
|
||||||
<ion-spinner *ngIf="pageLoading; else pageLoaded" class="center" name="lines" color="warning"></ion-spinner>
|
<ion-spinner *ngIf="pageLoading; else pageLoaded" class="center" name="lines" color="warning"></ion-spinner>
|
||||||
|
|
||||||
<ng-template #pageLoaded>
|
<ng-template #pageLoaded>
|
||||||
@@ -43,23 +43,41 @@
|
|||||||
</ion-button>
|
</ion-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="pkgsLoading; else pkgsLoaded" style="margin-top: 64px;" class="ion-text-center">
|
<div *ngIf="pkgsLoading; else loaded" style="margin-top: 64px;" class="ion-text-center">
|
||||||
<ion-spinner name="lines" color="warning"></ion-spinner>
|
<ion-spinner name="lines" color="warning"></ion-spinner>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ng-template #pkgsLoaded>
|
<ng-template #loaded>
|
||||||
<ion-grid style="margin: 0 6px;">
|
<ion-grid>
|
||||||
<ion-row>
|
<ion-row>
|
||||||
<ion-col *ngFor="let pkg of pkgs" sizeXl="2" sizeLg="3" sizeMd="3" sizeSm="4" sizeXs="4">
|
<ion-col *ngFor="let pkg of pkgs" sizeSm="12" sizeMd="6">
|
||||||
<ion-card class="ion-text-center" style="margin: 0;" [routerLink]="['/marketplace', pkg.id]">
|
<ion-item [routerLink]="['/marketplace', pkg.id]">
|
||||||
<img [src]="pkg.icon" style="width: 90%; margin-top: 8px;" />
|
<ion-thumbnail slot="start">
|
||||||
<ion-card-header style="min-height: 70px; text-align: left;">
|
<img [src]="pkg.icon" />
|
||||||
<ion-card-title>{{ pkg.title }}</ion-card-title>
|
</ion-thumbnail>
|
||||||
</ion-card-header>
|
<ion-label>
|
||||||
<ion-card-content style="text-align: left;">
|
<h2 style="font-family: 'Montserrat';">{{ pkg.title }}</h2>
|
||||||
{{ pkg.descriptionShort }}
|
<p>{{ pkg.descriptionShort }}</p>
|
||||||
</ion-card-content>
|
<ng-container *ngIf="installedPkgs[pkg.id] as pkgI">
|
||||||
</ion-card>
|
<p *ngIf="pkgI.state === PackageState.Installed">
|
||||||
|
<ion-text *ngIf="(pkg.version | compareEmver : pkgI.installed.manifest.version) === 0" color="success">Installed</ion-text>
|
||||||
|
<ion-text *ngIf="(pkg.version | compareEmver : pkgI.installed.manifest.version) === 1" color="warning">Update Available</ion-text>
|
||||||
|
</p>
|
||||||
|
<p *ngIf="pkgI.state === PackageState.Installing" style="display: flex; flex-direction: row; align-items: center;">
|
||||||
|
<ion-text color="primary">Installing</ion-text>
|
||||||
|
<ion-spinner name="crescent" style="height: 10px; width: 15px; margin-left: 3px; margin-right: -4px;" color="primary"></ion-spinner>
|
||||||
|
</p>
|
||||||
|
<p *ngIf="pkgI.state === PackageState.Updating" style="display: flex; flex-direction: row; align-items: center;">
|
||||||
|
<ion-text color="primary">Updating</ion-text>
|
||||||
|
<ion-spinner name="crescent" style="height: 10px; width: 15px; margin-left: 3px; margin-right: -4px;" color="primary"></ion-spinner>
|
||||||
|
</p>
|
||||||
|
<p *ngIf="pkgI.state === PackageState.Removing" style="display: flex; flex-direction: row; align-items: center;">
|
||||||
|
<ion-text color="danger">Removing</ion-text>
|
||||||
|
<ion-spinner name="crescent" style="height: 10px; width: 15px; margin-left: 3px; margin-right: -4px;" color="danger"></ion-spinner>
|
||||||
|
</p>
|
||||||
|
</ng-container>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
</ion-col>
|
</ion-col>
|
||||||
</ion-row>
|
</ion-row>
|
||||||
</ion-grid>
|
</ion-grid>
|
||||||
|
|||||||
@@ -1,10 +1,3 @@
|
|||||||
.beneath-title {
|
|
||||||
font-size: 12px;
|
|
||||||
font-style: italic;
|
|
||||||
font-family: 'Open Sans';
|
|
||||||
padding: 1px 0px 1.5px 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scrollable {
|
.scrollable {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ import { wizardModal } from 'src/app/components/install-wizard/install-wizard.co
|
|||||||
import { ModalController } from '@ionic/angular'
|
import { ModalController } from '@ionic/angular'
|
||||||
import { WizardBaker } from 'src/app/components/install-wizard/prebaked-wizards'
|
import { WizardBaker } from 'src/app/components/install-wizard/prebaked-wizards'
|
||||||
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
|
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
|
||||||
import { PackageState } from 'src/app/models/patch-db/data-model'
|
import { PackageDataEntry, PackageState } from 'src/app/models/patch-db/data-model'
|
||||||
|
import { Subscription } from 'rxjs'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'marketplace-list',
|
selector: 'marketplace-list',
|
||||||
@@ -23,6 +24,7 @@ export class MarketplaceListPage {
|
|||||||
data: MarketplaceData
|
data: MarketplaceData
|
||||||
eos: MarketplaceEOS
|
eos: MarketplaceEOS
|
||||||
pkgs: AvailablePreview[] = []
|
pkgs: AvailablePreview[] = []
|
||||||
|
installedPkgs: { [id: string]: PackageDataEntry } = { }
|
||||||
|
|
||||||
PackageState = PackageState
|
PackageState = PackageState
|
||||||
|
|
||||||
@@ -30,14 +32,21 @@ export class MarketplaceListPage {
|
|||||||
needInfinite = false
|
needInfinite = false
|
||||||
readonly perPage = 20
|
readonly perPage = 20
|
||||||
|
|
||||||
|
subs: Subscription[] = []
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private readonly apiService: ApiService,
|
private readonly apiService: ApiService,
|
||||||
private readonly modalCtrl: ModalController,
|
private readonly modalCtrl: ModalController,
|
||||||
private readonly wizardBaker: WizardBaker,
|
private readonly wizardBaker: WizardBaker,
|
||||||
public patch: PatchDbModel,
|
private readonly patch: PatchDbModel,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
async ngOnInit () {
|
async ngOnInit () {
|
||||||
|
this.subs = [
|
||||||
|
this.patch.watch$('package-data')
|
||||||
|
.subscribe(pkgs => this.installedPkgs = pkgs),
|
||||||
|
]
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const [data, eos, pkgs] = await Promise.all([
|
const [data, eos, pkgs] = await Promise.all([
|
||||||
this.apiService.getMarketplaceData({ }),
|
this.apiService.getMarketplaceData({ }),
|
||||||
@@ -56,6 +65,10 @@ export class MarketplaceListPage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnDestroy () {
|
||||||
|
this.subs.forEach(sub => sub.unsubscribe())
|
||||||
|
}
|
||||||
|
|
||||||
async doInfinite (e: any): Promise<void> {
|
async doInfinite (e: any): Promise<void> {
|
||||||
const pkgs = await this.getPkgs()
|
const pkgs = await this.getPkgs()
|
||||||
this.pkgs = this.pkgs.concat(pkgs)
|
this.pkgs = this.pkgs.concat(pkgs)
|
||||||
@@ -65,6 +78,7 @@ export class MarketplaceListPage {
|
|||||||
async search (e?: any): Promise<void> {
|
async search (e?: any): Promise<void> {
|
||||||
this.query = e.target.value || undefined
|
this.query = e.target.value || undefined
|
||||||
this.page = 1
|
this.page = 1
|
||||||
|
this.pkgsLoading = true
|
||||||
this.pkgs = await this.getPkgs()
|
this.pkgs = await this.getPkgs()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,7 +93,6 @@ export class MarketplaceListPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async getPkgs (): Promise<AvailablePreview[]> {
|
private async getPkgs (): Promise<AvailablePreview[]> {
|
||||||
this.pkgsLoading = true
|
|
||||||
try {
|
try {
|
||||||
const pkgs = await this.apiService.getAvailableList({
|
const pkgs = await this.apiService.getAvailableList({
|
||||||
category: this.category,
|
category: this.category,
|
||||||
@@ -100,6 +113,8 @@ export class MarketplaceListPage {
|
|||||||
|
|
||||||
async switchCategory (category: string): Promise<void> {
|
async switchCategory (category: string): Promise<void> {
|
||||||
this.category = category
|
this.category = category
|
||||||
|
this.pkgsLoading = true
|
||||||
|
this.page = 1
|
||||||
this.pkgs = await this.getPkgs()
|
this.pkgs = await this.getPkgs()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,59 +19,57 @@
|
|||||||
<ion-text class="ion-text-wrap" color="danger">{{ error }}</ion-text>
|
<ion-text class="ion-text-wrap" color="danger">{{ error }}</ion-text>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<ng-container *ngrxLet="patch.watch$('package-data', pkgId) as localPkg">
|
<ion-grid>
|
||||||
<ion-grid>
|
<ion-row>
|
||||||
<ion-row>
|
<ion-col sizeXl="9" sizeLg="9" sizeMd="9" sizeSm="12" sizeXs="12">
|
||||||
<ion-col sizeXl="9" sizeLg="9" sizeMd="9" sizeSm="12" sizeXs="12">
|
<div class="header">
|
||||||
<div class="header">
|
<img [src]="pkg.icon" />
|
||||||
<img [src]="pkg.icon" />
|
<div class="header-text">
|
||||||
<div class="header-text">
|
<h1 class="header-title">{{ pkg.manifest.title }}</h1>
|
||||||
<h1 class="header-title">{{ pkg.manifest.title }}</h1>
|
<p class="header-version">{{ pkg.manifest.version | displayEmver }}</p>
|
||||||
<p class="header-version">{{ pkg.manifest.version | displayEmver }}</p>
|
<div class="header-status">
|
||||||
<div class="header-status">
|
<!-- no installedPkg -->
|
||||||
<!-- no localPkg -->
|
<p *ngIf="!installedPkg; else local">
|
||||||
<p *ngIf="!localPkg; else local">
|
<ion-text color="medium">Not Installed</ion-text>
|
||||||
<ion-text color="medium">Not Installed</ion-text>
|
</p>
|
||||||
|
<!-- installedPkg -->
|
||||||
|
<ng-template #local>
|
||||||
|
<p *ngIf="installedPkg.state !== PackageState.Installed; else installed">
|
||||||
|
<!-- installing, updating, removing -->
|
||||||
|
<ion-text [color]="installedPkg.state === PackageState.Removing ? 'danger' : 'primary'">{{ installedPkg.state }}</ion-text>
|
||||||
|
<ion-spinner class="dots dots-medium" name="dots" [color]="installedPkg.state === PackageState.Removing ? 'danger' : 'primary'"></ion-spinner>
|
||||||
</p>
|
</p>
|
||||||
<!-- localPkg -->
|
<!-- installed -->
|
||||||
<ng-template #local>
|
<ng-template #installed>
|
||||||
<p *ngIf="localPkg.state !== PackageState.Installed; else installed">
|
<p>
|
||||||
<!-- installing, updating, removing -->
|
<ion-text color="medium">Installed at {{ installedPkg.installed.manifest.version | displayEmver }}</ion-text>
|
||||||
<ion-text [color]="localPkg.state === PackageState.Removing ? 'danger' : 'primary'">{{ localPkg.state }}</ion-text>
|
|
||||||
<ion-spinner class="dots dots-medium" name="dots" [color]="localPkg.state === PackageState.Removing ? 'danger' : 'primary'"></ion-spinner>
|
|
||||||
</p>
|
</p>
|
||||||
<!-- installed -->
|
|
||||||
<ng-template #installed>
|
|
||||||
<p>
|
|
||||||
<ion-text color="medium">Installed at {{ localPkg.installed.manifest.version | displayEmver }}</ion-text>
|
|
||||||
</p>
|
|
||||||
</ng-template>
|
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</div>
|
</ng-template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ion-col>
|
</div>
|
||||||
<ion-col sizeXl="3" sizeLg="3" sizeMd="3" sizeSm="12" sizeXs="12" class="ion-align-self-center">
|
</ion-col>
|
||||||
<!-- no localPkg -->
|
<ion-col sizeXl="3" sizeLg="3" sizeMd="3" sizeSm="12" sizeXs="12" class="ion-align-self-center">
|
||||||
<ion-button *ngIf="!localPkg; else localPkg2" class="main-action-button" expand="block" (click)="install()">
|
<!-- no installedPkg -->
|
||||||
Install
|
<ion-button *ngIf="!installedPkg; else installedPkg2" class="main-action-button" expand="block" (click)="install()">
|
||||||
</ion-button>
|
Install
|
||||||
<!-- localPkg -->
|
</ion-button>
|
||||||
<ng-template #localPkg2>
|
<!-- installedPkg -->
|
||||||
<!-- not installing, updating, or removing -->
|
<ng-template #installedPkg2>
|
||||||
<ng-container *ngIf="localPkg.state === PackageState.Installed">
|
<!-- not installing, updating, or removing -->
|
||||||
<ion-button *ngIf="(localPkg.installed.manifest.version | compareEmver : pkg.manifest.version) === -1" class="main-action-button" expand="block" (click)="update('update')">
|
<ng-container *ngIf="installedPkg.state === PackageState.Installed">
|
||||||
Update
|
<ion-button *ngIf="(installedPkg.installed.manifest.version | compareEmver : pkg.manifest.version) === -1" class="main-action-button" expand="block" (click)="update('update')">
|
||||||
</ion-button>
|
Update
|
||||||
<ion-button *ngIf="(localPkg.installed.manifest.version | compareEmver : pkg.manifest.version) === 1" class="main-action-button" expand="block" color="warning" (click)="update('downgrade')">
|
</ion-button>
|
||||||
Downgrade
|
<ion-button *ngIf="(installedPkg.installed.manifest.version | compareEmver : pkg.manifest.version) === 1" class="main-action-button" expand="block" color="warning" (click)="update('downgrade')">
|
||||||
</ion-button>
|
Downgrade
|
||||||
</ng-container>
|
</ion-button>
|
||||||
</ng-template>
|
</ng-container>
|
||||||
</ion-col>
|
</ng-template>
|
||||||
</ion-row>
|
</ion-col>
|
||||||
</ion-grid>
|
</ion-row>
|
||||||
</ng-container>
|
</ion-grid>
|
||||||
|
|
||||||
<!-- recommendation -->
|
<!-- recommendation -->
|
||||||
<ion-item *ngIf="rec && showRec" class="rec-item">
|
<ion-item *ngIf="rec && showRec" class="rec-item">
|
||||||
@@ -121,24 +119,20 @@
|
|||||||
<ion-item-divider>Dependencies</ion-item-divider>
|
<ion-item-divider>Dependencies</ion-item-divider>
|
||||||
<ion-grid>
|
<ion-grid>
|
||||||
<ion-row>
|
<ion-row>
|
||||||
<ion-col *ngFor="let dep of pkg.manifest.dependencies | keyvalue" sizeMd="6" sizeSm="12" sizeXs="12">
|
<ion-col *ngFor="let dep of pkg.manifest.dependencies | keyvalue" sizeSm="12" sizeMd="6">
|
||||||
<ion-card *ngIf="!dep.value.optional" style="--background: #171717" [routerLink]="['/marketplace', dep.key]">
|
<ion-item *ngIf="!dep.value.optional" [routerLink]="['/marketplace', dep.key]">
|
||||||
<ion-item color="transparent" lines="none">
|
<ion-thumbnail slot="start">
|
||||||
<ion-avatar slot="start">
|
<img [src]="pkg['dependency-metadata'][dep.key].icon" />
|
||||||
<img [src]="pkg['dependency-metadata'][dep.key].icon" />
|
</ion-thumbnail>
|
||||||
</ion-avatar>
|
<ion-label class="ion-text-wrap">
|
||||||
<ion-label class="ion-text-wrap">
|
<h2>
|
||||||
<h2>
|
{{ pkg['dependency-metadata'][dep.key].title }}
|
||||||
{{ pkg['dependency-metadata'][dep.key].title }}
|
<span *ngIf="dep.value.recommended"> (recommended)</span>
|
||||||
<span *ngIf="dep.value.recommended"> (recommended)</span>
|
</h2>
|
||||||
</h2>
|
<p style="font-size: small">{{ dep.value.version | displayEmver }}</p>
|
||||||
<p style="font-size: small">{{ dep.value.version | displayEmver }}</p>
|
<p>{{ dep.value.description }}</p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-card-content>
|
|
||||||
{{ dep.value.description }}
|
|
||||||
</ion-card-content>
|
|
||||||
</ion-card>
|
|
||||||
</ion-col>
|
</ion-col>
|
||||||
</ion-row>
|
</ion-row>
|
||||||
</ion-grid>
|
</ion-grid>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
font-family: 'Montserrat';
|
font-family: 'Montserrat';
|
||||||
padding: 2%;
|
padding: 2%;
|
||||||
img {
|
img {
|
||||||
min-width: 16%;
|
min-width: 15%;
|
||||||
max-width: 18%;
|
max-width: 18%;
|
||||||
}
|
}
|
||||||
.header-text {
|
.header-text {
|
||||||
@@ -10,12 +10,11 @@
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
.header-title {
|
.header-title {
|
||||||
line-height: .65;
|
|
||||||
margin: 0 0 0 -2px;
|
margin: 0 0 0 -2px;
|
||||||
font-size: calc(20px + 3vw)
|
font-size: calc(20px + 3vw)
|
||||||
}
|
}
|
||||||
.header-version {
|
.header-version {
|
||||||
padding: 12px 0;
|
padding: 4px 0 12px 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: calc(10px + 1vw)
|
font-size: calc(10px + 1vw)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,9 @@ import { Emver } from 'src/app/services/emver.service'
|
|||||||
import { displayEmver } from 'src/app/pipes/emver.pipe'
|
import { displayEmver } from 'src/app/pipes/emver.pipe'
|
||||||
import { pauseFor } from 'src/app/util/misc.util'
|
import { pauseFor } from 'src/app/util/misc.util'
|
||||||
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
|
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
|
||||||
import { PackageState } from 'src/app/models/patch-db/data-model'
|
import { PackageDataEntry, PackageState } from 'src/app/models/patch-db/data-model'
|
||||||
import { MarketplaceService } from '../marketplace.service'
|
import { MarketplaceService } from '../marketplace.service'
|
||||||
|
import { Subscription } from 'rxjs'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'marketplace-show',
|
selector: 'marketplace-show',
|
||||||
@@ -19,12 +20,13 @@ import { MarketplaceService } from '../marketplace.service'
|
|||||||
export class MarketplaceShowPage {
|
export class MarketplaceShowPage {
|
||||||
error = ''
|
error = ''
|
||||||
pkgId: string
|
pkgId: string
|
||||||
|
installedPkg: PackageDataEntry
|
||||||
PackageState = PackageState
|
PackageState = PackageState
|
||||||
|
|
||||||
rec: Recommendation | null = null
|
rec: Recommendation | null = null
|
||||||
showRec = true
|
showRec = true
|
||||||
|
|
||||||
|
subs: Subscription[] = []
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private readonly route: ActivatedRoute,
|
private readonly route: ActivatedRoute,
|
||||||
private readonly alertCtrl: AlertController,
|
private readonly alertCtrl: AlertController,
|
||||||
@@ -32,17 +34,30 @@ export class MarketplaceShowPage {
|
|||||||
private readonly wizardBaker: WizardBaker,
|
private readonly wizardBaker: WizardBaker,
|
||||||
private readonly navCtrl: NavController,
|
private readonly navCtrl: NavController,
|
||||||
private readonly emver: Emver,
|
private readonly emver: Emver,
|
||||||
public readonly patch: PatchDbModel,
|
private readonly patch: PatchDbModel,
|
||||||
public marketplaceService: MarketplaceService,
|
public marketplaceService: MarketplaceService,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
async ngOnInit () {
|
async ngOnInit () {
|
||||||
|
this.pkgId = this.route.snapshot.paramMap.get('pkgId')
|
||||||
this.rec = history.state && history.state.installRec as Recommendation
|
this.rec = history.state && history.state.installRec as Recommendation
|
||||||
|
|
||||||
|
this.subs = [
|
||||||
|
this.patch.watch$('package-data', this.pkgId)
|
||||||
|
.subscribe(pkg => {
|
||||||
|
console.log(pkg)
|
||||||
|
this.installedPkg = pkg
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
|
||||||
this.getPkg()
|
this.getPkg()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnDestroy () {
|
||||||
|
this.subs.forEach(sub => sub.unsubscribe())
|
||||||
|
}
|
||||||
|
|
||||||
async getPkg (version?: string): Promise<void> {
|
async getPkg (version?: string): Promise<void> {
|
||||||
this.pkgId = this.route.snapshot.paramMap.get('pkgId')
|
|
||||||
try {
|
try {
|
||||||
await this.marketplaceService.setPkg(this.pkgId, version)
|
await this.marketplaceService.setPkg(this.pkgId, version)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
<ion-content class="ion-padding-top">
|
<ion-content class="ion-padding-top">
|
||||||
|
|
||||||
<ion-item-group *ngrxLet="patch.watch$('server-info') as server">
|
<ion-item-group>
|
||||||
<ion-item detail="true" button [routerLink]="['ssh-keys']">
|
<ion-item detail="true" button [routerLink]="['ssh-keys']">
|
||||||
<ion-label>SSH Keys</ion-label>
|
<ion-label>SSH Keys</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { Component } from '@angular/core'
|
import { Component } from '@angular/core'
|
||||||
import { ServerConfigService } from 'src/app/services/server-config.service'
|
import { ServerConfigService } from 'src/app/services/server-config.service'
|
||||||
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
|
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
|
||||||
|
import { ServerInfo } from 'src/app/models/patch-db/data-model'
|
||||||
|
import { Subscription } from 'rxjs'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'dev-options',
|
selector: 'dev-options',
|
||||||
@@ -8,12 +10,27 @@ import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
|
|||||||
styleUrls: ['./dev-options.page.scss'],
|
styleUrls: ['./dev-options.page.scss'],
|
||||||
})
|
})
|
||||||
export class DevOptionsPage {
|
export class DevOptionsPage {
|
||||||
|
server: ServerInfo = { } as any
|
||||||
|
subs: Subscription[] = []
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private readonly serverConfigService: ServerConfigService,
|
private readonly serverConfigService: ServerConfigService,
|
||||||
public readonly patch: PatchDbModel,
|
private readonly patch: PatchDbModel,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
|
ngOnInit () {
|
||||||
|
this.subs = [
|
||||||
|
this.patch.watch$('server-info')
|
||||||
|
.subscribe(server => {
|
||||||
|
this.server = server
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy () {
|
||||||
|
this.subs.forEach(sub => sub.unsubscribe())
|
||||||
|
}
|
||||||
|
|
||||||
async presentModalValueEdit (key: string, current?: any): Promise<void> {
|
async presentModalValueEdit (key: string, current?: any): Promise<void> {
|
||||||
await this.serverConfigService.presentModalValueEdit(key, current)
|
await this.serverConfigService.presentModalValueEdit(key, current)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,11 @@
|
|||||||
<pwa-back-button></pwa-back-button>
|
<pwa-back-button></pwa-back-button>
|
||||||
</ion-buttons>
|
</ion-buttons>
|
||||||
<ion-title>SSH Keys</ion-title>
|
<ion-title>SSH Keys</ion-title>
|
||||||
|
<ion-buttons slot="end" class="ion-padding-end">
|
||||||
|
<ion-button (click)="presentModalAdd()">
|
||||||
|
<ion-icon slot="icon-only" name="add-outline"></ion-icon>
|
||||||
|
</ion-button>
|
||||||
|
</ion-buttons>
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
@@ -16,7 +21,7 @@
|
|||||||
|
|
||||||
<ion-item-group>
|
<ion-item-group>
|
||||||
<ion-item-divider>Saved Keys</ion-item-divider>
|
<ion-item-divider>Saved Keys</ion-item-divider>
|
||||||
<ion-item *ngFor="let ssh of sshService.watch$() | ngrxPush | keyvalue : asIsOrder">
|
<ion-item *ngFor="let ssh of sshKeys | keyvalue : asIsOrder">
|
||||||
<ion-label class="ion-text-wrap">
|
<ion-label class="ion-text-wrap">
|
||||||
{{ ssh.value.alg }} {{ ssh.key }} {{ ssh.value.hostname }}
|
{{ ssh.value.alg }} {{ ssh.key }} {{ ssh.value.hostname }}
|
||||||
</ion-label>
|
</ion-label>
|
||||||
@@ -26,10 +31,4 @@
|
|||||||
</ion-item>
|
</ion-item>
|
||||||
</ion-item-group>
|
</ion-item-group>
|
||||||
|
|
||||||
<ion-fab vertical="bottom" horizontal="end" slot="fixed">
|
|
||||||
<ion-fab-button (click)="presentModalAdd()" class="fab-button">
|
|
||||||
<ion-icon name="add"></ion-icon>
|
|
||||||
</ion-fab-button>
|
|
||||||
</ion-fab>
|
|
||||||
|
|
||||||
</ion-content>
|
</ion-content>
|
||||||
@@ -3,6 +3,8 @@ import { ServerConfigService } from 'src/app/services/server-config.service'
|
|||||||
import { AlertController } from '@ionic/angular'
|
import { AlertController } from '@ionic/angular'
|
||||||
import { LoaderService } from 'src/app/services/loader.service'
|
import { LoaderService } from 'src/app/services/loader.service'
|
||||||
import { SSHService } from './ssh.service'
|
import { SSHService } from './ssh.service'
|
||||||
|
import { Subscription } from 'rxjs'
|
||||||
|
import { SSHKeys } from 'src/app/services/api/api-types'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'dev-ssh-keys',
|
selector: 'dev-ssh-keys',
|
||||||
@@ -12,18 +14,31 @@ import { SSHService } from './ssh.service'
|
|||||||
export class DevSSHKeysPage {
|
export class DevSSHKeysPage {
|
||||||
error = ''
|
error = ''
|
||||||
loading = true
|
loading = true
|
||||||
|
sshKeys: SSHKeys
|
||||||
|
subs: Subscription[] = []
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private readonly loader: LoaderService,
|
private readonly loader: LoaderService,
|
||||||
private readonly serverConfigService: ServerConfigService,
|
private readonly serverConfigService: ServerConfigService,
|
||||||
private readonly alertCtrl: AlertController,
|
private readonly alertCtrl: AlertController,
|
||||||
public readonly sshService: SSHService,
|
private readonly sshService: SSHService,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
ngOnInit () {
|
async ngOnInit () {
|
||||||
this.sshService.getKeys().then(() => {
|
await this.sshService.getKeys()
|
||||||
this.loading = false
|
|
||||||
})
|
this.subs = [
|
||||||
|
this.sshService.watch$()
|
||||||
|
.subscribe(keys => {
|
||||||
|
this.sshKeys = keys
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy () {
|
||||||
|
this.subs.forEach(sub => sub.unsubscribe())
|
||||||
}
|
}
|
||||||
|
|
||||||
async presentModalAdd () {
|
async presentModalAdd () {
|
||||||
|
|||||||
@@ -17,8 +17,9 @@
|
|||||||
<h2>You can connect to your Embassy over your Local Area Network (LAN). This can be useful for achieving a faster experience, as well as a fallback in case the Tor network is experiencing issues.</h2>
|
<h2>You can connect to your Embassy over your Local Area Network (LAN). This can be useful for achieving a faster experience, as well as a fallback in case the Tor network is experiencing issues.</h2>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item>
|
<ion-item [href]="docsUrl" target="_blank" detail="false">
|
||||||
<ion-button slot="start" fill="clear" color="primary" [href]="docsUrl" target="_blank">View Instructions</ion-button>
|
<ion-icon slot="start" name="list-outline"></ion-icon>
|
||||||
|
<ion-label>View Instructions</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<ng-container *ngIf="lanDisabled">
|
<ng-container *ngIf="lanDisabled">
|
||||||
@@ -39,11 +40,9 @@
|
|||||||
<h2>If you are having issues connecting to your Embassy over LAN, try refreshing the network by clicking the button below.</h2>
|
<h2>If you are having issues connecting to your Embassy over LAN, try refreshing the network by clicking the button below.</h2>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item>
|
<ion-item button (click)="refreshLAN()" detail="false">
|
||||||
<ion-button slot="start" fill="clear" (click)="refreshLAN()">
|
<ion-icon slot="start" name="refresh-outline"></ion-icon>
|
||||||
<ion-icon slot="start" name="refresh-outline"></ion-icon>
|
<ion-label>Refresh Network</ion-label>
|
||||||
Refresh Network
|
|
||||||
</ion-button>
|
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<ion-item-divider></ion-item-divider>
|
<ion-item-divider></ion-item-divider>
|
||||||
@@ -63,7 +62,7 @@
|
|||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-label class="ion-text-wrap">
|
<ion-label class="ion-text-wrap">
|
||||||
<h2>LAN Address</h2>
|
<h2>LAN Address</h2>
|
||||||
<p>https://{{ patch.watch$('server-info', 'lan-address') | ngrxPush }}</p>
|
<p>{{ lanAddress }}</p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
<ion-button slot="end" fill="clear" (click)="copyLAN()">
|
<ion-button slot="end" fill="clear" (click)="copyLAN()">
|
||||||
<ion-icon slot="icon-only" name="copy-outline"></ion-icon>
|
<ion-icon slot="icon-only" name="copy-outline"></ion-icon>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { ConfigService } from 'src/app/services/config.service'
|
|||||||
import { LoaderService } from 'src/app/services/loader.service'
|
import { LoaderService } from 'src/app/services/loader.service'
|
||||||
import { ApiService } from 'src/app/services/api/api.service'
|
import { ApiService } from 'src/app/services/api/api.service'
|
||||||
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
|
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
|
||||||
|
import { Subscription } from 'rxjs'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'lan',
|
selector: 'lan',
|
||||||
@@ -19,13 +20,14 @@ export class LANPage {
|
|||||||
NotTor: `For security reasons, you must setup LAN over a Tor connection. Please navigate to your Embassy Tor Address and try again.`,
|
NotTor: `For security reasons, you must setup LAN over a Tor connection. Please navigate to your Embassy Tor Address and try again.`,
|
||||||
}
|
}
|
||||||
readonly docsUrl = 'https://docs.start9.com/user-manual/general/lan-setup'
|
readonly docsUrl = 'https://docs.start9.com/user-manual/general/lan-setup'
|
||||||
|
subs: Subscription[] = []
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private readonly toastCtrl: ToastController,
|
private readonly toastCtrl: ToastController,
|
||||||
private readonly config: ConfigService,
|
private readonly config: ConfigService,
|
||||||
private readonly loader: LoaderService,
|
private readonly loader: LoaderService,
|
||||||
private readonly apiService: ApiService,
|
private readonly apiService: ApiService,
|
||||||
public readonly patch: PatchDbModel,
|
private readonly patch: PatchDbModel,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
ngOnInit () {
|
ngOnInit () {
|
||||||
@@ -34,6 +36,16 @@ export class LANPage {
|
|||||||
} else if (!this.config.isTor()) {
|
} else if (!this.config.isTor()) {
|
||||||
this.lanDisabled = LanSetupIssue.NOT_TOR
|
this.lanDisabled = LanSetupIssue.NOT_TOR
|
||||||
}
|
}
|
||||||
|
this.subs = [
|
||||||
|
this.patch.watch$('server-info', 'lan-address')
|
||||||
|
.subscribe(addr => {
|
||||||
|
this.lanAddress = `https://${addr}`
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy () {
|
||||||
|
this.subs.forEach(sub => sub.unsubscribe())
|
||||||
}
|
}
|
||||||
|
|
||||||
async refreshLAN (): Promise<void> {
|
async refreshLAN (): Promise<void> {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
<ion-content class="ion-padding-top">
|
<ion-content class="ion-padding-top">
|
||||||
|
|
||||||
<ion-item-group *ngrxLet="patch.watch$('ui') as ui">
|
<ion-item-group>
|
||||||
<ion-item button (click)="presentModalValueEdit('name', ui['server-name'])">
|
<ion-item button (click)="presentModalValueEdit('name', ui['server-name'])">
|
||||||
<ion-label>Embassy Name</ion-label>
|
<ion-label>Embassy Name</ion-label>
|
||||||
<ion-note slot="end">{{ ui['server-name'] }}</ion-note>
|
<ion-note slot="end">{{ ui['server-name'] }}</ion-note>
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { Component } from '@angular/core'
|
import { Component } from '@angular/core'
|
||||||
import { ServerConfigService } from 'src/app/services/server-config.service'
|
import { ServerConfigService } from 'src/app/services/server-config.service'
|
||||||
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
|
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
|
||||||
|
import { Subscription } from 'rxjs'
|
||||||
|
import { UIData } from 'src/app/models/patch-db/data-model'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'preferences',
|
selector: 'preferences',
|
||||||
@@ -8,11 +10,27 @@ import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
|
|||||||
styleUrls: ['./preferences.page.scss'],
|
styleUrls: ['./preferences.page.scss'],
|
||||||
})
|
})
|
||||||
export class PreferencesPage {
|
export class PreferencesPage {
|
||||||
|
subs: Subscription[] = []
|
||||||
|
ui: UIData = { } as any
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private readonly serverConfigService: ServerConfigService,
|
private readonly serverConfigService: ServerConfigService,
|
||||||
public readonly patch: PatchDbModel,
|
private readonly patch: PatchDbModel,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
|
ngOnInit () {
|
||||||
|
this.subs = [
|
||||||
|
this.patch.watch$('ui')
|
||||||
|
.subscribe(ui => {
|
||||||
|
this.ui = ui
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy () {
|
||||||
|
this.subs.forEach(sub => sub.unsubscribe())
|
||||||
|
}
|
||||||
|
|
||||||
async presentModalValueEdit (key: string, current?: string): Promise<void> {
|
async presentModalValueEdit (key: string, current?: string): Promise<void> {
|
||||||
await this.serverConfigService.presentModalValueEdit(key, current)
|
await this.serverConfigService.presentModalValueEdit(key, current)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,9 @@
|
|||||||
<pwa-back-button></pwa-back-button>
|
<pwa-back-button></pwa-back-button>
|
||||||
</ion-buttons>
|
</ion-buttons>
|
||||||
<ion-title>Create Backup</ion-title>
|
<ion-title>Create Backup</ion-title>
|
||||||
<ion-buttons slot="end">
|
<ion-buttons slot="end" class="ion-padding-end">
|
||||||
<ion-button (click)="doRefresh()" color="primary">
|
<ion-button (click)="doRefresh()">
|
||||||
<ion-icon slot="icon-only" name="reload-outline"></ion-icon>
|
<ion-icon slot="icon-only" name="refresh-outline"></ion-icon>
|
||||||
</ion-button>
|
</ion-button>
|
||||||
</ion-buttons>
|
</ion-buttons>
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
|
|||||||
@@ -5,8 +5,8 @@
|
|||||||
</ion-buttons>
|
</ion-buttons>
|
||||||
<ion-title>Logs</ion-title>
|
<ion-title>Logs</ion-title>
|
||||||
<ion-buttons slot="end" class="ion-padding-end">
|
<ion-buttons slot="end" class="ion-padding-end">
|
||||||
<ion-button (click)="getLogs()" color="primary">
|
<ion-button (click)="getLogs()">
|
||||||
<ion-icon name="refresh-outline"></ion-icon>
|
<ion-icon slot="icon-only" name="refresh-outline"></ion-icon>
|
||||||
</ion-button>
|
</ion-button>
|
||||||
</ion-buttons>
|
</ion-buttons>
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<ion-header>
|
<ion-header>
|
||||||
<ion-toolbar>
|
<ion-toolbar>
|
||||||
<ion-title>{{ patch.watch$('ui', 'server-name') | ngrxPush }}</ion-title>
|
<ion-title>{{ patch.watch$('ui', 'server-name') | async }}</ion-title>
|
||||||
<ion-buttons slot="end">
|
<ion-buttons slot="end">
|
||||||
<badge-menu-button></badge-menu-button>
|
<badge-menu-button></badge-menu-button>
|
||||||
</ion-buttons>
|
</ion-buttons>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
<ion-item-divider>Basic</ion-item-divider>
|
<ion-item-divider>Basic</ion-item-divider>
|
||||||
|
|
||||||
<ion-item-group *ngIf="patch.watch$('server-info') | ngrxPush as server">
|
<ion-item-group *ngIf="server">
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h2>Version</h2>
|
<h2>Version</h2>
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { Component } from '@angular/core'
|
|||||||
import { ToastController } from '@ionic/angular'
|
import { ToastController } from '@ionic/angular'
|
||||||
import { copyToClipboard } from 'src/app/util/web.util'
|
import { copyToClipboard } from 'src/app/util/web.util'
|
||||||
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
|
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
|
||||||
|
import { ServerInfo } from 'src/app/models/patch-db/data-model'
|
||||||
|
import { Subscription } from 'rxjs'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'server-specs',
|
selector: 'server-specs',
|
||||||
@@ -9,12 +11,27 @@ import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
|
|||||||
styleUrls: ['./server-specs.page.scss'],
|
styleUrls: ['./server-specs.page.scss'],
|
||||||
})
|
})
|
||||||
export class ServerSpecsPage {
|
export class ServerSpecsPage {
|
||||||
|
server: ServerInfo
|
||||||
|
subs: Subscription[] = []
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private readonly toastCtrl: ToastController,
|
private readonly toastCtrl: ToastController,
|
||||||
public readonly patch: PatchDbModel,
|
private readonly patch: PatchDbModel,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
|
ngOnInit () {
|
||||||
|
this.subs = [
|
||||||
|
this.patch.watch$('server-info')
|
||||||
|
.subscribe(server => {
|
||||||
|
this.server = server
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy () {
|
||||||
|
this.subs.forEach(sub => sub.unsubscribe())
|
||||||
|
}
|
||||||
|
|
||||||
async copy (address: string) {
|
async copy (address: string) {
|
||||||
let message = ''
|
let message = ''
|
||||||
await copyToClipboard(address || '')
|
await copyToClipboard(address || '')
|
||||||
|
|||||||
@@ -4,6 +4,11 @@
|
|||||||
<pwa-back-button></pwa-back-button>
|
<pwa-back-button></pwa-back-button>
|
||||||
</ion-buttons>
|
</ion-buttons>
|
||||||
<ion-title>WiFi Settings</ion-title>
|
<ion-title>WiFi Settings</ion-title>
|
||||||
|
<ion-buttons slot="end" class="ion-padding-end">
|
||||||
|
<ion-button [routerLink]="['add']">
|
||||||
|
<ion-icon slot="icon-only" name="add-outline"></ion-icon>
|
||||||
|
</ion-button>
|
||||||
|
</ion-buttons>
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
@@ -17,13 +22,11 @@
|
|||||||
<ion-label class="ion-text-wrap">
|
<ion-label class="ion-text-wrap">
|
||||||
<p style="padding-bottom: 6px;">About</p>
|
<p style="padding-bottom: 6px;">About</p>
|
||||||
<h2>Embassy will automatically connect to available networks, allowing you to remove the Ethernet cable.</h2>
|
<h2>Embassy will automatically connect to available networks, allowing you to remove the Ethernet cable.</h2>
|
||||||
<br />
|
|
||||||
<h2>Connecting, disconnecting, or changing WiFi networks can cause your Embassy and its services to become unreachable for up to an hour. Please be patient.</h2>
|
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<ion-item-divider>Saved Networks</ion-item-divider>
|
<ion-item-divider>Saved Networks</ion-item-divider>
|
||||||
<ng-container *ngIf="patch.watch$('server-info', 'wifi') | ngrxPush as wifi">
|
<ng-container *ngIf="wifi">
|
||||||
<ion-item button detail="false" *ngFor="let ssid of wifi.ssids" (click)="presentAction(ssid, wifi)">
|
<ion-item button detail="false" *ngFor="let ssid of wifi.ssids" (click)="presentAction(ssid, wifi)">
|
||||||
<ion-label>{{ ssid }}</ion-label>
|
<ion-label>{{ ssid }}</ion-label>
|
||||||
<ion-icon *ngIf="ssid === wifi.connected" name="wifi" color="success"></ion-icon>
|
<ion-icon *ngIf="ssid === wifi.connected" name="wifi" color="success"></ion-icon>
|
||||||
@@ -31,10 +34,4 @@
|
|||||||
</ng-container>
|
</ng-container>
|
||||||
</ion-item-group>
|
</ion-item-group>
|
||||||
|
|
||||||
<ion-fab vertical="bottom" horizontal="end" slot="fixed">
|
|
||||||
<ion-fab-button [routerLink]="['add']" class="fab-button">
|
|
||||||
<ion-icon name="add"></ion-icon>
|
|
||||||
</ion-fab-button>
|
|
||||||
</ion-fab>
|
|
||||||
|
|
||||||
</ion-content>
|
</ion-content>
|
||||||
@@ -6,6 +6,7 @@ import { WifiService } from './wifi.service'
|
|||||||
import { LoaderService } from 'src/app/services/loader.service'
|
import { LoaderService } from 'src/app/services/loader.service'
|
||||||
import { WiFiInfo } from 'src/app/models/patch-db/data-model'
|
import { WiFiInfo } from 'src/app/models/patch-db/data-model'
|
||||||
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
|
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
|
||||||
|
import { Subscription } from 'rxjs'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'wifi',
|
selector: 'wifi',
|
||||||
@@ -14,15 +15,30 @@ import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
|
|||||||
})
|
})
|
||||||
export class WifiListPage {
|
export class WifiListPage {
|
||||||
error = ''
|
error = ''
|
||||||
|
wifi: WiFiInfo
|
||||||
|
subs: Subscription[] = []
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private readonly apiService: ApiService,
|
private readonly apiService: ApiService,
|
||||||
private readonly loader: LoaderService,
|
private readonly loader: LoaderService,
|
||||||
private readonly actionCtrl: ActionSheetController,
|
private readonly actionCtrl: ActionSheetController,
|
||||||
private readonly wifiService: WifiService,
|
private readonly wifiService: WifiService,
|
||||||
public readonly patch: PatchDbModel,
|
private readonly patch: PatchDbModel,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
|
ngOnInit () {
|
||||||
|
this.subs = [
|
||||||
|
this.patch.watch$('server-info', 'wifi')
|
||||||
|
.subscribe(wifi => {
|
||||||
|
this.wifi = wifi
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy () {
|
||||||
|
this.subs.forEach(sub => sub.unsubscribe())
|
||||||
|
}
|
||||||
|
|
||||||
async presentAction (ssid: string, wifi: WiFiInfo) {
|
async presentAction (ssid: string, wifi: WiFiInfo) {
|
||||||
const buttons: ActionSheetButton[] = [
|
const buttons: ActionSheetButton[] = [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ export class WifiService {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
cssClass: 'notification-toast-error',
|
cssClass: 'notification-toast',
|
||||||
})
|
})
|
||||||
|
|
||||||
await toast.present()
|
await toast.present()
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
// import { Pipe, PipeTransform } from '@angular/core'
|
|
||||||
// import { combineLatest, Observable } from 'rxjs'
|
|
||||||
// import { map } from 'rxjs/operators'
|
|
||||||
// import { Emver } from '../services/emver.service'
|
|
||||||
|
|
||||||
|
|
||||||
// @Pipe({
|
|
||||||
// name: 'compareInstalledAndLatest',
|
|
||||||
// })
|
|
||||||
// export class InstalledLatestComparisonPipe implements PipeTransform {
|
|
||||||
// constructor (private readonly emver: Emver) { }
|
|
||||||
|
|
||||||
// transform (app: PropertySubject<AppAvailablePreview>): Observable<'not-installed' | 'installed-below' | 'installed-above' | 'installed-equal'> {
|
|
||||||
// return combineLatest([app.versionInstalled, app.versionLatest]).pipe(
|
|
||||||
// map(([i, l]) => {
|
|
||||||
// if (!i) return 'not-installed'
|
|
||||||
// switch (this.emver.compare(i, l)){
|
|
||||||
// case 0: return 'installed-equal'
|
|
||||||
// case 1: return 'installed-above'
|
|
||||||
// case -1: return 'installed-below'
|
|
||||||
// }
|
|
||||||
// }),
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// @Pipe({
|
|
||||||
// name: 'compareInstalledAndViewing',
|
|
||||||
// })
|
|
||||||
// export class InstalledViewingComparisonPipe implements PipeTransform {
|
|
||||||
// constructor (private readonly emver: Emver) { }
|
|
||||||
|
|
||||||
// transform (app: PropertySubject<AppAvailableFull>): Observable<'not-installed' | 'installed-below' | 'installed-above' | 'installed-equal'> {
|
|
||||||
// return combineLatest([app.versionInstalled, app.versionViewing]).pipe(
|
|
||||||
// map(([i, l]) => {
|
|
||||||
// if (!i) return 'not-installed'
|
|
||||||
// switch (this.emver.compare(i, l)){
|
|
||||||
// case 0: return 'installed-equal'
|
|
||||||
// case 1: return 'installed-above'
|
|
||||||
// case -1: return 'installed-below'
|
|
||||||
// }
|
|
||||||
// }),
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
@@ -5,5 +5,5 @@ import { Injectable } from '@angular/core'
|
|||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class SplitPaneTracker {
|
export class SplitPaneTracker {
|
||||||
menuFixedOpenOnLeft$: BehaviorSubject<boolean> = new BehaviorSubject(false)
|
sidebarOpen$: BehaviorSubject<boolean> = new BehaviorSubject(false)
|
||||||
}
|
}
|
||||||
@@ -58,14 +58,6 @@
|
|||||||
border-style: none;
|
border-style: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fab-button {
|
|
||||||
margin: 20px;
|
|
||||||
--background: transparent;
|
|
||||||
--border-color: var(--ion-color-primary);
|
|
||||||
--border-style: solid;
|
|
||||||
--border-width: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.help-button {
|
.help-button {
|
||||||
margin: 0 8px 0 0;
|
margin: 0 8px 0 0;
|
||||||
}
|
}
|
||||||
@@ -74,12 +66,6 @@
|
|||||||
--highlight-background: transparent !important;
|
--highlight-background: transparent !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.alert-config-value {
|
|
||||||
.alert-message.sc-ion-alert-md {
|
|
||||||
color: var(--ion-color-danger) !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.center {
|
.center {
|
||||||
display: block;
|
display: block;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
@@ -95,15 +81,6 @@
|
|||||||
--color: white;
|
--color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notification-toast-error {
|
|
||||||
--background: var(--ion-color-light);
|
|
||||||
--button-color: var(--ion-color-dark);
|
|
||||||
--border-color: var(--ion-color-danger);
|
|
||||||
--border-style: solid;
|
|
||||||
--border-width: 1px;
|
|
||||||
--color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sublist-spinner {
|
.sublist-spinner {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-top: 40px;
|
margin-top: 40px;
|
||||||
@@ -315,8 +292,3 @@ ion-item-divider {
|
|||||||
width: 16px !important;
|
width: 16px !important;
|
||||||
height: 16px !important;
|
height: 16px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dependency-item {
|
|
||||||
--padding-start: 20px;
|
|
||||||
--padding-end: 2px;
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user