mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 10:21:52 +00:00
refactor loaders, better err toasts, rework Embassy tab organization
This commit is contained in:
committed by
Aiden McClelland
parent
2ff9c622ac
commit
eb245aea50
@@ -3,12 +3,11 @@ import { Storage } from '@ionic/storage'
|
||||
import { AuthService, AuthState } from './services/auth.service'
|
||||
import { ApiService } from './services/api/embassy/embassy-api.service'
|
||||
import { Router, RoutesRecognized } from '@angular/router'
|
||||
import { debounceTime, distinctUntilChanged, filter, finalize, skip, take, takeWhile } from 'rxjs/operators'
|
||||
import { AlertController, IonicSafeString, ToastController } from '@ionic/angular'
|
||||
import { LoaderService } from './services/loader.service'
|
||||
import { debounceTime, distinctUntilChanged, filter, take, takeWhile } from 'rxjs/operators'
|
||||
import { AlertController, IonicSafeString, LoadingController, ToastController } from '@ionic/angular'
|
||||
import { Emver } from './services/emver.service'
|
||||
import { SplitPaneTracker } from './services/split-pane.service'
|
||||
import { LoadingOptions, ToastButton } from '@ionic/core'
|
||||
import { ToastButton } from '@ionic/core'
|
||||
import { PatchDbService } from './services/patch-db/patch-db.service'
|
||||
import { HttpService } from './services/http.service'
|
||||
import { ServerStatus } from './services/patch-db/data-model'
|
||||
@@ -17,6 +16,7 @@ import { StartupAlertsService } from './services/startup-alerts.service'
|
||||
import { ConfigService } from './services/config.service'
|
||||
import { isEmptyObject } from './util/misc.util'
|
||||
import { MarketplaceApiService } from './services/api/marketplace/marketplace-api.service'
|
||||
import { ErrorToastService } from './services/error-toast.service'
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
@@ -32,7 +32,7 @@ export class AppComponent {
|
||||
unreadCount: number
|
||||
appPages = [
|
||||
{
|
||||
title: 'Installed Services',
|
||||
title: 'Services',
|
||||
url: '/services',
|
||||
icon: 'grid-outline',
|
||||
},
|
||||
@@ -42,7 +42,7 @@ export class AppComponent {
|
||||
icon: 'cube-outline',
|
||||
},
|
||||
{
|
||||
title: 'Service Marketplace',
|
||||
title: 'Marketplace',
|
||||
url: '/marketplace',
|
||||
icon: 'storefront-outline',
|
||||
},
|
||||
@@ -60,12 +60,13 @@ export class AppComponent {
|
||||
private readonly embassyApi: ApiService,
|
||||
private readonly http: HttpService,
|
||||
private readonly alertCtrl: AlertController,
|
||||
private readonly loader: LoaderService,
|
||||
private readonly emver: Emver,
|
||||
private readonly connectionService: ConnectionService,
|
||||
private readonly marketplaceApi: MarketplaceApiService,
|
||||
private readonly startupAlertsService: StartupAlertsService,
|
||||
private readonly toastCtrl: ToastController,
|
||||
private readonly loadingCtrl: LoadingController,
|
||||
private readonly errToast: ErrorToastService,
|
||||
private readonly patch: PatchDbService,
|
||||
private readonly config: ConfigService,
|
||||
readonly splitPane: SplitPaneTracker,
|
||||
@@ -198,8 +199,8 @@ export class AppComponent {
|
||||
.subscribe(status => {
|
||||
const maintenance = '/maintenance'
|
||||
const route = this.router.url
|
||||
console.log('STATUS', status, 'URL', route)
|
||||
if (status === ServerStatus.Running && route.startsWith(maintenance)) {
|
||||
this.showMenu = true
|
||||
this.router.navigate([''], { replaceUrl: true })
|
||||
}
|
||||
if ([ServerStatus.Updating, ServerStatus.BackingUp].includes(status) && !route.startsWith(maintenance)) {
|
||||
@@ -215,6 +216,7 @@ export class AppComponent {
|
||||
takeWhile(() => auth === AuthState.VERIFIED),
|
||||
)
|
||||
.subscribe(version => {
|
||||
console.log('VERSIONS', this.config.version, version)
|
||||
if (this.emver.compare(this.config.version, version) !== 0) {
|
||||
this.presentAlertRefreshNeeded()
|
||||
}
|
||||
@@ -275,10 +277,21 @@ export class AppComponent {
|
||||
}
|
||||
|
||||
private async logout () {
|
||||
this.loader.of(LoadingSpinner('Logging out...'))
|
||||
.displayDuringP(this.embassyApi.logout({ }))
|
||||
.then(() => this.authService.setUnverified())
|
||||
.catch(e => this.setError(e))
|
||||
const loader = await this.loadingCtrl.create({
|
||||
spinner: 'lines',
|
||||
message: 'Logging out...',
|
||||
cssClass: 'loader',
|
||||
})
|
||||
await loader.present()
|
||||
|
||||
try {
|
||||
await this.embassyApi.logout({ })
|
||||
this.authService.setUnverified()
|
||||
} catch (e) {
|
||||
await this.errToast.present(e)
|
||||
} finally {
|
||||
loader.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
private async presentToastNotifications () {
|
||||
@@ -347,35 +360,7 @@ export class AppComponent {
|
||||
await this.offlineToast.present()
|
||||
}
|
||||
|
||||
private async setError (e: Error) {
|
||||
console.error(e)
|
||||
await this.presentError(e.message)
|
||||
}
|
||||
|
||||
private async presentError (e: string) {
|
||||
const alert = await this.alertCtrl.create({
|
||||
backdropDismiss: true,
|
||||
message: `Exception on logout: ${e}`,
|
||||
buttons: [
|
||||
{
|
||||
text: 'Dismiss',
|
||||
role: 'cancel',
|
||||
},
|
||||
],
|
||||
})
|
||||
await alert.present()
|
||||
}
|
||||
|
||||
splitPaneVisible (e: any) {
|
||||
this.splitPane.sidebarOpen$.next(e.detail.visible)
|
||||
}
|
||||
}
|
||||
|
||||
const LoadingSpinner: (m?: string) => LoadingOptions = (m) => {
|
||||
const toMergeIn = m ? { message: m } : { }
|
||||
return {
|
||||
spinner: 'lines',
|
||||
cssClass: 'loader',
|
||||
...toMergeIn,
|
||||
} as LoadingOptions
|
||||
}
|
||||
@@ -1,9 +1,8 @@
|
||||
import { Component, Input, OnInit } from '@angular/core'
|
||||
import { BehaviorSubject, from, Subject } from 'rxjs'
|
||||
import { takeUntil } from 'rxjs/operators'
|
||||
import { markAsLoadingDuring$ } from 'src/app/services/loader.service'
|
||||
import { capitalizeFirstLetter } from 'src/app/util/misc.util'
|
||||
import { Loadable } from '../loadable'
|
||||
import { Loadable, markAsLoadingDuring$ } from '../loadable'
|
||||
import { WizardAction } from '../wizard-types'
|
||||
|
||||
@Component({
|
||||
|
||||
@@ -2,9 +2,8 @@ import { Component, Input, OnInit } from '@angular/core'
|
||||
import { BehaviorSubject, from, Subject } from 'rxjs'
|
||||
import { takeUntil, tap } from 'rxjs/operators'
|
||||
import { Breakages } from 'src/app/services/api/api.types'
|
||||
import { markAsLoadingDuring$ } from 'src/app/services/loader.service'
|
||||
import { capitalizeFirstLetter, isEmptyObject } from 'src/app/util/misc.util'
|
||||
import { Loadable } from '../loadable'
|
||||
import { Loadable, markAsLoadingDuring$ } from '../loadable'
|
||||
import { WizardAction } from '../wizard-types'
|
||||
|
||||
@Component({
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { BehaviorSubject, Subject } from 'rxjs'
|
||||
import { BehaviorSubject, Observable, Subject } from 'rxjs'
|
||||
import { concatMap, finalize } from 'rxjs/operators'
|
||||
import { fromSync$, emitAfter$ } from 'src/app/util/rxjs.util'
|
||||
|
||||
export interface Loadable {
|
||||
load: (prevResult?: any) => void
|
||||
@@ -7,3 +9,16 @@ export interface Loadable {
|
||||
cancel$: Subject<void> // will cancel load function
|
||||
}
|
||||
|
||||
export function markAsLoadingDuring$<T> ($trigger$: Subject<boolean>, o: Observable<T>): Observable<T> {
|
||||
let shouldBeOn = true
|
||||
const displayIfItsBeenAtLeast = 5 // ms
|
||||
return fromSync$(() => {
|
||||
emitAfter$(displayIfItsBeenAtLeast).subscribe(() => { if (shouldBeOn) $trigger$.next(true) })
|
||||
}).pipe(
|
||||
concatMap(() => o),
|
||||
finalize(() => {
|
||||
$trigger$.next(false)
|
||||
shouldBeOn = false
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Component, Input } from '@angular/core'
|
||||
import { LoadingController, ModalController } from '@ionic/angular'
|
||||
import { ConfigCursor } from 'src/app/pkg-config/config-cursor'
|
||||
import { ValueSpecObject } from 'src/app/pkg-config/config-types'
|
||||
import { LoaderService } from 'src/app/services/loader.service'
|
||||
import { ErrorToastService } from 'src/app/services/error-toast.service'
|
||||
import { Action } from 'src/app/services/patch-db/data-model'
|
||||
|
||||
@Component({
|
||||
@@ -20,8 +20,8 @@ export class AppActionInputPage {
|
||||
|
||||
constructor (
|
||||
private readonly modalCtrl: ModalController,
|
||||
private readonly errToast: ErrorToastService,
|
||||
private readonly loadingCtrl: LoadingController,
|
||||
private loaderService: LoaderService,
|
||||
) { }
|
||||
|
||||
ngOnInit () {
|
||||
@@ -35,18 +35,21 @@ export class AppActionInputPage {
|
||||
}
|
||||
|
||||
async save (): Promise<void> {
|
||||
this.loaderService.of({
|
||||
const loader = await this.loadingCtrl.create({
|
||||
spinner: 'lines',
|
||||
message: 'Executing action',
|
||||
cssClass: 'loader-ontop-of-all',
|
||||
}).displayDuringAsync(async () => {
|
||||
try {
|
||||
await this.execute()
|
||||
this.modalCtrl.dismiss()
|
||||
} catch (e) {
|
||||
this.error = e.message
|
||||
}
|
||||
})
|
||||
await loader.present()
|
||||
|
||||
try {
|
||||
await this.execute()
|
||||
this.modalCtrl.dismiss()
|
||||
} catch (e) {
|
||||
this.errToast.present(e)
|
||||
} finally {
|
||||
loader.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
handleObjectEdit (): void {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Component, Input } from '@angular/core'
|
||||
import { getDefaultConfigValue, getDefaultDescription, Range } from 'src/app/pkg-config/config-utilities'
|
||||
import { AlertController, ModalController, ToastController } from '@ionic/angular'
|
||||
import { LoaderService } from 'src/app/services/loader.service'
|
||||
import { AlertController, LoadingController, ModalController, ToastController } from '@ionic/angular'
|
||||
import { ConfigCursor } from 'src/app/pkg-config/config-cursor'
|
||||
import { ValueSpecOf } from 'src/app/pkg-config/config-types'
|
||||
import { copyToClipboard } from 'src/app/util/web.util'
|
||||
import { ErrorToastService } from 'src/app/services/error-toast.service'
|
||||
|
||||
@Component({
|
||||
selector: 'app-config-value',
|
||||
@@ -29,10 +29,11 @@ export class AppConfigValuePage {
|
||||
rangeDescription: string
|
||||
|
||||
constructor (
|
||||
private readonly loader: LoaderService,
|
||||
private readonly loadingCtrl: LoadingController,
|
||||
private readonly modalCtrl: ModalController,
|
||||
private readonly alertCtrl: AlertController,
|
||||
private readonly toastCtrl: ToastController,
|
||||
private readonly errToast: ErrorToastService,
|
||||
) { }
|
||||
|
||||
ngOnInit () {
|
||||
@@ -68,14 +69,21 @@ export class AppConfigValuePage {
|
||||
this.value = Number(this.value)
|
||||
}
|
||||
|
||||
this.loader.displayDuringP(
|
||||
this.saveFn(this.value).catch(e => {
|
||||
console.error(e)
|
||||
this.error = e.message
|
||||
}),
|
||||
)
|
||||
const loader = await this.loadingCtrl.create({
|
||||
spinner: 'lines',
|
||||
message: 'Saving...',
|
||||
cssClass: 'loader',
|
||||
})
|
||||
await loader.present()
|
||||
|
||||
await this.modalCtrl.dismiss(this.value)
|
||||
try {
|
||||
await this.saveFn(this.value)
|
||||
this.modalCtrl.dismiss(this.value)
|
||||
} catch (e) {
|
||||
this.errToast.present(e)
|
||||
} finally {
|
||||
loader.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
refreshDefault () {
|
||||
|
||||
@@ -20,10 +20,10 @@
|
||||
</div>
|
||||
|
||||
<div style="display: flex; justify-content: flex-end; align-items: center;">
|
||||
<ion-button fill="clear" color="medium" (click)="cancel()">
|
||||
<ion-button fill="clear" (click)="cancel()">
|
||||
Cancel
|
||||
</ion-button>
|
||||
<ion-button fill="clear" (click)="submit()">
|
||||
<ion-button fill="clear" (click)="submit()" [disabled]="!password.length">
|
||||
{{ type === 'backup' ? 'Create Backup' : 'Restore Backup' }}
|
||||
</ion-button>
|
||||
</div>
|
||||
|
||||
@@ -24,8 +24,7 @@ export class MarkdownPage {
|
||||
try {
|
||||
this.content = await this.embassyApi.getStatic(this.contentUrl)
|
||||
} catch (e) {
|
||||
console.error(e.message)
|
||||
this.errToast.present(e.message)
|
||||
this.errToast.present(e)
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title >
|
||||
<ion-label style="font-size: 20px;" class="ion-text-wrap">Welcome to {{ version }}!</ion-label>
|
||||
</ion-title>
|
||||
<ion-title>Welcome to {{ version }}!</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content class="ion-padding">
|
||||
<div style="display: flex; flex-direction: column; justify-content: space-between; height: 100%">
|
||||
<h2>Highlights</h2>
|
||||
<h2>A Whole New Embassy</h2>
|
||||
<div class="main-content">
|
||||
<p>This release fixes a bug with certificate generation that caused the Embassy web interface to become inaccessible</p>
|
||||
<p>Version {{ version }} is something new.</p>
|
||||
<p>This release also enables displaying Service license information and contains utilities to facilitate the next major release of EmbassyOS.</p>
|
||||
</div>
|
||||
|
||||
<div class="close-button">
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { Component, Input } from '@angular/core'
|
||||
import { ModalController } from '@ionic/angular'
|
||||
import { ApiService } from 'src/app/services/api/embassy/embassy-api.service'
|
||||
import { ConfigService } from 'src/app/services/config.service'
|
||||
|
||||
@Component({
|
||||
selector: 'os-welcome',
|
||||
@@ -13,16 +11,9 @@ export class OSWelcomePage {
|
||||
|
||||
constructor (
|
||||
private readonly modalCtrl: ModalController,
|
||||
private readonly embassyApi: ApiService,
|
||||
private readonly config: ConfigService,
|
||||
) { }
|
||||
|
||||
async dismiss () {
|
||||
this.embassyApi.setDbValue({ pointer: '/welcome-ack', value: this.config.version })
|
||||
.catch(console.error)
|
||||
|
||||
// return false to skip subsequent alert modals (e.g. check for updates modals)
|
||||
// return true to show subsequent alert modals
|
||||
return this.modalCtrl.dismiss(true)
|
||||
return this.modalCtrl.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
import { Component, ViewChild } from '@angular/core'
|
||||
import { ActivatedRoute } from '@angular/router'
|
||||
import { ApiService } from 'src/app/services/api/embassy/embassy-api.service'
|
||||
import { AlertController, IonContent, ModalController, NavController } from '@ionic/angular'
|
||||
import { LoaderService } from 'src/app/services/loader.service'
|
||||
import { HttpErrorResponse } from '@angular/common/http'
|
||||
import { AlertController, IonContent, LoadingController, ModalController, NavController } from '@ionic/angular'
|
||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||
import { Action, Manifest, PackageDataEntry, PackageMainStatus } from 'src/app/services/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'
|
||||
import { AppConfigObjectPage } from 'src/app/modals/app-config-object/app-config-object.page'
|
||||
import { ConfigCursor } from 'src/app/pkg-config/config-cursor'
|
||||
import { AppActionInputPage } from 'src/app/modals/app-action-input/app-action-input.page'
|
||||
import { ErrorToastService } from 'src/app/services/error-toast.service'
|
||||
|
||||
@Component({
|
||||
selector: 'app-actions',
|
||||
@@ -29,7 +27,8 @@ export class AppActionsPage {
|
||||
private readonly embassyApi: ApiService,
|
||||
private readonly modalCtrl: ModalController,
|
||||
private readonly alertCtrl: AlertController,
|
||||
private readonly loaderService: LoaderService,
|
||||
private readonly errToast: ErrorToastService,
|
||||
private readonly loadingCtrl: LoadingController,
|
||||
private readonly wizardBaker: WizardBaker,
|
||||
private readonly navCtrl: NavController,
|
||||
public readonly patch: PatchDbService,
|
||||
@@ -120,11 +119,16 @@ export class AppActionsPage {
|
||||
return this.navCtrl.navigateRoot('/services')
|
||||
}
|
||||
|
||||
private async executeAction (pkgId: string, actionId: string) {
|
||||
private async executeAction (pkgId: string, actionId: string): Promise<void> {
|
||||
const loader = await this.loadingCtrl.create({
|
||||
spinner: 'lines',
|
||||
message: 'Executing action...',
|
||||
cssClass: 'loader',
|
||||
})
|
||||
await loader.present()
|
||||
|
||||
try {
|
||||
const res = await this.loaderService.displayDuringP(
|
||||
this.embassyApi.executePackageAction({ id: pkgId, 'action-id': actionId }),
|
||||
)
|
||||
const res = await this.embassyApi.executePackageAction({ id: pkgId, 'action-id': actionId })
|
||||
|
||||
const successAlert = await this.alertCtrl.create({
|
||||
header: 'Execution Complete',
|
||||
@@ -132,23 +136,11 @@ export class AppActionsPage {
|
||||
buttons: ['OK'],
|
||||
cssClass: 'alert-success-message',
|
||||
})
|
||||
return await successAlert.present()
|
||||
await successAlert.present()
|
||||
} catch (e) {
|
||||
if (e instanceof HttpErrorResponse) {
|
||||
this.presentAlertActionFail(e.status, e.message)
|
||||
} else {
|
||||
this.presentAlertActionFail(-1, e.message || JSON.stringify(e))
|
||||
}
|
||||
this.errToast.present(e)
|
||||
} finally {
|
||||
loader.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
private async presentAlertActionFail (code: number, message: string): Promise<void> {
|
||||
const failureAlert = await this.alertCtrl.create({
|
||||
header: 'Execution Failed',
|
||||
message: `Error code ${code}. ${message}`,
|
||||
buttons: ['OK'],
|
||||
cssClass: 'alert-error-message',
|
||||
})
|
||||
return await failureAlert.present()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { Component, ViewChild } from '@angular/core'
|
||||
import { NavController, AlertController, ModalController, IonContent } from '@ionic/angular'
|
||||
import { NavController, AlertController, ModalController, IonContent, LoadingController } from '@ionic/angular'
|
||||
import { ActivatedRoute } from '@angular/router'
|
||||
import { ApiService } from 'src/app/services/api/embassy/embassy-api.service'
|
||||
import { isEmptyObject, Recommendation } 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 { from, fromEvent, of, Subscription } from 'rxjs'
|
||||
import { catchError, concatMap, map, take, tap } from 'rxjs/operators'
|
||||
@@ -13,6 +12,7 @@ import { ConfigSpec } from 'src/app/pkg-config/config-types'
|
||||
import { ConfigCursor } from 'src/app/pkg-config/config-cursor'
|
||||
import { PackageDataEntry, PackageState } from 'src/app/services/patch-db/data-model'
|
||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||
import { ErrorToastService } from 'src/app/services/error-toast.service'
|
||||
|
||||
@Component({
|
||||
selector: 'app-config',
|
||||
@@ -51,7 +51,8 @@ export class AppConfigPage {
|
||||
private readonly route: ActivatedRoute,
|
||||
private readonly wizardBaker: WizardBaker,
|
||||
private readonly embassyApi: ApiService,
|
||||
private readonly loader: LoaderService,
|
||||
private readonly errToast: ErrorToastService,
|
||||
private readonly loadingCtrl: LoadingController,
|
||||
private readonly alertCtrl: AlertController,
|
||||
private readonly modalController: ModalController,
|
||||
private readonly trackingModalCtrl: TrackingModalController,
|
||||
@@ -85,7 +86,7 @@ export class AppConfigPage {
|
||||
this.patch.watch$('package-data', pkgId)
|
||||
.pipe(
|
||||
tap(pkg => this.pkg = pkg),
|
||||
tap(() => this.loadingText = 'Fetching config spec...'),
|
||||
tap(() => this.loadingText = 'Loading config...'),
|
||||
concatMap(() => this.embassyApi.getPackageConfig({ id: pkgId })),
|
||||
concatMap(({ spec, config }) => {
|
||||
const rec = history.state && history.state.configRecommendation as Recommendation
|
||||
@@ -157,11 +158,14 @@ export class AppConfigPage {
|
||||
}
|
||||
|
||||
async save (pkg: PackageDataEntry) {
|
||||
return this.loader.of({
|
||||
message: `Saving config...`,
|
||||
const loader = await this.loadingCtrl.create({
|
||||
spinner: 'lines',
|
||||
message: `Saving config...`,
|
||||
cssClass: 'loader',
|
||||
}).displayDuringAsync(async () => {
|
||||
})
|
||||
await loader.present()
|
||||
|
||||
try {
|
||||
const breakages = await this.embassyApi.drySetPackageConfig({ id: pkg.manifest.id, config: this.config })
|
||||
|
||||
if (!isEmptyObject(breakages.length)) {
|
||||
@@ -172,17 +176,16 @@ export class AppConfigPage {
|
||||
breakages,
|
||||
}),
|
||||
)
|
||||
if (cancelled) return { skip: true }
|
||||
if (cancelled) return
|
||||
}
|
||||
|
||||
return this.embassyApi.setPackageConfig({ id: pkg.manifest.id, config: this.config })
|
||||
.then(() => ({ skip: false }))
|
||||
})
|
||||
.then(({ skip }) => {
|
||||
if (skip) return
|
||||
await this.embassyApi.setPackageConfig({ id: pkg.manifest.id, config: this.config })
|
||||
this.navCtrl.back()
|
||||
})
|
||||
.catch(e => this.error = { text: e.message })
|
||||
} catch (e) {
|
||||
this.errToast.present(e)
|
||||
} finally {
|
||||
loader.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
handleObjectEdit () {
|
||||
|
||||
@@ -31,8 +31,7 @@ export class AppInstructionsPage {
|
||||
try {
|
||||
this.instructions = await this.embassyApi.getStatic(url)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
this.errToast.present(e.message)
|
||||
this.errToast.present(e)
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
|
||||
@@ -33,8 +33,7 @@ export class AppLogsPage {
|
||||
this.logs = logs.map(l => `${l.timestamp} ${l.log}`).join('\n\n')
|
||||
setTimeout(async () => await this.content.scrollToBottom(100), 200)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
this.errToast.present(e.message)
|
||||
this.errToast.present(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,8 +68,7 @@ export class AppMetricsPage {
|
||||
try {
|
||||
this.metrics = await this.embassyApi.getPkgMetrics({ id: this.pkgId})
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
this.errToast.present(e.message)
|
||||
this.errToast.present(e)
|
||||
this.stopDaemon()
|
||||
} finally {
|
||||
this.loading = false
|
||||
|
||||
@@ -122,8 +122,7 @@ export class AppPropertiesPage {
|
||||
this.properties = await this.embassyApi.getPackageProperties({ id: this.pkgId })
|
||||
this.node = JsonPointer.get(this.properties, this.pointer || '')
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
this.errToast.present(e.message)
|
||||
this.errToast.present(e)
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
|
||||
@@ -32,36 +32,29 @@
|
||||
<ion-text class="ion-text-wrap" color="warning">No partitions available. Insert the storage device containing the backup you intend to restore.</ion-text>
|
||||
</ion-item>
|
||||
|
||||
<ion-grid>
|
||||
<ion-row>
|
||||
<ion-col *ngFor="let disk of disks | keyvalue" sizeSm="12" sizeMd="6">
|
||||
<ion-card>
|
||||
<ion-card-header>
|
||||
<ion-card-title>
|
||||
{{ disk.value.size }}
|
||||
</ion-card-title>
|
||||
<ion-card-subtitle>
|
||||
{{ disk.key }}
|
||||
</ion-card-subtitle>
|
||||
</ion-card-header>
|
||||
<ion-card-content>
|
||||
<ion-item-group>
|
||||
<ion-item button *ngFor="let partition of disk.value.partitions | keyvalue" [disabled]="partition.value['is-mounted']" (click)="presentModal(partition.key)">
|
||||
<ion-icon slot="start" name="save-outline"></ion-icon>
|
||||
<ion-label>
|
||||
<h2>{{ partition.value.label || partition.key }} ({{ partition.value.size || 'unknown size' }})</h2>
|
||||
<p *ngIf="!partition.value['is-mounted']; else unavailable"><ion-text color="success">Available</ion-text></p>
|
||||
<ng-template #unavailable>
|
||||
<p><ion-text color="danger">Unavailable</ion-text></p>
|
||||
</ng-template>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-item-group>
|
||||
</ion-card-content>
|
||||
</ion-card>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
</ion-grid>
|
||||
<ion-card *ngFor="let disk of disks | keyvalue">
|
||||
<ion-card-header>
|
||||
<ion-card-title>
|
||||
{{ disk.value.size }}
|
||||
</ion-card-title>
|
||||
<ion-card-subtitle>
|
||||
{{ disk.key }}
|
||||
</ion-card-subtitle>
|
||||
</ion-card-header>
|
||||
<ion-card-content>
|
||||
<ion-item-group>
|
||||
<ion-item button *ngFor="let partition of disk.value.partitions | keyvalue" [disabled]="partition.value['is-mounted']" (click)="presentModal(partition.key)">
|
||||
<ion-icon slot="start" name="save-outline"></ion-icon>
|
||||
<ion-label>
|
||||
<h2>{{ partition.value.label || partition.key }} ({{ partition.value.size || 'unknown size' }})</h2>
|
||||
<p *ngIf="!partition.value['is-mounted']; else unavailable"><ion-text color="success">Available</ion-text></p>
|
||||
<ng-template #unavailable>
|
||||
<p><ion-text color="danger">Unavailable</ion-text></p>
|
||||
</ng-template>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-item-group>
|
||||
</ion-card-content>
|
||||
</ion-card>
|
||||
</ng-template>
|
||||
|
||||
</ion-content>
|
||||
|
||||
@@ -7,6 +7,7 @@ import { ActivatedRoute } from '@angular/router'
|
||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||
import { Subscription } from 'rxjs'
|
||||
import { take } from 'rxjs/operators'
|
||||
import { ErrorToastService } from 'src/app/services/error-toast.service'
|
||||
|
||||
@Component({
|
||||
selector: 'app-restore',
|
||||
@@ -18,7 +19,6 @@ export class AppRestorePage {
|
||||
pkgId: string
|
||||
title: string
|
||||
loading = true
|
||||
error: string
|
||||
allPartitionsMounted: boolean
|
||||
|
||||
@ViewChild(IonContent) content: IonContent
|
||||
@@ -29,6 +29,7 @@ export class AppRestorePage {
|
||||
private readonly modalCtrl: ModalController,
|
||||
private readonly embassyApi: ApiService,
|
||||
private readonly loadingCtrl: LoadingController,
|
||||
private readonly errToast: ErrorToastService,
|
||||
public readonly patch: PatchDbService,
|
||||
) { }
|
||||
|
||||
@@ -51,8 +52,7 @@ export class AppRestorePage {
|
||||
this.disks = await this.embassyApi.getDisks({ })
|
||||
this.allPartitionsMounted = Object.values(this.disks).every(d => Object.values(d.partitions).every(p => p['is-mounted']))
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
this.error = e.message
|
||||
this.errToast.present(e)
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
@@ -74,12 +74,10 @@ export class AppRestorePage {
|
||||
this.restore(logicalname, data.password)
|
||||
})
|
||||
|
||||
return await m.present()
|
||||
await m.present()
|
||||
}
|
||||
|
||||
private async restore (logicalname: string, password: string): Promise<void> {
|
||||
this.error = ''
|
||||
|
||||
const loader = await this.loadingCtrl.create({
|
||||
spinner: 'lines',
|
||||
})
|
||||
@@ -92,8 +90,7 @@ export class AppRestorePage {
|
||||
password,
|
||||
})
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
this.error = e.message
|
||||
this.errToast.present(e)
|
||||
} finally {
|
||||
loader.dismiss()
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { Component, ViewChild } from '@angular/core'
|
||||
import { AlertController, NavController, ModalController, IonContent } from '@ionic/angular'
|
||||
import { AlertController, NavController, ModalController, IonContent, LoadingController } from '@ionic/angular'
|
||||
import { ApiService } from 'src/app/services/api/embassy/embassy-api.service'
|
||||
import { ActivatedRoute, NavigationExtras } from '@angular/router'
|
||||
import { chill, isEmptyObject, Recommendation } from 'src/app/util/misc.util'
|
||||
import { LoaderService } from 'src/app/services/loader.service'
|
||||
import { combineLatest, Observable, of, Subscription } from 'rxjs'
|
||||
import { combineLatest, Subscription } from 'rxjs'
|
||||
import { wizardModal } from 'src/app/components/install-wizard/install-wizard.component'
|
||||
import { WizardBaker } from 'src/app/components/install-wizard/prebaked-wizards'
|
||||
import { ConfigService } from 'src/app/services/config.service'
|
||||
@@ -38,7 +37,7 @@ export class AppShowPage {
|
||||
private readonly route: ActivatedRoute,
|
||||
private readonly navCtrl: NavController,
|
||||
private readonly errToast: ErrorToastService,
|
||||
private readonly loader: LoaderService,
|
||||
private readonly loadingCtrl: LoadingController,
|
||||
private readonly modalCtrl: ModalController,
|
||||
private readonly embassyApi: ApiService,
|
||||
private readonly wizardBaker: WizardBaker,
|
||||
@@ -77,11 +76,14 @@ export class AppShowPage {
|
||||
|
||||
async stop (): Promise<void> {
|
||||
const { id, title, version } = this.pkg.manifest
|
||||
await this.loader.of({
|
||||
const loader = await this.loadingCtrl.create({
|
||||
message: `Stopping...`,
|
||||
spinner: 'lines',
|
||||
cssClass: 'loader',
|
||||
}).displayDuringAsync(async () => {
|
||||
})
|
||||
await loader.present()
|
||||
|
||||
try {
|
||||
const breakages = await this.embassyApi.dryStopPackage({ id })
|
||||
|
||||
console.log('BREAKAGES', breakages)
|
||||
@@ -96,12 +98,14 @@ export class AppShowPage {
|
||||
breakages,
|
||||
}),
|
||||
)
|
||||
|
||||
if (cancelled) return { }
|
||||
if (cancelled) return
|
||||
}
|
||||
|
||||
return this.embassyApi.stopPackage({ id }).then(chill)
|
||||
}).catch(e => this.setError(e))
|
||||
} catch (e) {
|
||||
this.errToast.present(e)
|
||||
} finally {
|
||||
loader.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
async tryStart (): Promise<void> {
|
||||
@@ -206,19 +210,20 @@ export class AppShowPage {
|
||||
}
|
||||
|
||||
private async start (): Promise<void> {
|
||||
this.loader.of({
|
||||
const loader = await this.loadingCtrl.create({
|
||||
message: `Starting...`,
|
||||
spinner: 'lines',
|
||||
cssClass: 'loader',
|
||||
}).displayDuringP(
|
||||
this.embassyApi.startPackage({ id: this.pkgId }),
|
||||
).catch(e => this.setError(e))
|
||||
}
|
||||
})
|
||||
await loader.present()
|
||||
|
||||
private setError (e: Error): Observable<void> {
|
||||
console.error(e)
|
||||
this.errToast.present(e.message)
|
||||
return of()
|
||||
try {
|
||||
await this.embassyApi.startPackage({ id: this.pkgId })
|
||||
} catch (e) {
|
||||
this.errToast.present(e)
|
||||
} finally {
|
||||
loader.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
setButtons (): void {
|
||||
|
||||
@@ -68,8 +68,7 @@ export class MarketplaceListPage {
|
||||
this.data.categories = [this.category, 'updates'].concat(filterdCategories).concat(['all'])
|
||||
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
this.errToast.present(e.message)
|
||||
this.errToast.present(e)
|
||||
} finally {
|
||||
this.pageLoading = false
|
||||
this.pkgsLoading = false
|
||||
@@ -128,8 +127,7 @@ export class MarketplaceListPage {
|
||||
this.pkgs = doInfinite ? this.pkgs.concat(pkgs) : pkgs
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
this.errToast.present(e.message)
|
||||
this.errToast.present(e)
|
||||
} finally {
|
||||
this.pkgsLoading = false
|
||||
}
|
||||
|
||||
@@ -71,8 +71,7 @@ export class MarketplaceShowPage {
|
||||
try {
|
||||
await this.marketplaceService.getPkg(this.pkgId, version)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
this.errToast.present(e.message)
|
||||
this.errToast.present(e)
|
||||
} finally {
|
||||
await pauseFor(100)
|
||||
this.loading = false
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { Component } from '@angular/core'
|
||||
import { ApiService } from 'src/app/services/api/embassy/embassy-api.service'
|
||||
import { LoaderService } from 'src/app/services/loader.service'
|
||||
import { ServerNotification, ServerNotifications } from 'src/app/services/api/api.types'
|
||||
import { AlertController } from '@ionic/angular'
|
||||
import { AlertController, LoadingController } from '@ionic/angular'
|
||||
import { ActivatedRoute } from '@angular/router'
|
||||
import { ErrorToastService } from 'src/app/services/error-toast.service'
|
||||
|
||||
@@ -21,7 +20,7 @@ export class NotificationsPage {
|
||||
|
||||
constructor (
|
||||
private readonly embassyApi: ApiService,
|
||||
private readonly loader: LoaderService,
|
||||
private readonly loadingCtrl: LoadingController,
|
||||
private readonly errToast: ErrorToastService,
|
||||
private readonly alertCtrl: AlertController,
|
||||
private readonly route: ActivatedRoute,
|
||||
@@ -52,26 +51,28 @@ export class NotificationsPage {
|
||||
this.needInfinite = notifications.length >= this.perPage
|
||||
this.page++
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
this.errToast.present(e.message)
|
||||
this.errToast.present(e)
|
||||
} finally {
|
||||
return notifications
|
||||
}
|
||||
}
|
||||
|
||||
async remove (id: string, index: number): Promise<void> {
|
||||
this.loader.of({
|
||||
message: 'Deleting...',
|
||||
const loader = await this.loadingCtrl.create({
|
||||
spinner: 'lines',
|
||||
message: 'Deleting...',
|
||||
cssClass: 'loader',
|
||||
}).displayDuringP(
|
||||
this.embassyApi.deleteNotification({ id }).then(() => {
|
||||
this.notifications.splice(index, 1)
|
||||
}),
|
||||
).catch(e => {
|
||||
console.error(e)
|
||||
this.errToast.present(e.message)
|
||||
})
|
||||
await loader.present()
|
||||
|
||||
try {
|
||||
await this.embassyApi.deleteNotification({ id })
|
||||
this.notifications.splice(index, 1)
|
||||
} catch (e) {
|
||||
this.errToast.present(e)
|
||||
} finally {
|
||||
loader.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
async viewBackupReport (notification: ServerNotification<1>) {
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<pwa-back-button></pwa-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title>Developer Options</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content class="ion-padding-top" *ngIf="patch.data['server-info'] as server">
|
||||
|
||||
<ion-item-group>
|
||||
<ion-item detail="true" button [routerLink]="['ssh-keys']">
|
||||
<ion-label>SSH Keys</ion-label>
|
||||
</ion-item>
|
||||
</ion-item-group>
|
||||
|
||||
</ion-content>
|
||||
@@ -1,20 +0,0 @@
|
||||
import { Component } from '@angular/core'
|
||||
import { ServerConfigService } from 'src/app/services/server-config.service'
|
||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||
|
||||
@Component({
|
||||
selector: 'dev-options',
|
||||
templateUrl: './dev-options.page.html',
|
||||
styleUrls: ['./dev-options.page.scss'],
|
||||
})
|
||||
export class DevOptionsPage {
|
||||
|
||||
constructor (
|
||||
private readonly serverConfigService: ServerConfigService,
|
||||
public readonly patch: PatchDbService,
|
||||
) { }
|
||||
|
||||
async presentModalValueEdit (key: string, current?: any): Promise<void> {
|
||||
await this.serverConfigService.presentModalValueEdit(key, current)
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,10 @@
|
||||
<ion-icon slot="start" name="list-outline"></ion-icon>
|
||||
<ion-label>View Instructions</ion-label>
|
||||
</ion-item>
|
||||
<ion-item button (click)="installCert()" [disabled]="lanDisabled">
|
||||
<ion-icon slot="start" name="download-outline"></ion-icon>
|
||||
<ion-label>Download Root Certificate Authority</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ng-container *ngIf="lanDisabled">
|
||||
<ion-item-divider></ion-item-divider>
|
||||
@@ -37,38 +41,13 @@
|
||||
<ion-item>
|
||||
<ion-label class="ion-text-wrap">
|
||||
<p style="padding-bottom: 6px;">Troubleshooting</p>
|
||||
<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 your LAN services by clicking the button below.</h2>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item button (click)="refreshLAN()" detail="false">
|
||||
<ion-icon slot="start" name="refresh-outline"></ion-icon>
|
||||
<ion-label>Refresh Network</ion-label>
|
||||
<ion-label>Refresh LAN</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-item-divider></ion-item-divider>
|
||||
|
||||
<!-- Certificate and Lan Address -->
|
||||
<ng-container *ngIf="!lanDisabled">
|
||||
<ion-item>
|
||||
<ion-label class="ion-text-wrap">
|
||||
<h2>Root Certificate Authority</h2>
|
||||
<p>Embassy Local CA</p>
|
||||
</ion-label>
|
||||
<ion-button slot="end" fill="clear" (click)="installCert()">
|
||||
<ion-icon slot="icon-only" name="download-outline"></ion-icon>
|
||||
</ion-button>
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
<ion-label class="ion-text-wrap">
|
||||
<h2>LAN Address</h2>
|
||||
<p>{{ lanAddress }}</p>
|
||||
</ion-label>
|
||||
<ion-button slot="end" fill="clear" (click)="copyLAN()">
|
||||
<ion-icon slot="icon-only" name="copy-outline"></ion-icon>
|
||||
</ion-button>
|
||||
</ion-item>
|
||||
</ng-container>
|
||||
</ion-item-group>
|
||||
|
||||
<!-- hidden element for downloading cert -->
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { Component } from '@angular/core'
|
||||
import { isPlatform, ToastController } from '@ionic/angular'
|
||||
import { isPlatform, LoadingController, ToastController } from '@ionic/angular'
|
||||
import { copyToClipboard } from 'src/app/util/web.util'
|
||||
import { ConfigService } from 'src/app/services/config.service'
|
||||
import { LoaderService } from 'src/app/services/loader.service'
|
||||
import { ApiService } from 'src/app/services/api/embassy/embassy-api.service'
|
||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||
import { Subscription } from 'rxjs'
|
||||
import { ErrorToastService } from 'src/app/services/error-toast.service'
|
||||
|
||||
@Component({
|
||||
selector: 'lan',
|
||||
@@ -25,7 +25,8 @@ export class LANPage {
|
||||
constructor (
|
||||
private readonly toastCtrl: ToastController,
|
||||
private readonly config: ConfigService,
|
||||
private readonly loader: LoaderService,
|
||||
private readonly loadingCtrl: LoadingController,
|
||||
private readonly errToast: ErrorToastService,
|
||||
private readonly embassyApi: ApiService,
|
||||
private readonly patch: PatchDbService,
|
||||
) { }
|
||||
@@ -49,15 +50,21 @@ export class LANPage {
|
||||
}
|
||||
|
||||
async refreshLAN (): Promise<void> {
|
||||
this.loader.of({
|
||||
message: 'Refreshing Network',
|
||||
const loader = await this.loadingCtrl.create({
|
||||
spinner: 'lines',
|
||||
message: 'Refreshing LAN...',
|
||||
cssClass: 'loader',
|
||||
}).displayDuringAsync( async () => {
|
||||
await this.embassyApi.refreshLan({ })
|
||||
}).catch(e => {
|
||||
console.error(e)
|
||||
})
|
||||
await loader.present()
|
||||
|
||||
try {
|
||||
await this.embassyApi.refreshLan({ })
|
||||
this.presentToastSuccess()
|
||||
} catch (e) {
|
||||
this.errToast.present(e)
|
||||
} finally {
|
||||
loader.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
async copyLAN (): Promise <void> {
|
||||
@@ -74,6 +81,27 @@ export class LANPage {
|
||||
installCert (): void {
|
||||
document.getElementById('install-cert').click()
|
||||
}
|
||||
|
||||
private async presentToastSuccess (): Promise<void> {
|
||||
const toast = await this.toastCtrl.create({
|
||||
header: 'Success',
|
||||
message: `LAN refreshed.`,
|
||||
position: 'bottom',
|
||||
duration: 3000,
|
||||
buttons: [
|
||||
{
|
||||
side: 'start',
|
||||
icon: 'close',
|
||||
handler: () => {
|
||||
return true
|
||||
},
|
||||
},
|
||||
],
|
||||
cssClass: 'success-toast',
|
||||
})
|
||||
|
||||
await toast.present()
|
||||
}
|
||||
}
|
||||
|
||||
enum LanSetupIssue {
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
import { NgModule } from '@angular/core'
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { IonicModule } from '@ionic/angular'
|
||||
import { PrivacyPage } from './privacy.page'
|
||||
import { Routes, RouterModule } from '@angular/router'
|
||||
import { SharingModule } from 'src/app/modules/sharing.module'
|
||||
import { PwaBackComponentModule } from 'src/app/components/pwa-back-button/pwa-back.component.module'
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: PrivacyPage,
|
||||
},
|
||||
]
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
SharingModule,
|
||||
RouterModule.forChild(routes),
|
||||
PwaBackComponentModule,
|
||||
],
|
||||
declarations: [
|
||||
PrivacyPage,
|
||||
],
|
||||
})
|
||||
export class PrivacyPageModule { }
|
||||
@@ -1,7 +1,7 @@
|
||||
import { NgModule } from '@angular/core'
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { IonicModule } from '@ionic/angular'
|
||||
import { DevOptionsPage } from './dev-options.page'
|
||||
import { SecurityOptionsPage } from './security-options.page'
|
||||
import { Routes, RouterModule } from '@angular/router'
|
||||
import { PwaBackComponentModule } from 'src/app/components/pwa-back-button/pwa-back.component.module'
|
||||
import { ObjectConfigComponentModule } from 'src/app/components/object-config/object-config.component.module'
|
||||
@@ -10,7 +10,7 @@ import { SharingModule } from 'src/app/modules/sharing.module'
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: DevOptionsPage,
|
||||
component: SecurityOptionsPage,
|
||||
},
|
||||
]
|
||||
|
||||
@@ -24,7 +24,7 @@ const routes: Routes = [
|
||||
SharingModule,
|
||||
],
|
||||
declarations: [
|
||||
DevOptionsPage,
|
||||
SecurityOptionsPage,
|
||||
],
|
||||
})
|
||||
export class DevOptionsPageModule { }
|
||||
export class SecurityOptionsPageModule { }
|
||||
@@ -7,28 +7,37 @@
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content class="ion-padding-top">
|
||||
<ion-content class="ion-padding-top" *ngIf="patch.data['server-info'] as server">
|
||||
|
||||
<ion-item-group>
|
||||
<ion-item-divider>Marketplace Settings</ion-item-divider>
|
||||
<ion-item button (click)="presentModalValueEdit('eosMarketplace', patch.data['server-info']['eos-marketplace'])">
|
||||
<ion-label>Use Tor</ion-label>
|
||||
<ion-item-divider>General</ion-item-divider>
|
||||
<ion-item button (click)="presentModalValueEdit('shareStats', patch.data['server-info']['share-stats'])">
|
||||
<ion-label>Share Anonymous Statistics</ion-label>
|
||||
<ion-note slot="end">{{ patch.data['server-info']['share-stats'] }}</ion-note>
|
||||
</ion-item>
|
||||
|
||||
<ion-item-divider>Marketplace</ion-item-divider>
|
||||
<ion-item button (click)="presentModalValueEdit('autoCheckUpdates', patch.data.ui['auto-check-updates'])">
|
||||
<ion-label>Auto Check for Updates</ion-label>
|
||||
<ion-note slot="end">{{ patch.data.ui['auto-check-updates'] }}</ion-note>
|
||||
</ion-item>
|
||||
<ion-item button (click)="presentModalValueEdit('eosMarketplace', patch.data['server-info']['eos-marketplace'] === config.start9Marketplace.tor)">
|
||||
<ion-label>Tor Only Marketplace</ion-label>
|
||||
<ion-note slot="end">{{ patch.data['server-info']['eos-marketplace'] === config.start9Marketplace.tor }}</ion-note>
|
||||
</ion-item>
|
||||
<!-- <ion-item button (click)="presentModalValueEdit('packageMarketplace', patch.data['server-info']['package-marketplace'])">
|
||||
<ion-label>Package Marketplace</ion-label>
|
||||
<ion-note slot="end">{{ patch.data['server-info']['package-marketplace'] }}</ion-note>
|
||||
</ion-item> -->
|
||||
<ion-item button (click)="presentModalValueEdit('autoCheckUpdates', patch.data.ui['auto-check-updates'])">
|
||||
<ion-label>Auto Check for Updates</ion-label>
|
||||
<ion-note slot="end">{{ patch.data.ui['auto-check-updates'] }}</ion-note>
|
||||
</ion-item>
|
||||
|
||||
<!-- <ion-item-divider></ion-item-divider>
|
||||
<ion-item button (click)="presentModalValueEdit('password')">
|
||||
<ion-item-divider>Security</ion-item-divider>
|
||||
<!-- <ion-item button (click)="presentModalValueEdit('password')">
|
||||
<ion-label>Change Password</ion-label>
|
||||
<ion-note slot="end">********</ion-note>
|
||||
</ion-item> -->
|
||||
<ion-item detail="true" button [routerLink]="['ssh-keys']">
|
||||
<ion-label>SSH Keys</ion-label>
|
||||
</ion-item>
|
||||
</ion-item-group>
|
||||
|
||||
</ion-content>
|
||||
</ion-content>
|
||||
@@ -1,16 +1,14 @@
|
||||
import { Component } from '@angular/core'
|
||||
import { ServerConfigService } from 'src/app/services/server-config.service'
|
||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||
import { Subscription } from 'rxjs'
|
||||
import { ConfigService } from 'src/app/services/config.service'
|
||||
|
||||
@Component({
|
||||
selector: 'privacy',
|
||||
templateUrl: './privacy.page.html',
|
||||
styleUrls: ['./privacy.page.scss'],
|
||||
selector: 'security-options',
|
||||
templateUrl: './security-options.page.html',
|
||||
styleUrls: ['./security-options.page.scss'],
|
||||
})
|
||||
export class PrivacyPage {
|
||||
subs: Subscription[] = []
|
||||
export class SecurityOptionsPage {
|
||||
|
||||
constructor (
|
||||
private readonly serverConfigService: ServerConfigService,
|
||||
@@ -18,7 +16,7 @@ export class PrivacyPage {
|
||||
public readonly patch: PatchDbService,
|
||||
) { }
|
||||
|
||||
async presentModalValueEdit (key: string, current?: string): Promise<void> {
|
||||
async presentModalValueEdit (key: string, current?: any): Promise<void> {
|
||||
await this.serverConfigService.presentModalValueEdit(key, current)
|
||||
}
|
||||
}
|
||||
@@ -4,11 +4,11 @@ import { Routes, RouterModule } from '@angular/router'
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
loadChildren: () => import('./dev-options/dev-options.module').then(m => m.DevOptionsPageModule),
|
||||
loadChildren: () => import('./security-options/security-options.module').then(m => m.SecurityOptionsPageModule),
|
||||
},
|
||||
{
|
||||
path: 'ssh-keys',
|
||||
loadChildren: () => import('./dev-ssh-keys/dev-ssh-keys.module').then(m => m.DevSSHKeysPageModule),
|
||||
loadChildren: () => import('./ssh-keys/ssh-keys.module').then(m => m.SSHKeysPageModule),
|
||||
},
|
||||
]
|
||||
|
||||
@@ -16,4 +16,4 @@ const routes: Routes = [
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule],
|
||||
})
|
||||
export class DeveloperRoutingModule { }
|
||||
export class SecurityRoutingModule { }
|
||||
@@ -2,7 +2,7 @@ import { NgModule } from '@angular/core'
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { IonicModule } from '@ionic/angular'
|
||||
import { RouterModule, Routes } from '@angular/router'
|
||||
import { DevSSHKeysPage } from './dev-ssh-keys.page'
|
||||
import { SSHKeysPage } from './ssh-keys.page'
|
||||
import { PwaBackComponentModule } from 'src/app/components/pwa-back-button/pwa-back.component.module'
|
||||
import { SharingModule } from 'src/app/modules/sharing.module'
|
||||
import { TextSpinnerComponentModule } from 'src/app/components/text-spinner/text-spinner.component.module'
|
||||
@@ -10,7 +10,7 @@ import { TextSpinnerComponentModule } from 'src/app/components/text-spinner/text
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: DevSSHKeysPage,
|
||||
component: SSHKeysPage,
|
||||
},
|
||||
]
|
||||
|
||||
@@ -23,6 +23,6 @@ const routes: Routes = [
|
||||
SharingModule,
|
||||
TextSpinnerComponentModule,
|
||||
],
|
||||
declarations: [DevSSHKeysPage],
|
||||
declarations: [SSHKeysPage],
|
||||
})
|
||||
export class DevSSHKeysPageModule { }
|
||||
export class SSHKeysPageModule { }
|
||||
@@ -1,24 +1,23 @@
|
||||
import { Component } from '@angular/core'
|
||||
import { ServerConfigService } from 'src/app/services/server-config.service'
|
||||
import { AlertController } from '@ionic/angular'
|
||||
import { LoaderService } from 'src/app/services/loader.service'
|
||||
import { AlertController, LoadingController } from '@ionic/angular'
|
||||
import { SSHService } from './ssh.service'
|
||||
import { Subscription } from 'rxjs'
|
||||
import { SSHKeys } from 'src/app/services/api/api.types'
|
||||
import { ErrorToastService } from 'src/app/services/error-toast.service'
|
||||
|
||||
@Component({
|
||||
selector: 'dev-ssh-keys',
|
||||
templateUrl: 'dev-ssh-keys.page.html',
|
||||
styleUrls: ['dev-ssh-keys.page.scss'],
|
||||
selector: 'ssh-keys',
|
||||
templateUrl: 'ssh-keys.page.html',
|
||||
styleUrls: ['ssh-keys.page.scss'],
|
||||
})
|
||||
export class DevSSHKeysPage {
|
||||
export class SSHKeysPage {
|
||||
loading = true
|
||||
sshKeys: SSHKeys
|
||||
subs: Subscription[] = []
|
||||
|
||||
constructor (
|
||||
private readonly loader: LoaderService,
|
||||
private readonly loadingCtrl: LoadingController,
|
||||
private readonly errToast: ErrorToastService,
|
||||
private readonly serverConfigService: ServerConfigService,
|
||||
private readonly alertCtrl: AlertController,
|
||||
@@ -69,16 +68,20 @@ export class DevSSHKeysPage {
|
||||
}
|
||||
|
||||
async delete (hash: string): Promise<void> {
|
||||
this.loader.of({
|
||||
message: 'Deleting...',
|
||||
const loader = await this.loadingCtrl.create({
|
||||
spinner: 'lines',
|
||||
message: 'Deleting...',
|
||||
cssClass: 'loader',
|
||||
}).displayDuringAsync(async () => {
|
||||
await this.sshService.delete(hash)
|
||||
}).catch(e => {
|
||||
console.error(e)
|
||||
this.errToast.present(e.message)
|
||||
})
|
||||
await loader.present()
|
||||
|
||||
try {
|
||||
await this.sshService.delete(hash)
|
||||
} catch (e) {
|
||||
this.errToast.present(e)
|
||||
} finally {
|
||||
loader.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
asIsOrder (a: any, b: any) {
|
||||
@@ -33,36 +33,30 @@
|
||||
<ion-text *ngIf="type === 'restore'" class="ion-text-wrap" color="warning">No partitions available. Insert the storage device containing the backup you wish to restore.</ion-text>
|
||||
</ion-item>
|
||||
|
||||
<ion-grid>
|
||||
<ion-row>
|
||||
<ion-col *ngFor="let disk of disks | keyvalue" sizeSm="12" sizeMd="6">
|
||||
<ion-card>
|
||||
<ion-card-header>
|
||||
<ion-card-title>
|
||||
{{ disk.value.size }}
|
||||
</ion-card-title>
|
||||
<ion-card-subtitle>
|
||||
{{ disk.key }}
|
||||
</ion-card-subtitle>
|
||||
</ion-card-header>
|
||||
<ion-card-content>
|
||||
<ion-item-group>
|
||||
<ion-item button *ngFor="let partition of disk.value.partitions | keyvalue" [disabled]="partition.value['is-mounted']" (click)="presentModal(partition.key)">
|
||||
<ion-icon slot="start" name="save-outline"></ion-icon>
|
||||
<ion-label>
|
||||
<h2>{{ partition.value.label || partition.key }} ({{ partition.value.size || 'unknown size' }})</h2>
|
||||
<p *ngIf="!partition.value['is-mounted']; else unavailable"><ion-text color="success">Available</ion-text></p>
|
||||
<ng-template #unavailable>
|
||||
<p><ion-text color="danger">Unavailable</ion-text></p>
|
||||
</ng-template>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-item-group>
|
||||
</ion-card-content>
|
||||
</ion-card>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
</ion-grid>
|
||||
<ion-card *ngFor="let disk of disks | keyvalue">
|
||||
<ion-card-header>
|
||||
<ion-card-title>
|
||||
{{ disk.value.size }}
|
||||
</ion-card-title>
|
||||
<ion-card-subtitle>
|
||||
{{ disk.key }}
|
||||
</ion-card-subtitle>
|
||||
</ion-card-header>
|
||||
<ion-card-content>
|
||||
<ion-item-group>
|
||||
<ion-item button *ngFor="let partition of disk.value.partitions | keyvalue" [disabled]="partition.value['is-mounted']" (click)="presentModal(partition.key)">
|
||||
<ion-icon slot="start" name="save-outline"></ion-icon>
|
||||
<ion-label>
|
||||
<h2>{{ partition.value.label || partition.key }} ({{ partition.value.size || 'unknown size' }})</h2>
|
||||
<p *ngIf="!partition.value['is-mounted']; else unavailable"><ion-text color="success">Available</ion-text></p>
|
||||
<ng-template #unavailable>
|
||||
<p><ion-text color="danger">Unavailable</ion-text></p>
|
||||
</ng-template>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-item-group>
|
||||
</ion-card-content>
|
||||
</ion-card>
|
||||
</ng-template>
|
||||
|
||||
</ion-content>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { LoadingController, ModalController } from '@ionic/angular'
|
||||
import { ApiService } from 'src/app/services/api/embassy/embassy-api.service'
|
||||
import { BackupConfirmationComponent } from 'src/app/modals/backup-confirmation/backup-confirmation.component'
|
||||
import { DiskInfo } from 'src/app/services/api/api.types'
|
||||
import { ErrorToastService } from 'src/app/services/error-toast.service'
|
||||
|
||||
@Component({
|
||||
selector: 'server-backup',
|
||||
@@ -12,12 +13,12 @@ import { DiskInfo } from 'src/app/services/api/api.types'
|
||||
export class ServerBackupPage {
|
||||
disks: DiskInfo
|
||||
loading = true
|
||||
error: string
|
||||
allPartitionsMounted: boolean
|
||||
|
||||
constructor (
|
||||
private readonly modalCtrl: ModalController,
|
||||
private readonly embassyApi: ApiService,
|
||||
private readonly errToast: ErrorToastService,
|
||||
private readonly loadingCtrl: LoadingController,
|
||||
) { }
|
||||
|
||||
@@ -35,8 +36,7 @@ export class ServerBackupPage {
|
||||
this.disks = await this.embassyApi.getDisks({ })
|
||||
this.allPartitionsMounted = Object.values(this.disks).every(d => Object.values(d.partitions).every(p => p['is-mounted']))
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
this.error = e.message
|
||||
this.errToast.present(e)
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
@@ -62,18 +62,17 @@ export class ServerBackupPage {
|
||||
}
|
||||
|
||||
private async create (logicalname: string, password: string): Promise<void> {
|
||||
this.error = ''
|
||||
|
||||
const loader = await this.loadingCtrl.create({
|
||||
spinner: 'lines',
|
||||
message: 'Starting backup...',
|
||||
cssClass: 'loader',
|
||||
})
|
||||
await loader.present()
|
||||
|
||||
try {
|
||||
await this.embassyApi.createBackup({ logicalname, password })
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
this.error = e.message
|
||||
this.errToast.present(e)
|
||||
} finally {
|
||||
loader.dismiss()
|
||||
}
|
||||
|
||||
@@ -30,8 +30,7 @@ export class ServerLogsPage {
|
||||
this.logs = logs.map(l => `${l.timestamp} ${l.log}`).join('\n\n')
|
||||
setTimeout(async () => await this.content.scrollToBottom(100), 200)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
this.errToast.present(e.message)
|
||||
this.errToast.present(e)
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
|
||||
@@ -43,8 +43,7 @@ export class ServerMetricsPage {
|
||||
try {
|
||||
this.metrics = await this.embassyApi.getServerMetrics({ })
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
this.errToast.present(e.message)
|
||||
this.errToast.present(e)
|
||||
this.stopDaemon()
|
||||
} finally {
|
||||
this.loading = false
|
||||
|
||||
@@ -22,10 +22,6 @@ const routes: Routes = [
|
||||
path: 'logs',
|
||||
loadChildren: () => import('./server-logs/server-logs.module').then(m => m.ServerLogsPageModule),
|
||||
},
|
||||
{
|
||||
path: 'privacy',
|
||||
loadChildren: () => import('./privacy/privacy.module').then(m => m.PrivacyPageModule),
|
||||
},
|
||||
{
|
||||
path: 'wifi',
|
||||
loadChildren: () => import('./wifi/wifi.module').then(m => m.WifiListPageModule),
|
||||
@@ -35,8 +31,8 @@ const routes: Routes = [
|
||||
loadChildren: () => import('./lan/lan.module').then(m => m.LANPageModule),
|
||||
},
|
||||
{
|
||||
path: 'developer',
|
||||
loadChildren: () => import('./developer-routes/developer-routing.module').then( m => m.DeveloperRoutingModule),
|
||||
path: 'security',
|
||||
loadChildren: () => import('./security-routes/security-routing.module').then( m => m.SecurityRoutingModule),
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@@ -9,15 +9,13 @@
|
||||
|
||||
<ion-content>
|
||||
|
||||
<ion-grid>
|
||||
<ion-row>
|
||||
<ion-col *ngFor="let cat of settings | keyvalue : asIsOrder" sizeXs="12" sizeMd="6">
|
||||
<ion-item-divider>{{ cat.key }}</ion-item-divider>
|
||||
<ion-item style="cursor: pointer;" button *ngFor="let button of cat.value" (click)="button.action()">
|
||||
<ion-icon slot="start" [name]="button.icon"></ion-icon>
|
||||
<ion-label>{{ button.title }}</ion-label>
|
||||
</ion-item>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
</ion-grid>
|
||||
<ion-item-group>
|
||||
<div *ngFor="let cat of settings | keyvalue : asIsOrder">
|
||||
<ion-item-divider>{{ cat.key }}</ion-item-divider>
|
||||
<ion-item style="cursor: pointer;" button *ngFor="let button of cat.value" (click)="button.action()">
|
||||
<ion-icon slot="start" [name]="button.icon"></ion-icon>
|
||||
<ion-label>{{ button.title }}</ion-label>
|
||||
</ion-item>
|
||||
</div>
|
||||
</ion-item-group>
|
||||
</ion-content>
|
||||
@@ -1,9 +1,8 @@
|
||||
import { Component } from '@angular/core'
|
||||
import { LoadingOptions } from '@ionic/core'
|
||||
import { AlertController, NavController } from '@ionic/angular'
|
||||
import { AlertController, LoadingController, NavController } from '@ionic/angular'
|
||||
import { ApiService } from 'src/app/services/api/embassy/embassy-api.service'
|
||||
import { LoaderService } from 'src/app/services/loader.service'
|
||||
import { ActivatedRoute } from '@angular/router'
|
||||
import { ErrorToastService } from 'src/app/services/error-toast.service'
|
||||
|
||||
@Component({
|
||||
selector: 'server-show',
|
||||
@@ -15,7 +14,8 @@ export class ServerShowPage {
|
||||
|
||||
constructor (
|
||||
private readonly alertCtrl: AlertController,
|
||||
private readonly loader: LoaderService,
|
||||
private readonly loadingCtrl: LoadingController,
|
||||
private readonly errToast: ErrorToastService,
|
||||
private readonly embassyApi: ApiService,
|
||||
private readonly navCtrl: NavController,
|
||||
private readonly route: ActivatedRoute,
|
||||
@@ -70,21 +70,37 @@ export class ServerShowPage {
|
||||
}
|
||||
|
||||
private async restart () {
|
||||
this.loader
|
||||
.of(LoadingSpinner(`Restarting...`))
|
||||
.displayDuringAsync( async () => {
|
||||
await this.embassyApi.restartServer({ })
|
||||
})
|
||||
.catch(console.error)
|
||||
const loader = await this.loadingCtrl.create({
|
||||
spinner: 'lines',
|
||||
message: 'Restarting...',
|
||||
cssClass: 'loader',
|
||||
})
|
||||
await loader.present()
|
||||
|
||||
try {
|
||||
await this.embassyApi.restartServer({ })
|
||||
} catch (e) {
|
||||
this.errToast.present(e)
|
||||
} finally {
|
||||
loader.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
private async shutdown () {
|
||||
this.loader
|
||||
.of(LoadingSpinner(`Shutting down...`))
|
||||
.displayDuringAsync( async () => {
|
||||
await this.embassyApi.shutdownServer({ })
|
||||
})
|
||||
.catch(console.error)
|
||||
const loader = await this.loadingCtrl.create({
|
||||
spinner: 'lines',
|
||||
message: 'Shutting down...',
|
||||
cssClass: 'loader',
|
||||
})
|
||||
await loader.present()
|
||||
|
||||
try {
|
||||
await this.embassyApi.shutdownServer({ })
|
||||
} catch (e) {
|
||||
this.errToast.present(e)
|
||||
} finally {
|
||||
loader.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
private setButtons (): void {
|
||||
@@ -93,7 +109,7 @@ export class ServerShowPage {
|
||||
{
|
||||
title: 'Privacy and Security',
|
||||
icon: 'shield-checkmark-outline',
|
||||
action: () => this.navCtrl.navigateForward(['privacy'], { relativeTo: this.route }),
|
||||
action: () => this.navCtrl.navigateForward(['security'], { relativeTo: this.route }),
|
||||
},
|
||||
{
|
||||
title: 'LAN',
|
||||
@@ -105,11 +121,6 @@ export class ServerShowPage {
|
||||
icon: 'wifi',
|
||||
action: () => this.navCtrl.navigateForward(['wifi'], { relativeTo: this.route }),
|
||||
},
|
||||
{
|
||||
title: 'Developer Options',
|
||||
icon: 'terminal-outline',
|
||||
action: () => this.navCtrl.navigateForward(['developer'], { relativeTo: this.route }),
|
||||
},
|
||||
],
|
||||
'Insights': [
|
||||
{
|
||||
@@ -155,15 +166,6 @@ export class ServerShowPage {
|
||||
}
|
||||
}
|
||||
|
||||
const LoadingSpinner: (m?: string) => LoadingOptions = (m) => {
|
||||
const toMergeIn = m ? { message: m } : { }
|
||||
return {
|
||||
spinner: 'lines',
|
||||
cssClass: 'loader',
|
||||
...toMergeIn,
|
||||
} as LoadingOptions
|
||||
}
|
||||
|
||||
interface ServerSettings {
|
||||
[key: string]: {
|
||||
title: string
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { Component } from '@angular/core'
|
||||
import { NavController } from '@ionic/angular'
|
||||
import { LoadingController, NavController } from '@ionic/angular'
|
||||
import { ApiService } from 'src/app/services/api/embassy/embassy-api.service'
|
||||
import { WifiService } from '../wifi.service'
|
||||
import { LoaderService } from 'src/app/services/loader.service'
|
||||
import { ErrorToastService } from 'src/app/services/error-toast.service'
|
||||
|
||||
@Component({
|
||||
@@ -20,16 +19,19 @@ export class WifiAddPage {
|
||||
private readonly navCtrl: NavController,
|
||||
private readonly errToast: ErrorToastService,
|
||||
private readonly embassyApi: ApiService,
|
||||
private readonly loader: LoaderService,
|
||||
private readonly loadingCtrl: LoadingController,
|
||||
private readonly wifiService: WifiService,
|
||||
) { }
|
||||
|
||||
async save (): Promise<void> {
|
||||
this.loader.of({
|
||||
message: 'Saving...',
|
||||
const loader = await this.loadingCtrl.create({
|
||||
spinner: 'lines',
|
||||
message: 'Saving...',
|
||||
cssClass: 'loader',
|
||||
}).displayDuringAsync(async () => {
|
||||
})
|
||||
await loader.present()
|
||||
|
||||
try {
|
||||
await this.embassyApi.addWifi({
|
||||
ssid: this.ssid,
|
||||
password: this.password,
|
||||
@@ -38,18 +40,22 @@ export class WifiAddPage {
|
||||
connect: false,
|
||||
})
|
||||
this.navCtrl.back()
|
||||
}).catch(e => {
|
||||
console.error(e)
|
||||
this.errToast.present(e.message)
|
||||
})
|
||||
} catch (e) {
|
||||
this.errToast.present(e)
|
||||
} finally {
|
||||
loader.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
async saveAndConnect (): Promise<void> {
|
||||
this.loader.of({
|
||||
message: 'Connecting. This could take while...',
|
||||
const loader = await this.loadingCtrl.create({
|
||||
spinner: 'lines',
|
||||
message: 'Connecting. This could take while...',
|
||||
cssClass: 'loader',
|
||||
}).displayDuringAsync(async () => {
|
||||
})
|
||||
await loader.present()
|
||||
|
||||
try {
|
||||
await this.embassyApi.addWifi({
|
||||
ssid: this.ssid,
|
||||
password: this.password,
|
||||
@@ -58,13 +64,14 @@ export class WifiAddPage {
|
||||
connect: true,
|
||||
})
|
||||
const success = this.wifiService.confirmWifi(this.ssid)
|
||||
if (success) {
|
||||
this.navCtrl.back()
|
||||
}
|
||||
}).catch (e => {
|
||||
console.error(e)
|
||||
this.errToast.present(e.message)
|
||||
})
|
||||
if (success) {
|
||||
this.navCtrl.back()
|
||||
}
|
||||
} catch (e) {
|
||||
this.errToast.present(e)
|
||||
} finally {
|
||||
loader.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
asIsOrder (a: any, b: any) {
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<ion-item>
|
||||
<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>
|
||||
<h2>Embassy will automatically connect to saved WiFi networks when they are available, allowing you to remove the Ethernet cable.</h2>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { Component } from '@angular/core'
|
||||
import { ActionSheetController } from '@ionic/angular'
|
||||
import { ActionSheetController, LoadingController } from '@ionic/angular'
|
||||
import { ApiService } from 'src/app/services/api/embassy/embassy-api.service'
|
||||
import { ActionSheetButton } from '@ionic/core'
|
||||
import { WifiService } from './wifi.service'
|
||||
import { LoaderService } from 'src/app/services/loader.service'
|
||||
import { WiFiInfo } from 'src/app/services/patch-db/data-model'
|
||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||
import { Subscription } from 'rxjs'
|
||||
@@ -19,7 +18,7 @@ export class WifiListPage {
|
||||
|
||||
constructor (
|
||||
private readonly embassyApi: ApiService,
|
||||
private readonly loader: LoaderService,
|
||||
private readonly loadingCtrl: LoadingController,
|
||||
private readonly errToast: ErrorToastService,
|
||||
private readonly actionCtrl: ActionSheetController,
|
||||
private readonly wifiService: WifiService,
|
||||
@@ -57,29 +56,37 @@ export class WifiListPage {
|
||||
|
||||
// Let's add country code here
|
||||
async connect (ssid: string): Promise<void> {
|
||||
this.loader.of({
|
||||
message: 'Connecting. This could take while...',
|
||||
const loader = await this.loadingCtrl.create({
|
||||
spinner: 'lines',
|
||||
message: 'Connecting. This could take while...',
|
||||
cssClass: 'loader',
|
||||
}).displayDuringAsync(async () => {
|
||||
})
|
||||
await loader.present()
|
||||
|
||||
try {
|
||||
await this.embassyApi.connectWifi({ ssid })
|
||||
this.wifiService.confirmWifi(ssid)
|
||||
}).catch(e => {
|
||||
console.error(e)
|
||||
this.errToast.present(e.message)
|
||||
})
|
||||
} catch (e) {
|
||||
this.errToast.present(e)
|
||||
} finally {
|
||||
loader.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
async delete (ssid: string): Promise<void> {
|
||||
this.loader.of({
|
||||
message: 'Deleting...',
|
||||
const loader = await this.loadingCtrl.create({
|
||||
spinner: 'lines',
|
||||
message: 'Deleting...',
|
||||
cssClass: 'loader',
|
||||
}).displayDuringAsync(async () => {
|
||||
await this.embassyApi.deleteWifi({ ssid })
|
||||
}).catch(e => {
|
||||
console.error(e)
|
||||
this.errToast.present(e.message)
|
||||
})
|
||||
await loader.present()
|
||||
|
||||
try {
|
||||
await this.embassyApi.deleteWifi({ ssid })
|
||||
} catch (e) {
|
||||
this.errToast.present(e)
|
||||
} finally {
|
||||
loader.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,9 @@ export module RR {
|
||||
|
||||
// server
|
||||
|
||||
export type SetShareStatsReq = WithExpire<{ value: any }> // server.config.share-stats
|
||||
export type SetShareStatsRes = WithRevision<null>
|
||||
|
||||
export type GetServerLogsReq = { before?: string } // server.logs
|
||||
export type GetServerLogsRes = Log[]
|
||||
|
||||
|
||||
@@ -34,6 +34,11 @@ export abstract class ApiService implements Source<DataModel>, Http<DataModel> {
|
||||
|
||||
// server
|
||||
|
||||
protected abstract setShareStatsRaw (params: RR.SetShareStatsReq): Promise<RR.SetShareStatsRes>
|
||||
setShareStats = (params: RR.SetShareStatsReq) => this.syncResponse(
|
||||
() => this.setShareStatsRaw(params),
|
||||
)()
|
||||
|
||||
abstract getServerLogs (params: RR.GetServerLogsReq): Promise<RR.GetServerLogsRes>
|
||||
|
||||
abstract getServerMetrics (params: RR.GetServerMetricsReq): Promise<RR.GetServerMetricsRes>
|
||||
|
||||
@@ -43,6 +43,10 @@ export class LiveApiService extends ApiService {
|
||||
|
||||
// server
|
||||
|
||||
async setShareStatsRaw (params: RR.SetShareStatsReq): Promise<RR.SetShareStatsRes> {
|
||||
return this.http.rpcRequest( { method: 'server.config.share-stats', params })
|
||||
}
|
||||
|
||||
async getServerLogs (params: RR.GetServerLogsReq): Promise<RR.GetServerLogsRes> {
|
||||
return this.http.rpcRequest( { method: 'server.logs', params })
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Injectable } from '@angular/core'
|
||||
import { pauseFor } from '../../../util/misc.util'
|
||||
import { ApiService } from './embassy-api.service'
|
||||
import { Operation, PatchOp } from 'patch-db-client'
|
||||
import { DataModel, InstallProgress, PackageDataEntry, PackageMainStatus, PackageState, ServerStatus } from 'src/app/services/patch-db/data-model'
|
||||
import { InstallProgress, PackageDataEntry, PackageMainStatus, PackageState, ServerStatus } from 'src/app/services/patch-db/data-model'
|
||||
import { RR, WithRevision } from '../api.types'
|
||||
import { parsePropertiesPermissive } from 'src/app/util/properties.util'
|
||||
import { Mock } from '../api.fixures'
|
||||
@@ -27,39 +27,14 @@ export class MockApiService extends ApiService {
|
||||
// db
|
||||
|
||||
async getRevisions (since: number): Promise<RR.GetRevisionsRes> {
|
||||
// await pauseFor(2000)
|
||||
// return {
|
||||
// ...Mock.DbDump,
|
||||
// id: this.nextSequence(),
|
||||
// }
|
||||
return this.http.rpcRequest<RR.GetRevisionsRes>({ method: 'db.revisions', params: { since } })
|
||||
}
|
||||
|
||||
async getDump (): Promise<RR.GetDumpRes> {
|
||||
// await pauseFor(2000)
|
||||
// return {
|
||||
// ...Mock.DbDump,
|
||||
// id: this.nextSequence(),
|
||||
// }
|
||||
return this.http.rpcRequest<RR.GetDumpRes>({ method: 'db.dump' })
|
||||
}
|
||||
|
||||
async setDbValueRaw (params: RR.SetDBValueReq): Promise<RR.SetDBValueRes> {
|
||||
// await pauseFor(2000)
|
||||
// return {
|
||||
// response: null,
|
||||
// revision: {
|
||||
// id: this.nextSequence(),
|
||||
// patch: [
|
||||
// {
|
||||
// op: PatchOp.REPLACE,
|
||||
// path: params.pointer,
|
||||
// value: params.value,
|
||||
// },
|
||||
// ],
|
||||
// expireId: null,
|
||||
// },
|
||||
// }
|
||||
return this.http.rpcRequest<WithRevision<null>>({ method: 'db.put.ui', params })
|
||||
}
|
||||
|
||||
@@ -77,6 +52,18 @@ export class MockApiService extends ApiService {
|
||||
|
||||
// server
|
||||
|
||||
async setShareStatsRaw (params: RR.SetShareStatsReq): Promise<RR.SetShareStatsRes> {
|
||||
await pauseFor(2000)
|
||||
const patch = [
|
||||
{
|
||||
op: PatchOp.REPLACE,
|
||||
path: '/server-info/share-stats',
|
||||
value: params.value,
|
||||
},
|
||||
]
|
||||
return this.http.rpcRequest<WithRevision<null>>({ method: 'db.patch', params: { patch } })
|
||||
}
|
||||
|
||||
async getServerLogs (params: RR.GetServerLogsReq): Promise<RR.GetServerLogsRes> {
|
||||
await pauseFor(2000)
|
||||
return Mock.ServerLogs
|
||||
@@ -94,20 +81,19 @@ export class MockApiService extends ApiService {
|
||||
|
||||
async updateServerRaw (params: RR.UpdateServerReq): Promise<RR.UpdateServerRes> {
|
||||
await pauseFor(2000)
|
||||
const path = '/server-info/status'
|
||||
const patch = [
|
||||
{
|
||||
op: PatchOp.REPLACE,
|
||||
path,
|
||||
path: '/server-info/status',
|
||||
value: ServerStatus.Updating,
|
||||
},
|
||||
]
|
||||
const res = await this.http.rpcRequest<WithRevision<null>>({ method: 'db.patch', params: { patch } })
|
||||
setTimeout(() => {
|
||||
setTimeout(async () => {
|
||||
const patch = [
|
||||
{
|
||||
op: PatchOp.REPLACE,
|
||||
path,
|
||||
path: '/server-info/status',
|
||||
value: ServerStatus.Running,
|
||||
},
|
||||
{
|
||||
@@ -116,7 +102,7 @@ export class MockApiService extends ApiService {
|
||||
value: this.config.version + '.1',
|
||||
},
|
||||
]
|
||||
this.http.rpcRequest<WithRevision<null>>({ method: 'db.patch', params: { patch } })
|
||||
await this.http.rpcRequest<WithRevision<null>>({ method: 'db.patch', params: { patch } })
|
||||
// quickly revert patch to proper version to prevent infinite refresh loop
|
||||
const patch2 = [
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { IonicSafeString, ToastController } from '@ionic/angular'
|
||||
import { ToastButton } from '@ionic/core'
|
||||
import { RequestError } from './http.service'
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
@@ -12,11 +12,24 @@ export class ErrorToastService {
|
||||
private readonly toastCtrl: ToastController,
|
||||
) { }
|
||||
|
||||
async present (message: string | IonicSafeString, link?: string): Promise<void> {
|
||||
async present (e: RequestError, link?: string): Promise<void> {
|
||||
console.error(e)
|
||||
|
||||
if (this.toast) return
|
||||
|
||||
let message: string | IonicSafeString
|
||||
|
||||
if (e.status) message = String(e.status)
|
||||
if (e.message) message = `${message ? message + ' ' : ''}${e.message}`
|
||||
if (e.data) message = `${message ? message + '. ' : ''}${e.data.code}: ${e.data.message}`
|
||||
|
||||
if (!message) {
|
||||
message = 'Unknown Error.'
|
||||
link = 'https://docs.start9.com'
|
||||
}
|
||||
|
||||
if (link) {
|
||||
message = new IonicSafeString(message + `<br /><br /><a href=${link} target="_blank" style="color: white;">Get Help</a>`)
|
||||
message = new IonicSafeString(`${message}<br /><br /><a href=${link} target="_blank" style="color: white;">Get Help</a>`)
|
||||
}
|
||||
|
||||
this.toast = await this.toastCtrl.create({
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { concatMap, finalize } from 'rxjs/operators'
|
||||
import { Observable, from, Subject } from 'rxjs'
|
||||
import { fromAsync$, fromAsyncP, emitAfter$, fromSync$ } from '../util/rxjs.util'
|
||||
import { LoadingController } from '@ionic/angular'
|
||||
import { LoadingOptions } from '@ionic/core'
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class LoaderService {
|
||||
private loadingOptions: LoadingOptions = defaultOptions()
|
||||
constructor (private readonly loadingCtrl: LoadingController) { }
|
||||
|
||||
private loader: HTMLIonLoadingElement
|
||||
|
||||
public get ionLoader (): HTMLIonLoadingElement {
|
||||
return this.loader
|
||||
}
|
||||
|
||||
public get ctrl () {
|
||||
return this.loadingCtrl
|
||||
}
|
||||
|
||||
private setOptions (l: LoadingOptions): LoaderService {
|
||||
this.loadingOptions = l
|
||||
return this
|
||||
}
|
||||
|
||||
of (overrideOptions: LoadingOptions): LoaderService {
|
||||
return new LoaderService(this.loadingCtrl).setOptions(Object.assign(defaultOptions(), overrideOptions))
|
||||
}
|
||||
|
||||
displayDuring$<T> (o: Observable<T>): Observable<T> {
|
||||
let shouldDisplay = true
|
||||
const displayIfItsBeenAtLeast = 10 // ms
|
||||
return fromAsync$(
|
||||
async () => {
|
||||
this.loader = await this.loadingCtrl.create(this.loadingOptions)
|
||||
emitAfter$(displayIfItsBeenAtLeast).subscribe(() => { if (shouldDisplay) this.loader.present() })
|
||||
},
|
||||
).pipe(
|
||||
concatMap(() => o),
|
||||
finalize(() => {
|
||||
this.loader.dismiss(); shouldDisplay = false; this.loader = undefined
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
displayDuringP<T> (p: Promise<T>): Promise<T> {
|
||||
return this.displayDuring$(from(p)).toPromise()
|
||||
}
|
||||
|
||||
displayDuringAsync<T> (thunk: () => Promise<T>): Promise<T> {
|
||||
return this.displayDuringP(fromAsyncP(thunk))
|
||||
}
|
||||
}
|
||||
|
||||
export function markAsLoadingDuring$<T> ($trigger$: Subject<boolean>, o: Observable<T>): Observable<T> {
|
||||
let shouldBeOn = true
|
||||
const displayIfItsBeenAtLeast = 5 // ms
|
||||
return fromSync$(() => {
|
||||
emitAfter$(displayIfItsBeenAtLeast).subscribe(() => { if (shouldBeOn) $trigger$.next(true) })
|
||||
}).pipe(
|
||||
concatMap(() => o),
|
||||
finalize(() => {
|
||||
$trigger$.next(false)
|
||||
shouldBeOn = false
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
const defaultOptions: () => LoadingOptions = () => ({
|
||||
spinner: 'lines',
|
||||
cssClass: 'loader',
|
||||
backdropDismiss: true,
|
||||
})
|
||||
@@ -44,7 +44,6 @@ export class PatchDbService {
|
||||
.pipe(debounceTime(500))
|
||||
.subscribe({
|
||||
next: cache => {
|
||||
console.log('saving cacheee: ', JSON.parse(JSON.stringify(cache)))
|
||||
this.connectionStatus$.next(ConnectionStatus.Connected)
|
||||
this.bootstrapper.update(cache)
|
||||
},
|
||||
@@ -54,11 +53,11 @@ export class PatchDbService {
|
||||
// this.start()
|
||||
},
|
||||
complete: () => {
|
||||
console.error('patch-db-sync sub COMPLETE')
|
||||
console.warn('patch-db-sync sub COMPLETE')
|
||||
},
|
||||
})
|
||||
} catch (e) {
|
||||
console.log('Failed to initialize PatchDB', e)
|
||||
console.error('Failed to initialize PatchDB', e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,12 +83,12 @@ export class PatchDbService {
|
||||
console.log('WATCHING', ...args)
|
||||
return this.patchDb.store.watch$(...(args as []))
|
||||
.pipe(
|
||||
tap(data => console.log('CHANGE IN STORE', data, ...args)),
|
||||
tap(data => console.log('NEW VALUE', data, ...args)),
|
||||
catchError(e => {
|
||||
console.error(e)
|
||||
console.error('Error watching Patch DB', e)
|
||||
return of(e.message)
|
||||
}),
|
||||
finalize(() => console.log('UNSUBSCRIBING')),
|
||||
finalize(() => console.log('UNSUBSCRIBING', ...args)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { AppConfigValuePage } from '../modals/app-config-value/app-config-value.
|
||||
import { ApiService } from './api/embassy/embassy-api.service'
|
||||
import { ConfigSpec } from '../pkg-config/config-types'
|
||||
import { ConfigCursor } from '../pkg-config/config-cursor'
|
||||
import { SSHService } from '../pages/server-routes/developer-routes/dev-ssh-keys/ssh.service'
|
||||
import { SSHService } from '../pages/server-routes/security-routes/ssh-keys/ssh.service'
|
||||
import { TrackingModalController } from './tracking-modal-controller.service'
|
||||
|
||||
@Injectable({
|
||||
@@ -34,8 +34,8 @@ export class ServerConfigService {
|
||||
}
|
||||
|
||||
saveFns: { [key: string]: (val: any) => Promise<any> } = {
|
||||
autoCheckUpdates: async (value: boolean) => {
|
||||
return this.embassyApi.setDbValue({ pointer: 'ui/auto-check-updates', value })
|
||||
autoCheckUpdates: async (enabled: boolean) => {
|
||||
return this.embassyApi.setDbValue({ pointer: '/auto-check-updates', value: enabled })
|
||||
},
|
||||
ssh: async (pubkey: string) => {
|
||||
return this.sshService.add(pubkey)
|
||||
@@ -46,6 +46,9 @@ export class ServerConfigService {
|
||||
// packageMarketplace: async (url: string) => {
|
||||
// return this.embassyApi.setPackageMarketplace({ url })
|
||||
// },
|
||||
shareStats: async (enabled: boolean) => {
|
||||
return this.embassyApi.setShareStats({ value: enabled })
|
||||
},
|
||||
// password: async (password: string) => {
|
||||
// return this.embassyApi.updatePassword({ password })
|
||||
// },
|
||||
@@ -72,8 +75,9 @@ const serverConfig: ConfigSpec = {
|
||||
},
|
||||
eosMarketplace: {
|
||||
type: 'boolean',
|
||||
name: 'Use Tor',
|
||||
description: `Use Start9's Tor Hidden Service Marketplace (instead of clearnet).`,
|
||||
name: 'Tor Only Marketplace',
|
||||
description: `Use Start9's Tor (instead of clearnet) Marketplace.`,
|
||||
changeWarning: 'This will result in higher latency and slower download times.',
|
||||
default: false,
|
||||
},
|
||||
// packageMarketplace: {
|
||||
@@ -87,6 +91,12 @@ const serverConfig: ConfigSpec = {
|
||||
// masked: false,
|
||||
// copyable: false,
|
||||
// },
|
||||
shareStats: {
|
||||
type: 'boolean',
|
||||
name: 'Share Anonymous Statistics',
|
||||
description: 'Start9 uses this information to identify bugs quickly and improve EmbassyOS. The information is 100% anonymous and transmitted over Tor.',
|
||||
default: false,
|
||||
},
|
||||
// password: {
|
||||
// type: 'string',
|
||||
// name: 'Change Password',
|
||||
|
||||
@@ -13,6 +13,7 @@ import { DataModel, PackageDataEntry } from './patch-db/data-model'
|
||||
import { PatchDbService } from './patch-db/patch-db.service'
|
||||
import { filter, take } from 'rxjs/operators'
|
||||
import { isEmptyObject } from '../util/misc.util'
|
||||
import { ApiService } from './api/embassy/embassy-api.service'
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
@@ -28,6 +29,7 @@ export class StartupAlertsService {
|
||||
private readonly modalCtrl: ModalController,
|
||||
private readonly marketplaceService: MarketplaceService,
|
||||
private readonly marketplaceApi: MarketplaceApiService,
|
||||
private readonly embassyApi: ApiService,
|
||||
private readonly emver: Emver,
|
||||
private readonly wizardBaker: WizardBaker,
|
||||
private readonly patch: PatchDbService,
|
||||
@@ -120,18 +122,18 @@ export class StartupAlertsService {
|
||||
private async displayOsWelcome (): Promise<boolean> {
|
||||
return new Promise(async resolve => {
|
||||
const modal = await this.modalCtrl.create({
|
||||
backdropDismiss: false,
|
||||
component: OSWelcomePage,
|
||||
presentingElement: await this.modalCtrl.getTop(),
|
||||
componentProps: {
|
||||
version: this.config.version,
|
||||
},
|
||||
})
|
||||
|
||||
await modal.present()
|
||||
modal.onWillDismiss().then(res => {
|
||||
return resolve(res.data)
|
||||
modal.onWillDismiss().then(() => {
|
||||
this.embassyApi.setDbValue({ pointer: '/welcome-ack', value: this.config.version })
|
||||
.catch()
|
||||
return resolve(true)
|
||||
})
|
||||
await modal.present()
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -73,6 +73,10 @@ ion-toast {
|
||||
--border-color: var(--ion-color-warning);
|
||||
}
|
||||
|
||||
.success-toast {
|
||||
--border-color: var(--ion-color-success);
|
||||
}
|
||||
|
||||
.error-toast {
|
||||
--border-color: var(--ion-color-danger);
|
||||
width: 40%;
|
||||
|
||||
Reference in New Issue
Block a user