mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 10:21:52 +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-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-header>
|
||||
<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>
|
||||
<ion-title><ion-spinner name="dots" color="warning"></ion-spinner></ion-title>
|
||||
</ng-template>
|
||||
@@ -24,7 +24,7 @@
|
||||
>
|
||||
<ion-icon slot="start" [name]="page.icon"></ion-icon>
|
||||
<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-menu-toggle>
|
||||
</ion-list>
|
||||
|
||||
@@ -13,6 +13,7 @@ import { PatchDbModel } from './models/patch-db/patch-db-model'
|
||||
import { HttpService } from './services/http.service'
|
||||
import { ServerStatus } from './models/patch-db/data-model'
|
||||
import { ConnectionFailure, ConnectionService } from './services/connection.service'
|
||||
import { combineLatest, merge } from 'rxjs'
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
@@ -23,8 +24,9 @@ export class AppComponent {
|
||||
ServerStatus = ServerStatus
|
||||
showMenu = false
|
||||
selectedIndex = 0
|
||||
untilLoaded = true
|
||||
offlineToast: HTMLIonToastElement
|
||||
serverName: string
|
||||
unreadCount: number
|
||||
appPages = [
|
||||
{
|
||||
title: 'Services',
|
||||
@@ -59,8 +61,8 @@ export class AppComponent {
|
||||
private readonly emver: Emver,
|
||||
private readonly connectionService: ConnectionService,
|
||||
private readonly toastCtrl: ToastController,
|
||||
private readonly patch: PatchDbModel,
|
||||
readonly splitPane: SplitPaneTracker,
|
||||
readonly patch: PatchDbModel,
|
||||
) {
|
||||
// set dark theme
|
||||
document.body.classList.toggle('dark', true)
|
||||
@@ -83,8 +85,10 @@ export class AppComponent {
|
||||
this.http.authReqEnabled = true
|
||||
this.showMenu = true
|
||||
this.patch.start()
|
||||
// watch patch DB to display name and unread count
|
||||
this.watchPatch()
|
||||
this.connectionService.start()
|
||||
// watch network
|
||||
// watch connection to display connectivity issues
|
||||
this.watchConnection(auth)
|
||||
// watch router to highlight selected menu item
|
||||
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 {
|
||||
this.connectionService.watch$()
|
||||
.pipe(
|
||||
@@ -265,7 +280,7 @@ export class AppComponent {
|
||||
}
|
||||
|
||||
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;">
|
||||
<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>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Component } from '@angular/core'
|
||||
import { Observable } from 'rxjs'
|
||||
import { SplitPaneTracker } from 'src/app/services/split-pane.service'
|
||||
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
|
||||
import { combineLatest, Subscription } from 'rxjs'
|
||||
|
||||
@Component({
|
||||
selector: 'badge-menu-button',
|
||||
@@ -10,14 +10,30 @@ import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
|
||||
})
|
||||
|
||||
export class BadgeMenuComponent {
|
||||
badge$: Observable<number>
|
||||
menuFixedOpen$: Observable<boolean>
|
||||
unreadCount: number
|
||||
sidebarOpen: boolean
|
||||
|
||||
subs: Subscription[] = []
|
||||
|
||||
constructor (
|
||||
private readonly splitPane: SplitPaneTracker,
|
||||
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-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">
|
||||
<!-- 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>
|
||||
@@ -19,7 +19,7 @@
|
||||
</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: 15px; display: flex; justify-content: center; align-items: center;">
|
||||
<ion-label color="danger" style="font-size: xx-large; font-weight: bold;">
|
||||
@@ -35,7 +35,7 @@
|
||||
|
||||
<ion-footer>
|
||||
<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 -->
|
||||
<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>
|
||||
|
||||
</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>
|
||||
</ng-container>
|
||||
</ion-toolbar>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Component, Input, NgZone, QueryList, ViewChild, ViewChildren } from '@angular/core'
|
||||
import { IonContent, IonSlides, ModalController } from '@ionic/angular'
|
||||
import { BehaviorSubject } from 'rxjs'
|
||||
import { capitalizeFirstLetter, pauseFor } from 'src/app/util/misc.util'
|
||||
import { CompleteComponent } from './complete/complete.component'
|
||||
import { DependentsComponent } from './dependents/dependents.component'
|
||||
@@ -39,8 +38,8 @@ export class InstallWizardComponent {
|
||||
return this.params.slideDefinitions[this.slideIndex].bottomBar
|
||||
}
|
||||
|
||||
initializing$ = new BehaviorSubject(true)
|
||||
error$ = new BehaviorSubject(undefined)
|
||||
initializing = true
|
||||
error = ''
|
||||
|
||||
constructor (
|
||||
private readonly modalController: ModalController,
|
||||
@@ -54,7 +53,7 @@ export class InstallWizardComponent {
|
||||
}
|
||||
|
||||
ionViewDidEnter () {
|
||||
this.initializing$.next(false)
|
||||
this.initializing = false
|
||||
}
|
||||
|
||||
// process bottom bar buttons
|
||||
@@ -62,7 +61,7 @@ export class InstallWizardComponent {
|
||||
const i = info as { next?: any, error?: Error, cancelled?: true, final?: true }
|
||||
if (i.cancelled) this.currentSlide.cancel$.next()
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
'server-info': ServerInfo
|
||||
'package-data': { [id: string]: PackageDataEntry }
|
||||
ui: {
|
||||
'server-name': string
|
||||
'welcome-ack': string
|
||||
'auto-check-updates': boolean
|
||||
}
|
||||
ui: UIData
|
||||
}
|
||||
|
||||
export interface ServerInfo {
|
||||
@@ -381,3 +377,9 @@ export interface InterfaceInfo {
|
||||
}
|
||||
|
||||
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 { TypeofPipe } from '../pipes/typeof.pipe'
|
||||
import { MarkdownPipe } from '../pipes/markdown.pipe'
|
||||
// import { InstalledLatestComparisonPipe, InstalledViewingComparisonPipe } from '../pipes/installed-latest-comparison.pipe'
|
||||
import { AnnotationStatusPipe } from '../pipes/annotation-status.pipe'
|
||||
import { TruncateCenterPipe, TruncateEndPipe } from '../pipes/truncate.pipe'
|
||||
import { MaskPipe } from '../pipes/mask.pipe'
|
||||
@@ -21,8 +20,6 @@ import { ReactiveComponentModule } from '@ngrx/component'
|
||||
TypeofPipe,
|
||||
IncludesPipe,
|
||||
MarkdownPipe,
|
||||
// InstalledLatestComparisonPipe,
|
||||
// InstalledViewingComparisonPipe,
|
||||
AnnotationStatusPipe,
|
||||
TruncateCenterPipe,
|
||||
TruncateEndPipe,
|
||||
@@ -45,8 +42,6 @@ import { ReactiveComponentModule } from '@ngrx/component'
|
||||
TypeofPipe,
|
||||
IncludesPipe,
|
||||
MarkdownPipe,
|
||||
// InstalledLatestComparisonPipe,
|
||||
// InstalledViewingComparisonPipe,
|
||||
AnnotationStatusPipe,
|
||||
TruncateEndPipe,
|
||||
TruncateCenterPipe,
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
</ion-header>
|
||||
|
||||
<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 button *ngFor="let action of installed.manifest.actions | keyvalue: asIsOrder" (click)="handleAction(installed, action)" >
|
||||
<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 { 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 { HttpErrorResponse } from '@angular/common/http'
|
||||
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 { wizardModal } from 'src/app/components/install-wizard/install-wizard.component'
|
||||
import { WizardBaker } from 'src/app/components/install-wizard/prebaked-wizards'
|
||||
import { Subscription } from 'rxjs'
|
||||
|
||||
@Component({
|
||||
selector: 'app-actions',
|
||||
@@ -15,7 +16,10 @@ import { WizardBaker } from 'src/app/components/install-wizard/prebaked-wizards'
|
||||
styleUrls: ['./app-actions.page.scss'],
|
||||
})
|
||||
export class AppActionsPage {
|
||||
pkgId: string
|
||||
installed: InstalledPackageDataEntry
|
||||
|
||||
subs: Subscription[] = []
|
||||
@ViewChild(IonContent) content: IonContent
|
||||
|
||||
constructor (
|
||||
private readonly route: ActivatedRoute,
|
||||
@@ -25,11 +29,26 @@ export class AppActionsPage {
|
||||
private readonly loaderService: LoaderService,
|
||||
private readonly wizardBaker: WizardBaker,
|
||||
private readonly navCtrl: NavController,
|
||||
public readonly patch: PatchDbModel,
|
||||
private readonly patch: PatchDbModel,
|
||||
) { }
|
||||
|
||||
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 }) {
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<ion-content class="ion-padding-top">
|
||||
|
||||
<!-- 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-col>
|
||||
<ion-spinner name="lines" color="warning"></ion-spinner>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { Component } from '@angular/core'
|
||||
import { NavController, AlertController, ModalController, PopoverController } from '@ionic/angular'
|
||||
import { Component, ViewChild } from '@angular/core'
|
||||
import { NavController, AlertController, ModalController, PopoverController, IonContent } from '@ionic/angular'
|
||||
import { ActivatedRoute } from '@angular/router'
|
||||
import { ApiService } from 'src/app/services/api/api.service'
|
||||
import { isEmptyObject } from 'src/app/util/misc.util'
|
||||
import { LoaderService } from 'src/app/services/loader.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 { Recommendation } from 'src/app/components/recommendation-button/recommendation-button.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 }
|
||||
}
|
||||
|
||||
loadingText$ = new BehaviorSubject(undefined)
|
||||
loadingText: string | undefined
|
||||
|
||||
pkg: InstalledPackageDataEntry
|
||||
hasConfig = false
|
||||
@@ -45,7 +45,8 @@ export class AppConfigPage {
|
||||
spec: ConfigSpec
|
||||
config: object
|
||||
|
||||
subs: Subscription[]
|
||||
@ViewChild(IonContent) content: IonContent
|
||||
subs: Subscription[] = []
|
||||
|
||||
constructor (
|
||||
private readonly navCtrl: NavController,
|
||||
@@ -84,47 +85,50 @@ export class AppConfigPage {
|
||||
this.navCtrl.back()
|
||||
}
|
||||
}),
|
||||
]
|
||||
|
||||
this.patch.watch$('package-data', pkgId, 'installed')
|
||||
.pipe(
|
||||
tap(pkg => this.pkg = pkg),
|
||||
tap(() => this.loadingText$.next(`Fetching config spec...`)),
|
||||
concatMap(() => this.apiService.getPackageConfig({ id: pkgId })),
|
||||
concatMap(({ spec, config }) => {
|
||||
const rec = history.state && history.state.configRecommendation as Recommendation
|
||||
if (rec) {
|
||||
this.loadingText$.next(`Setting properties to accommodate ${rec.dependentTitle}...`)
|
||||
return from(this.apiService.dryConfigureDependency({ 'dependency-id': pkgId, 'dependent-id': rec.dependentId }))
|
||||
.pipe(
|
||||
map(res => ({
|
||||
spec,
|
||||
config,
|
||||
dependencyConfig: res,
|
||||
})),
|
||||
tap(() => this.rec = rec),
|
||||
catchError(e => {
|
||||
this.error = { text: `Could not set properties to accommodate ${rec.dependentTitle}: ${e.message}`, moreInfo: {
|
||||
title: `${rec.dependentTitle} requires the following:`,
|
||||
description: rec.description,
|
||||
buttonText: 'Configure Manually',
|
||||
} }
|
||||
return of({ spec, config, dependencyConfig: null })
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
return of({ spec, config, dependencyConfig: null })
|
||||
}
|
||||
this.patch.watch$('package-data', pkgId, 'installed')
|
||||
.pipe(
|
||||
tap(pkg => this.pkg = pkg),
|
||||
tap(() => this.loadingText = 'Fetching config spec...'),
|
||||
concatMap(() => this.apiService.getPackageConfig({ id: pkgId })),
|
||||
concatMap(({ spec, config }) => {
|
||||
const rec = history.state && history.state.configRecommendation as Recommendation
|
||||
if (rec) {
|
||||
this.loadingText = `Setting properties to accommodate ${rec.dependentTitle}...`
|
||||
return from(this.apiService.dryConfigureDependency({ 'dependency-id': pkgId, 'dependent-id': rec.dependentId }))
|
||||
.pipe(
|
||||
map(res => ({
|
||||
spec,
|
||||
config,
|
||||
dependencyConfig: res,
|
||||
})),
|
||||
tap(() => this.rec = rec),
|
||||
catchError(e => {
|
||||
this.error = { text: `Could not set properties to accommodate ${rec.dependentTitle}: ${e.message}`, moreInfo: {
|
||||
title: `${rec.dependentTitle} requires the following:`,
|
||||
description: rec.description,
|
||||
buttonText: 'Configure Manually',
|
||||
} }
|
||||
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({
|
||||
error: e => {
|
||||
console.error(e.message)
|
||||
this.error = { text: e.message }
|
||||
},
|
||||
})
|
||||
]
|
||||
}
|
||||
|
||||
ngAfterViewInit () {
|
||||
this.content.scrollToPoint(undefined, 1)
|
||||
}
|
||||
|
||||
ngOnDestroy () {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { Component } from '@angular/core'
|
||||
import { Component, ViewChild } from '@angular/core'
|
||||
import { ActivatedRoute } from '@angular/router'
|
||||
import { IonContent } from '@ionic/angular'
|
||||
import { Subscription } from 'rxjs'
|
||||
import { concatMap, take, tap } from 'rxjs/operators'
|
||||
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
|
||||
import { ApiService } from 'src/app/services/api/api.service'
|
||||
import { Method } from 'src/app/services/http.service'
|
||||
|
||||
@Component({
|
||||
selector: 'app-instructions',
|
||||
@@ -15,6 +16,9 @@ export class AppInstructionsPage {
|
||||
loading = true
|
||||
error = ''
|
||||
|
||||
@ViewChild(IonContent) content: IonContent
|
||||
subs: Subscription[] = []
|
||||
|
||||
constructor (
|
||||
private readonly route: ActivatedRoute,
|
||||
private readonly apiService: ApiService,
|
||||
@@ -27,7 +31,6 @@ export class AppInstructionsPage {
|
||||
.pipe(
|
||||
concatMap(pkg => this.apiService.getStatic(pkg['static-files'].instructions)),
|
||||
tap(instructions => {
|
||||
console.log(instructions)
|
||||
this.instructions = instructions
|
||||
}),
|
||||
take(1),
|
||||
@@ -41,4 +44,12 @@ export class AppInstructionsPage {
|
||||
() => console.log('COMPLETE'),
|
||||
)
|
||||
}
|
||||
|
||||
ngAfterViewInit () {
|
||||
this.content.scrollToPoint(undefined, 1)
|
||||
}
|
||||
|
||||
ngOnDestroy () {
|
||||
this.subs.forEach(sub => sub.unsubscribe())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
</ion-header>
|
||||
|
||||
<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-header>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Component, ViewChild } from '@angular/core'
|
||||
import { ActivatedRoute } from '@angular/router'
|
||||
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 { ConfigService } from 'src/app/services/config.service'
|
||||
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'],
|
||||
})
|
||||
export class AppInterfacesPage {
|
||||
pkgId: string
|
||||
pkg: PackageDataEntry
|
||||
|
||||
@ViewChild(IonContent) content: IonContent
|
||||
subs: Subscription[] = []
|
||||
|
||||
constructor (
|
||||
private readonly route: ActivatedRoute,
|
||||
private readonly toastCtrl: ToastController,
|
||||
private readonly config: ConfigService,
|
||||
public readonly patch: PatchDbModel,
|
||||
private readonly patch: PatchDbModel,
|
||||
) { }
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
ngOnDestroy () {
|
||||
this.subs.forEach(sub => sub.unsubscribe())
|
||||
}
|
||||
|
||||
async copy (address: string): Promise<void> {
|
||||
let message = ''
|
||||
await copyToClipboard(address || '')
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
<img class="bulb-off" *ngIf="pkg.value | displayBulb: 'off' : connected" src="assets/img/off-bulb.png"/>
|
||||
|
||||
<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-header>
|
||||
</ion-card>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
}
|
||||
|
||||
ion-card-title {
|
||||
font-size: calc(8px + .7vw);
|
||||
font-size: calc(10px + .4vw);
|
||||
color: white;
|
||||
margin: 10px 0;
|
||||
white-space: nowrap;
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
</ion-buttons>
|
||||
<ion-title>Logs</ion-title>
|
||||
<ion-buttons slot="end" class="ion-padding-end">
|
||||
<ion-button (click)="getLogs()" color="primary">
|
||||
<ion-icon name="refresh-outline"></ion-icon>
|
||||
<ion-button (click)="getLogs()">
|
||||
<ion-icon slot="icon-only" name="refresh-outline"></ion-icon>
|
||||
</ion-button>
|
||||
</ion-buttons>
|
||||
</ion-toolbar>
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { Component } from '@angular/core'
|
||||
import { Component, ViewChild } from '@angular/core'
|
||||
import { ActivatedRoute } from '@angular/router'
|
||||
import { Subscription } from 'rxjs'
|
||||
import { PackageDataEntry } from 'src/app/models/patch-db/data-model'
|
||||
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
|
||||
import { getManifest } from 'src/app/services/config.service'
|
||||
import * as JsonPointer from 'json-pointer'
|
||||
import { IonContent } from '@ionic/angular'
|
||||
|
||||
@Component({
|
||||
selector: 'app-manifest',
|
||||
@@ -12,23 +13,24 @@ import * as JsonPointer from 'json-pointer'
|
||||
styleUrls: ['./app-manifest.page.scss'],
|
||||
})
|
||||
export class AppManifestPage {
|
||||
pkgId: string
|
||||
pkg: PackageDataEntry
|
||||
pointer: string
|
||||
node: object
|
||||
subs: Subscription[]
|
||||
segmentValue: 'formatted' | 'raw' = 'formatted'
|
||||
|
||||
@ViewChild(IonContent) content: IonContent
|
||||
subs: Subscription[] = []
|
||||
|
||||
constructor (
|
||||
private readonly route: ActivatedRoute,
|
||||
private readonly patch: PatchDbModel,
|
||||
) { }
|
||||
|
||||
ngOnInit () {
|
||||
this.pkgId = this.route.snapshot.paramMap.get('pkgId')
|
||||
const pkgId = this.route.snapshot.paramMap.get('pkgId')
|
||||
|
||||
this.subs = [
|
||||
this.patch.watch$('package-data', this.pkgId)
|
||||
this.patch.watch$('package-data', pkgId)
|
||||
.subscribe(pkg => {
|
||||
this.pkg = pkg
|
||||
this.setNode()
|
||||
@@ -38,6 +40,10 @@ export class AppManifestPage {
|
||||
this.setNode()
|
||||
}
|
||||
|
||||
ngAfterViewInit () {
|
||||
this.content.scrollToPoint(undefined, 1)
|
||||
}
|
||||
|
||||
ngOnDestroy () {
|
||||
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 { IonContent } from '@ionic/angular'
|
||||
import { Subscription } from 'rxjs'
|
||||
import { PackageDataEntry } from 'src/app/models/patch-db/data-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'],
|
||||
})
|
||||
export class AppMetricsPage {
|
||||
pkgId: string
|
||||
pkg: PackageDataEntry
|
||||
subs: Subscription[]
|
||||
|
||||
@ViewChild(IonContent) content: IonContent
|
||||
subs: Subscription[] = []
|
||||
|
||||
constructor (
|
||||
private readonly route: ActivatedRoute,
|
||||
@@ -20,16 +22,20 @@ export class AppMetricsPage {
|
||||
) { }
|
||||
|
||||
ngOnInit () {
|
||||
this.pkgId = this.route.snapshot.paramMap.get('pkgId')
|
||||
const pkgId = this.route.snapshot.paramMap.get('pkgId')
|
||||
|
||||
this.subs = [
|
||||
this.patch.watch$('package-data', this.pkgId)
|
||||
this.patch.watch$('package-data', pkgId)
|
||||
.subscribe(pkg => {
|
||||
this.pkg = pkg
|
||||
}),
|
||||
]
|
||||
}
|
||||
|
||||
ngAfterViewInit () {
|
||||
this.content.scrollToPoint(undefined, 1)
|
||||
}
|
||||
|
||||
ngOnDestroy () {
|
||||
this.subs.forEach(sub => sub.unsubscribe())
|
||||
}
|
||||
|
||||
@@ -4,6 +4,11 @@
|
||||
<pwa-back-button></pwa-back-button>
|
||||
</ion-buttons>
|
||||
<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-header>
|
||||
|
||||
@@ -11,64 +16,58 @@
|
||||
<ion-spinner *ngIf="loading; else loaded" class="center" name="lines" color="warning"></ion-spinner>
|
||||
|
||||
<ng-template #loaded>
|
||||
<ng-container *ngIf="patch.watch$('package-data', pkgId) | ngrxPush as pkg">
|
||||
<ion-refresher slot="fixed" (ionRefresh)="doRefresh($event)">
|
||||
<ion-refresher-content pullingIcon="lines" refreshingSpinner="lines"></ion-refresher-content>
|
||||
</ion-refresher>
|
||||
|
||||
<ion-item *ngIf="error" style="margin-bottom: 16px;">
|
||||
<ion-text class="ion-text-wrap" color="danger">{{ error }}</ion-text>
|
||||
</ion-item>
|
||||
|
||||
<!-- not running -->
|
||||
<ion-item *ngIf="pkg.installed.status.main.status !== FeStatus.Running" class="ion-margin-bottom">
|
||||
<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>
|
||||
<ion-item *ngIf="error" style="margin-bottom: 16px;">
|
||||
<ion-text class="ion-text-wrap" color="danger">{{ error }}</ion-text>
|
||||
</ion-item>
|
||||
|
||||
<!-- no properties -->
|
||||
<ion-item *ngIf="properties | empty">
|
||||
<ion-label class="ion-text-wrap">
|
||||
<p>No values.</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<!-- properties -->
|
||||
<ion-item-group *ngIf="!(properties | empty)">
|
||||
<div *ngFor="let prop of node | keyvalue: asIsOrder">
|
||||
<!-- object -->
|
||||
<ion-item button detail="true" *ngIf="prop.value.type === 'object'" (click)="goToNested(prop.key)">
|
||||
<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>
|
||||
<!-- not running -->
|
||||
<ion-item *ngIf="notRunning" class="ion-margin-bottom">
|
||||
<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>
|
||||
|
||||
<!-- no properties -->
|
||||
<ion-item *ngIf="properties | empty">
|
||||
<ion-label class="ion-text-wrap">
|
||||
<p>No values.</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<!-- properties -->
|
||||
<ion-item-group *ngIf="!(properties | empty)">
|
||||
<div *ngFor="let prop of node | keyvalue: asIsOrder">
|
||||
<!-- object -->
|
||||
<ion-item button detail="true" *ngIf="prop.value.type === 'object'" (click)="goToNested(prop.key)">
|
||||
<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>
|
||||
</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-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-label class="ion-text-wrap">
|
||||
<h2>{{ prop.key }}</h2>
|
||||
</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 *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-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 *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>
|
||||
<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-template>
|
||||
</ion-content>
|
||||
@@ -1,14 +1,15 @@
|
||||
import { Component } from '@angular/core'
|
||||
import { Component, ViewChild } from '@angular/core'
|
||||
import { ActivatedRoute } from '@angular/router'
|
||||
import { ApiService } from 'src/app/services/api/api.service'
|
||||
import { Subscription } from 'rxjs'
|
||||
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 { QRComponent } from 'src/app/components/qr/qr.component'
|
||||
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
|
||||
import * as JsonPointer from 'json-pointer'
|
||||
import { FEStatus } from 'src/app/services/pkg-status-rendering.service'
|
||||
import { PackageMainStatus } from 'src/app/models/patch-db/data-model'
|
||||
|
||||
@Component({
|
||||
selector: 'app-properties',
|
||||
@@ -24,8 +25,10 @@ export class AppPropertiesPage {
|
||||
properties: PackageProperties
|
||||
node: PackageProperties
|
||||
unmasked: { [key: string]: boolean } = { }
|
||||
FeStatus = FEStatus
|
||||
subs: Subscription[]
|
||||
running = true
|
||||
|
||||
@ViewChild(IonContent) content: IonContent
|
||||
subs: Subscription[] = []
|
||||
|
||||
constructor (
|
||||
private readonly route: ActivatedRoute,
|
||||
@@ -48,18 +51,23 @@ export class AppPropertiesPage {
|
||||
this.pointer = queryParams['pointer']
|
||||
this.node = JsonPointer.get(this.properties, this.pointer || '')
|
||||
}),
|
||||
this.patch.watch$('package-data', this.pkgId, 'installed', 'status', 'main', 'status')
|
||||
.subscribe(status => {
|
||||
this.running = status === PackageMainStatus.Running
|
||||
}),
|
||||
]
|
||||
}
|
||||
|
||||
this.loading = false
|
||||
ngAfterViewInit () {
|
||||
this.content.scrollToPoint(undefined, 1)
|
||||
}
|
||||
|
||||
ngOnDestroy () {
|
||||
this.subs.forEach(sub => sub.unsubscribe())
|
||||
}
|
||||
|
||||
async doRefresh (event: any) {
|
||||
await this.getProperties(),
|
||||
event.target.complete()
|
||||
async refresh () {
|
||||
await this.getProperties()
|
||||
}
|
||||
|
||||
async presentDescription (property: { key: string, value: PackageProperties[''] }, e: Event) {
|
||||
@@ -114,12 +122,15 @@ export class AppPropertiesPage {
|
||||
}
|
||||
|
||||
private async getProperties (): Promise<void> {
|
||||
this.loading = true
|
||||
try {
|
||||
this.properties = await this.apiService.getPackageProperties({ id: this.pkgId })
|
||||
this.node = JsonPointer.get(this.properties, this.pointer || '')
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
this.error = e.message
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,15 +4,15 @@
|
||||
<pwa-back-button></pwa-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title>Restore Backup</ion-title>
|
||||
<ion-buttons slot="end">
|
||||
<ion-button (click)="doRefresh()" color="primary">
|
||||
<ion-icon slot="icon-only" name="reload-outline"></ion-icon>
|
||||
<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-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-row class="ion-align-items-center ion-text-center" style="height: 100%;">
|
||||
@@ -32,7 +32,7 @@
|
||||
<ion-label class="ion-text-wrap">
|
||||
<p><ion-text color="dark">About</ion-text></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>
|
||||
</ion-label>
|
||||
</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 { ActivatedRoute } from '@angular/router'
|
||||
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
|
||||
import { Subscription } from 'rxjs'
|
||||
|
||||
@Component({
|
||||
selector: 'app-restore',
|
||||
@@ -14,30 +15,44 @@ import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
|
||||
export class AppRestorePage {
|
||||
disks: DiskInfo
|
||||
pkgId: string
|
||||
title: string
|
||||
loading = true
|
||||
error: string
|
||||
allPartitionsMounted: boolean
|
||||
|
||||
@ViewChild(IonContent) content: IonContent
|
||||
subs: Subscription[] = []
|
||||
|
||||
constructor (
|
||||
private readonly route: ActivatedRoute,
|
||||
private readonly modalCtrl: ModalController,
|
||||
private readonly apiService: ApiService,
|
||||
private readonly loadingCtrl: LoadingController,
|
||||
public readonly patch: PatchDbModel,
|
||||
private readonly patch: PatchDbModel,
|
||||
) { }
|
||||
|
||||
ngOnInit () {
|
||||
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()
|
||||
}
|
||||
|
||||
async ngAfterViewInit () {
|
||||
ngAfterViewInit () {
|
||||
this.content.scrollToPoint(undefined, 1)
|
||||
}
|
||||
|
||||
async doRefresh () {
|
||||
ngOnDestroy () {
|
||||
this.subs.forEach(sub => sub.unsubscribe())
|
||||
}
|
||||
|
||||
async refresh () {
|
||||
this.loading = true
|
||||
await this.getExternalDisks()
|
||||
}
|
||||
|
||||
@@ -80,41 +80,38 @@
|
||||
<ion-item-group class="ion-padding-bottom">
|
||||
<!-- dependencies -->
|
||||
<ng-container *ngIf="!(pkg.installed['current-dependencies'] | empty)">
|
||||
<ion-card id="dependencies" class="dep-card">
|
||||
<ion-card-header>
|
||||
<ion-card-title>Dependencies</ion-card-title>
|
||||
</ion-card-header>
|
||||
<ion-card-content>
|
||||
<!-- A current-dependency is a subset of the manifest.dependencies that is currently required as determined by the service config. -->
|
||||
<div *ngFor="let dep of pkg.installed['current-dependencies'] | keyvalue">
|
||||
<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>
|
||||
<ion-item-divider id="dependencies">Dependencies</ion-item-divider>
|
||||
<!-- A current-dependency is a subset of the manifest.dependencies that is currently required as determined by the service config. -->
|
||||
<ion-grid>
|
||||
<ion-row>
|
||||
<ion-col *ngFor="let dep of pkg.installed['current-dependencies'] | keyvalue" sizeSm="12" sizeMd="6">
|
||||
<ion-item *ngrxLet="patch.watch$('package-data', dep.key) as localDep">
|
||||
<ion-thumbnail slot="start">
|
||||
<img [src]="localDep ? localDep['static-files'].icon : pkg.installed.status['dependency-errors'][dep.key]?.icon" />
|
||||
</ion-avatar>
|
||||
<ion-label class="ion-text-wrap" style="padding: 1vh; padding-left: 2vh">
|
||||
<h4 style="font-family: 'Montserrat'">{{ localDep ? (localDep | manifest).title : pkg.installed.status['dependency-errors'][dep.key]?.title }}</h4>
|
||||
<p style="font-size: small">{{ 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>
|
||||
</ion-thumbnail>
|
||||
<ion-label class="ion-text-wrap">
|
||||
<h2 style="font-family: 'Montserrat'">{{ localDep ? (localDep | manifest).title : pkg.installed.status['dependency-errors'][dep.key]?.title }}</h2>
|
||||
<p>{{ manifest.dependencies[dep.key].version | displayEmver }}</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-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
|
||||
</ion-button>
|
||||
|
||||
<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
|
||||
</ion-button>
|
||||
|
||||
<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
|
||||
</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
|
||||
</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
|
||||
</ion-button>
|
||||
</ng-container>
|
||||
@@ -122,12 +119,11 @@
|
||||
<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>
|
||||
</div>
|
||||
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</ion-item>
|
||||
</div>
|
||||
</ion-card-content>
|
||||
</ion-card>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
</ion-grid>
|
||||
</ng-container>
|
||||
</ion-item-group>
|
||||
</ng-container>
|
||||
|
||||
@@ -40,13 +40,6 @@
|
||||
border-color: #404040;
|
||||
}
|
||||
|
||||
.dep-badge {
|
||||
position: absolute; width: 2.5vh;
|
||||
height: 2.5vh;
|
||||
border-radius: 50px;
|
||||
left: -1vh;
|
||||
top: -1vh;
|
||||
}
|
||||
|
||||
.dep-issue {
|
||||
background: radial-gradient(var(--ion-color-warning) 40%, transparent)
|
||||
@@ -55,3 +48,9 @@
|
||||
.dep-sat {
|
||||
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[] = []
|
||||
manifest: Manifest = { } as Manifest
|
||||
connected: boolean
|
||||
|
||||
subs: Subscription[] = []
|
||||
|
||||
FeStatus = FEStatus
|
||||
PackageState = PackageState
|
||||
DependencyErrorType = DependencyErrorType
|
||||
|
||||
@ViewChild(IonContent) content: IonContent
|
||||
subs: Subscription[] = []
|
||||
|
||||
constructor (
|
||||
private readonly alertCtrl: AlertController,
|
||||
@@ -61,7 +59,7 @@ export class AppShowPage {
|
||||
this.setButtons()
|
||||
}
|
||||
|
||||
async ngAfterViewInit () {
|
||||
ngAfterViewInit () {
|
||||
this.content.scrollToPoint(undefined, 1)
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
</ion-toolbar>
|
||||
</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>
|
||||
|
||||
<ng-template #pageLoaded>
|
||||
@@ -43,23 +43,41 @@
|
||||
</ion-button>
|
||||
</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>
|
||||
</div>
|
||||
|
||||
<ng-template #pkgsLoaded>
|
||||
<ion-grid style="margin: 0 6px;">
|
||||
<ng-template #loaded>
|
||||
<ion-grid>
|
||||
<ion-row>
|
||||
<ion-col *ngFor="let pkg of pkgs" sizeXl="2" sizeLg="3" sizeMd="3" sizeSm="4" sizeXs="4">
|
||||
<ion-card class="ion-text-center" style="margin: 0;" [routerLink]="['/marketplace', pkg.id]">
|
||||
<img [src]="pkg.icon" style="width: 90%; margin-top: 8px;" />
|
||||
<ion-card-header style="min-height: 70px; text-align: left;">
|
||||
<ion-card-title>{{ pkg.title }}</ion-card-title>
|
||||
</ion-card-header>
|
||||
<ion-card-content style="text-align: left;">
|
||||
{{ pkg.descriptionShort }}
|
||||
</ion-card-content>
|
||||
</ion-card>
|
||||
<ion-col *ngFor="let pkg of pkgs" sizeSm="12" sizeMd="6">
|
||||
<ion-item [routerLink]="['/marketplace', pkg.id]">
|
||||
<ion-thumbnail slot="start">
|
||||
<img [src]="pkg.icon" />
|
||||
</ion-thumbnail>
|
||||
<ion-label>
|
||||
<h2 style="font-family: 'Montserrat';">{{ pkg.title }}</h2>
|
||||
<p>{{ pkg.descriptionShort }}</p>
|
||||
<ng-container *ngIf="installedPkgs[pkg.id] as pkgI">
|
||||
<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-row>
|
||||
</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 {
|
||||
overflow: auto;
|
||||
white-space: nowrap;
|
||||
|
||||
@@ -5,7 +5,8 @@ import { wizardModal } from 'src/app/components/install-wizard/install-wizard.co
|
||||
import { ModalController } from '@ionic/angular'
|
||||
import { WizardBaker } from 'src/app/components/install-wizard/prebaked-wizards'
|
||||
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({
|
||||
selector: 'marketplace-list',
|
||||
@@ -23,6 +24,7 @@ export class MarketplaceListPage {
|
||||
data: MarketplaceData
|
||||
eos: MarketplaceEOS
|
||||
pkgs: AvailablePreview[] = []
|
||||
installedPkgs: { [id: string]: PackageDataEntry } = { }
|
||||
|
||||
PackageState = PackageState
|
||||
|
||||
@@ -30,14 +32,21 @@ export class MarketplaceListPage {
|
||||
needInfinite = false
|
||||
readonly perPage = 20
|
||||
|
||||
subs: Subscription[] = []
|
||||
|
||||
constructor (
|
||||
private readonly apiService: ApiService,
|
||||
private readonly modalCtrl: ModalController,
|
||||
private readonly wizardBaker: WizardBaker,
|
||||
public patch: PatchDbModel,
|
||||
private readonly patch: PatchDbModel,
|
||||
) { }
|
||||
|
||||
async ngOnInit () {
|
||||
this.subs = [
|
||||
this.patch.watch$('package-data')
|
||||
.subscribe(pkgs => this.installedPkgs = pkgs),
|
||||
]
|
||||
|
||||
try {
|
||||
const [data, eos, pkgs] = await Promise.all([
|
||||
this.apiService.getMarketplaceData({ }),
|
||||
@@ -56,6 +65,10 @@ export class MarketplaceListPage {
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy () {
|
||||
this.subs.forEach(sub => sub.unsubscribe())
|
||||
}
|
||||
|
||||
async doInfinite (e: any): Promise<void> {
|
||||
const pkgs = await this.getPkgs()
|
||||
this.pkgs = this.pkgs.concat(pkgs)
|
||||
@@ -65,6 +78,7 @@ export class MarketplaceListPage {
|
||||
async search (e?: any): Promise<void> {
|
||||
this.query = e.target.value || undefined
|
||||
this.page = 1
|
||||
this.pkgsLoading = true
|
||||
this.pkgs = await this.getPkgs()
|
||||
}
|
||||
|
||||
@@ -79,7 +93,6 @@ export class MarketplaceListPage {
|
||||
}
|
||||
|
||||
private async getPkgs (): Promise<AvailablePreview[]> {
|
||||
this.pkgsLoading = true
|
||||
try {
|
||||
const pkgs = await this.apiService.getAvailableList({
|
||||
category: this.category,
|
||||
@@ -100,6 +113,8 @@ export class MarketplaceListPage {
|
||||
|
||||
async switchCategory (category: string): Promise<void> {
|
||||
this.category = category
|
||||
this.pkgsLoading = true
|
||||
this.page = 1
|
||||
this.pkgs = await this.getPkgs()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,59 +19,57 @@
|
||||
<ion-text class="ion-text-wrap" color="danger">{{ error }}</ion-text>
|
||||
</ion-item>
|
||||
|
||||
<ng-container *ngrxLet="patch.watch$('package-data', pkgId) as localPkg">
|
||||
<ion-grid>
|
||||
<ion-row>
|
||||
<ion-col sizeXl="9" sizeLg="9" sizeMd="9" sizeSm="12" sizeXs="12">
|
||||
<div class="header">
|
||||
<img [src]="pkg.icon" />
|
||||
<div class="header-text">
|
||||
<h1 class="header-title">{{ pkg.manifest.title }}</h1>
|
||||
<p class="header-version">{{ pkg.manifest.version | displayEmver }}</p>
|
||||
<div class="header-status">
|
||||
<!-- no localPkg -->
|
||||
<p *ngIf="!localPkg; else local">
|
||||
<ion-text color="medium">Not Installed</ion-text>
|
||||
<ion-grid>
|
||||
<ion-row>
|
||||
<ion-col sizeXl="9" sizeLg="9" sizeMd="9" sizeSm="12" sizeXs="12">
|
||||
<div class="header">
|
||||
<img [src]="pkg.icon" />
|
||||
<div class="header-text">
|
||||
<h1 class="header-title">{{ pkg.manifest.title }}</h1>
|
||||
<p class="header-version">{{ pkg.manifest.version | displayEmver }}</p>
|
||||
<div class="header-status">
|
||||
<!-- no installedPkg -->
|
||||
<p *ngIf="!installedPkg; else local">
|
||||
<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>
|
||||
<!-- localPkg -->
|
||||
<ng-template #local>
|
||||
<p *ngIf="localPkg.state !== PackageState.Installed; else installed">
|
||||
<!-- installing, updating, removing -->
|
||||
<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>
|
||||
<!-- installed -->
|
||||
<ng-template #installed>
|
||||
<p>
|
||||
<ion-text color="medium">Installed at {{ installedPkg.installed.manifest.version | displayEmver }}</ion-text>
|
||||
</p>
|
||||
<!-- installed -->
|
||||
<ng-template #installed>
|
||||
<p>
|
||||
<ion-text color="medium">Installed at {{ localPkg.installed.manifest.version | displayEmver }}</ion-text>
|
||||
</p>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
</div>
|
||||
</ng-template>
|
||||
</div>
|
||||
</div>
|
||||
</ion-col>
|
||||
<ion-col sizeXl="3" sizeLg="3" sizeMd="3" sizeSm="12" sizeXs="12" class="ion-align-self-center">
|
||||
<!-- no localPkg -->
|
||||
<ion-button *ngIf="!localPkg; else localPkg2" class="main-action-button" expand="block" (click)="install()">
|
||||
Install
|
||||
</ion-button>
|
||||
<!-- localPkg -->
|
||||
<ng-template #localPkg2>
|
||||
<!-- not installing, updating, or removing -->
|
||||
<ng-container *ngIf="localPkg.state === PackageState.Installed">
|
||||
<ion-button *ngIf="(localPkg.installed.manifest.version | compareEmver : pkg.manifest.version) === -1" class="main-action-button" expand="block" (click)="update('update')">
|
||||
Update
|
||||
</ion-button>
|
||||
<ion-button *ngIf="(localPkg.installed.manifest.version | compareEmver : pkg.manifest.version) === 1" class="main-action-button" expand="block" color="warning" (click)="update('downgrade')">
|
||||
Downgrade
|
||||
</ion-button>
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
</ion-grid>
|
||||
</ng-container>
|
||||
</div>
|
||||
</ion-col>
|
||||
<ion-col sizeXl="3" sizeLg="3" sizeMd="3" sizeSm="12" sizeXs="12" class="ion-align-self-center">
|
||||
<!-- no installedPkg -->
|
||||
<ion-button *ngIf="!installedPkg; else installedPkg2" class="main-action-button" expand="block" (click)="install()">
|
||||
Install
|
||||
</ion-button>
|
||||
<!-- installedPkg -->
|
||||
<ng-template #installedPkg2>
|
||||
<!-- not installing, updating, or removing -->
|
||||
<ng-container *ngIf="installedPkg.state === PackageState.Installed">
|
||||
<ion-button *ngIf="(installedPkg.installed.manifest.version | compareEmver : pkg.manifest.version) === -1" class="main-action-button" expand="block" (click)="update('update')">
|
||||
Update
|
||||
</ion-button>
|
||||
<ion-button *ngIf="(installedPkg.installed.manifest.version | compareEmver : pkg.manifest.version) === 1" class="main-action-button" expand="block" color="warning" (click)="update('downgrade')">
|
||||
Downgrade
|
||||
</ion-button>
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
</ion-grid>
|
||||
|
||||
<!-- recommendation -->
|
||||
<ion-item *ngIf="rec && showRec" class="rec-item">
|
||||
@@ -121,24 +119,20 @@
|
||||
<ion-item-divider>Dependencies</ion-item-divider>
|
||||
<ion-grid>
|
||||
<ion-row>
|
||||
<ion-col *ngFor="let dep of pkg.manifest.dependencies | keyvalue" sizeMd="6" sizeSm="12" sizeXs="12">
|
||||
<ion-card *ngIf="!dep.value.optional" style="--background: #171717" [routerLink]="['/marketplace', dep.key]">
|
||||
<ion-item color="transparent" lines="none">
|
||||
<ion-avatar slot="start">
|
||||
<img [src]="pkg['dependency-metadata'][dep.key].icon" />
|
||||
</ion-avatar>
|
||||
<ion-label class="ion-text-wrap">
|
||||
<h2>
|
||||
{{ pkg['dependency-metadata'][dep.key].title }}
|
||||
<span *ngIf="dep.value.recommended"> (recommended)</span>
|
||||
</h2>
|
||||
<p style="font-size: small">{{ dep.value.version | displayEmver }}</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-card-content>
|
||||
{{ dep.value.description }}
|
||||
</ion-card-content>
|
||||
</ion-card>
|
||||
<ion-col *ngFor="let dep of pkg.manifest.dependencies | keyvalue" sizeSm="12" sizeMd="6">
|
||||
<ion-item *ngIf="!dep.value.optional" [routerLink]="['/marketplace', dep.key]">
|
||||
<ion-thumbnail slot="start">
|
||||
<img [src]="pkg['dependency-metadata'][dep.key].icon" />
|
||||
</ion-thumbnail>
|
||||
<ion-label class="ion-text-wrap">
|
||||
<h2>
|
||||
{{ pkg['dependency-metadata'][dep.key].title }}
|
||||
<span *ngIf="dep.value.recommended"> (recommended)</span>
|
||||
</h2>
|
||||
<p style="font-size: small">{{ dep.value.version | displayEmver }}</p>
|
||||
<p>{{ dep.value.description }}</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
</ion-grid>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
font-family: 'Montserrat';
|
||||
padding: 2%;
|
||||
img {
|
||||
min-width: 16%;
|
||||
min-width: 15%;
|
||||
max-width: 18%;
|
||||
}
|
||||
.header-text {
|
||||
@@ -10,12 +10,11 @@
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
.header-title {
|
||||
line-height: .65;
|
||||
margin: 0 0 0 -2px;
|
||||
font-size: calc(20px + 3vw)
|
||||
}
|
||||
.header-version {
|
||||
padding: 12px 0;
|
||||
padding: 4px 0 12px 0;
|
||||
margin: 0;
|
||||
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 { pauseFor } from 'src/app/util/misc.util'
|
||||
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 { Subscription } from 'rxjs'
|
||||
|
||||
@Component({
|
||||
selector: 'marketplace-show',
|
||||
@@ -19,12 +20,13 @@ import { MarketplaceService } from '../marketplace.service'
|
||||
export class MarketplaceShowPage {
|
||||
error = ''
|
||||
pkgId: string
|
||||
|
||||
installedPkg: PackageDataEntry
|
||||
PackageState = PackageState
|
||||
|
||||
rec: Recommendation | null = null
|
||||
showRec = true
|
||||
|
||||
subs: Subscription[] = []
|
||||
|
||||
constructor (
|
||||
private readonly route: ActivatedRoute,
|
||||
private readonly alertCtrl: AlertController,
|
||||
@@ -32,17 +34,30 @@ export class MarketplaceShowPage {
|
||||
private readonly wizardBaker: WizardBaker,
|
||||
private readonly navCtrl: NavController,
|
||||
private readonly emver: Emver,
|
||||
public readonly patch: PatchDbModel,
|
||||
private readonly patch: PatchDbModel,
|
||||
public marketplaceService: MarketplaceService,
|
||||
) { }
|
||||
|
||||
async ngOnInit () {
|
||||
this.pkgId = this.route.snapshot.paramMap.get('pkgId')
|
||||
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()
|
||||
}
|
||||
|
||||
ngOnDestroy () {
|
||||
this.subs.forEach(sub => sub.unsubscribe())
|
||||
}
|
||||
|
||||
async getPkg (version?: string): Promise<void> {
|
||||
this.pkgId = this.route.snapshot.paramMap.get('pkgId')
|
||||
try {
|
||||
await this.marketplaceService.setPkg(this.pkgId, version)
|
||||
} catch (e) {
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
<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-label>SSH Keys</ion-label>
|
||||
</ion-item>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { Component } from '@angular/core'
|
||||
import { ServerConfigService } from 'src/app/services/server-config.service'
|
||||
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({
|
||||
selector: 'dev-options',
|
||||
@@ -8,12 +10,27 @@ import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
|
||||
styleUrls: ['./dev-options.page.scss'],
|
||||
})
|
||||
export class DevOptionsPage {
|
||||
server: ServerInfo = { } as any
|
||||
subs: Subscription[] = []
|
||||
|
||||
constructor (
|
||||
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> {
|
||||
await this.serverConfigService.presentModalValueEdit(key, current)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,11 @@
|
||||
<pwa-back-button></pwa-back-button>
|
||||
</ion-buttons>
|
||||
<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-header>
|
||||
|
||||
@@ -16,7 +21,7 @@
|
||||
|
||||
<ion-item-group>
|
||||
<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">
|
||||
{{ ssh.value.alg }} {{ ssh.key }} {{ ssh.value.hostname }}
|
||||
</ion-label>
|
||||
@@ -26,10 +31,4 @@
|
||||
</ion-item>
|
||||
</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>
|
||||
@@ -3,6 +3,8 @@ import { ServerConfigService } from 'src/app/services/server-config.service'
|
||||
import { AlertController } from '@ionic/angular'
|
||||
import { LoaderService } from 'src/app/services/loader.service'
|
||||
import { SSHService } from './ssh.service'
|
||||
import { Subscription } from 'rxjs'
|
||||
import { SSHKeys } from 'src/app/services/api/api-types'
|
||||
|
||||
@Component({
|
||||
selector: 'dev-ssh-keys',
|
||||
@@ -12,18 +14,31 @@ import { SSHService } from './ssh.service'
|
||||
export class DevSSHKeysPage {
|
||||
error = ''
|
||||
loading = true
|
||||
sshKeys: SSHKeys
|
||||
subs: Subscription[] = []
|
||||
|
||||
constructor (
|
||||
private readonly loader: LoaderService,
|
||||
private readonly serverConfigService: ServerConfigService,
|
||||
private readonly alertCtrl: AlertController,
|
||||
public readonly sshService: SSHService,
|
||||
private readonly sshService: SSHService,
|
||||
) { }
|
||||
|
||||
ngOnInit () {
|
||||
this.sshService.getKeys().then(() => {
|
||||
this.loading = false
|
||||
})
|
||||
async ngOnInit () {
|
||||
await this.sshService.getKeys()
|
||||
|
||||
this.subs = [
|
||||
this.sshService.watch$()
|
||||
.subscribe(keys => {
|
||||
this.sshKeys = keys
|
||||
}),
|
||||
]
|
||||
|
||||
this.loading = false
|
||||
}
|
||||
|
||||
ngOnDestroy () {
|
||||
this.subs.forEach(sub => sub.unsubscribe())
|
||||
}
|
||||
|
||||
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>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-button slot="start" fill="clear" color="primary" [href]="docsUrl" target="_blank">View Instructions</ion-button>
|
||||
<ion-item [href]="docsUrl" target="_blank" detail="false">
|
||||
<ion-icon slot="start" name="list-outline"></ion-icon>
|
||||
<ion-label>View Instructions</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<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>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-button slot="start" fill="clear" (click)="refreshLAN()">
|
||||
<ion-icon slot="start" name="refresh-outline"></ion-icon>
|
||||
Refresh Network
|
||||
</ion-button>
|
||||
<ion-item button (click)="refreshLAN()" detail="false">
|
||||
<ion-icon slot="start" name="refresh-outline"></ion-icon>
|
||||
<ion-label>Refresh Network</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-item-divider></ion-item-divider>
|
||||
@@ -63,7 +62,7 @@
|
||||
<ion-item>
|
||||
<ion-label class="ion-text-wrap">
|
||||
<h2>LAN Address</h2>
|
||||
<p>https://{{ patch.watch$('server-info', 'lan-address') | ngrxPush }}</p>
|
||||
<p>{{ lanAddress }}</p>
|
||||
</ion-label>
|
||||
<ion-button slot="end" fill="clear" (click)="copyLAN()">
|
||||
<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 { ApiService } from 'src/app/services/api/api.service'
|
||||
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
|
||||
import { Subscription } from 'rxjs'
|
||||
|
||||
@Component({
|
||||
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.`,
|
||||
}
|
||||
readonly docsUrl = 'https://docs.start9.com/user-manual/general/lan-setup'
|
||||
subs: Subscription[] = []
|
||||
|
||||
constructor (
|
||||
private readonly toastCtrl: ToastController,
|
||||
private readonly config: ConfigService,
|
||||
private readonly loader: LoaderService,
|
||||
private readonly apiService: ApiService,
|
||||
public readonly patch: PatchDbModel,
|
||||
private readonly patch: PatchDbModel,
|
||||
) { }
|
||||
|
||||
ngOnInit () {
|
||||
@@ -34,6 +36,16 @@ export class LANPage {
|
||||
} else if (!this.config.isTor()) {
|
||||
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> {
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
<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-label>Embassy Name</ion-label>
|
||||
<ion-note slot="end">{{ ui['server-name'] }}</ion-note>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { Component } from '@angular/core'
|
||||
import { ServerConfigService } from 'src/app/services/server-config.service'
|
||||
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({
|
||||
selector: 'preferences',
|
||||
@@ -8,11 +10,27 @@ import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
|
||||
styleUrls: ['./preferences.page.scss'],
|
||||
})
|
||||
export class PreferencesPage {
|
||||
subs: Subscription[] = []
|
||||
ui: UIData = { } as any
|
||||
|
||||
constructor (
|
||||
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> {
|
||||
await this.serverConfigService.presentModalValueEdit(key, current)
|
||||
}
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
<pwa-back-button></pwa-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title>Create Backup</ion-title>
|
||||
<ion-buttons slot="end">
|
||||
<ion-button (click)="doRefresh()" color="primary">
|
||||
<ion-icon slot="icon-only" name="reload-outline"></ion-icon>
|
||||
<ion-buttons slot="end" class="ion-padding-end">
|
||||
<ion-button (click)="doRefresh()">
|
||||
<ion-icon slot="icon-only" name="refresh-outline"></ion-icon>
|
||||
</ion-button>
|
||||
</ion-buttons>
|
||||
</ion-toolbar>
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
</ion-buttons>
|
||||
<ion-title>Logs</ion-title>
|
||||
<ion-buttons slot="end" class="ion-padding-end">
|
||||
<ion-button (click)="getLogs()" color="primary">
|
||||
<ion-icon name="refresh-outline"></ion-icon>
|
||||
<ion-button (click)="getLogs()">
|
||||
<ion-icon slot="icon-only" name="refresh-outline"></ion-icon>
|
||||
</ion-button>
|
||||
</ion-buttons>
|
||||
</ion-toolbar>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<ion-header>
|
||||
<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">
|
||||
<badge-menu-button></badge-menu-button>
|
||||
</ion-buttons>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
<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-label>
|
||||
<h2>Version</h2>
|
||||
|
||||
@@ -2,6 +2,8 @@ import { Component } from '@angular/core'
|
||||
import { ToastController } from '@ionic/angular'
|
||||
import { copyToClipboard } from 'src/app/util/web.util'
|
||||
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({
|
||||
selector: 'server-specs',
|
||||
@@ -9,12 +11,27 @@ import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
|
||||
styleUrls: ['./server-specs.page.scss'],
|
||||
})
|
||||
export class ServerSpecsPage {
|
||||
server: ServerInfo
|
||||
subs: Subscription[] = []
|
||||
|
||||
constructor (
|
||||
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) {
|
||||
let message = ''
|
||||
await copyToClipboard(address || '')
|
||||
|
||||
@@ -4,6 +4,11 @@
|
||||
<pwa-back-button></pwa-back-button>
|
||||
</ion-buttons>
|
||||
<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-header>
|
||||
|
||||
@@ -17,13 +22,11 @@
|
||||
<ion-label class="ion-text-wrap">
|
||||
<p style="padding-bottom: 6px;">About</p>
|
||||
<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-item>
|
||||
|
||||
<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-label>{{ ssid }}</ion-label>
|
||||
<ion-icon *ngIf="ssid === wifi.connected" name="wifi" color="success"></ion-icon>
|
||||
@@ -31,10 +34,4 @@
|
||||
</ng-container>
|
||||
</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>
|
||||
@@ -6,6 +6,7 @@ import { WifiService } from './wifi.service'
|
||||
import { LoaderService } from 'src/app/services/loader.service'
|
||||
import { WiFiInfo } from 'src/app/models/patch-db/data-model'
|
||||
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
|
||||
import { Subscription } from 'rxjs'
|
||||
|
||||
@Component({
|
||||
selector: 'wifi',
|
||||
@@ -14,15 +15,30 @@ import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
|
||||
})
|
||||
export class WifiListPage {
|
||||
error = ''
|
||||
wifi: WiFiInfo
|
||||
subs: Subscription[] = []
|
||||
|
||||
constructor (
|
||||
private readonly apiService: ApiService,
|
||||
private readonly loader: LoaderService,
|
||||
private readonly actionCtrl: ActionSheetController,
|
||||
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) {
|
||||
const buttons: ActionSheetButton[] = [
|
||||
{
|
||||
|
||||
@@ -57,7 +57,7 @@ export class WifiService {
|
||||
},
|
||||
},
|
||||
],
|
||||
cssClass: 'notification-toast-error',
|
||||
cssClass: 'notification-toast',
|
||||
})
|
||||
|
||||
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',
|
||||
})
|
||||
export class SplitPaneTracker {
|
||||
menuFixedOpenOnLeft$: BehaviorSubject<boolean> = new BehaviorSubject(false)
|
||||
sidebarOpen$: BehaviorSubject<boolean> = new BehaviorSubject(false)
|
||||
}
|
||||
@@ -58,14 +58,6 @@
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
.fab-button {
|
||||
margin: 20px;
|
||||
--background: transparent;
|
||||
--border-color: var(--ion-color-primary);
|
||||
--border-style: solid;
|
||||
--border-width: 2px;
|
||||
}
|
||||
|
||||
.help-button {
|
||||
margin: 0 8px 0 0;
|
||||
}
|
||||
@@ -74,12 +66,6 @@
|
||||
--highlight-background: transparent !important;
|
||||
}
|
||||
|
||||
.alert-config-value {
|
||||
.alert-message.sc-ion-alert-md {
|
||||
color: var(--ion-color-danger) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.center {
|
||||
display: block;
|
||||
margin: auto;
|
||||
@@ -95,15 +81,6 @@
|
||||
--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 {
|
||||
text-align: center;
|
||||
margin-top: 40px;
|
||||
@@ -315,8 +292,3 @@ ion-item-divider {
|
||||
width: 16px !important;
|
||||
height: 16px !important;
|
||||
}
|
||||
|
||||
.dependency-item {
|
||||
--padding-start: 20px;
|
||||
--padding-end: 2px;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user