refactor loaders, better err toasts, rework Embassy tab organization

This commit is contained in:
Matt Hill
2021-07-20 16:57:20 -06:00
committed by Aiden McClelland
parent 2ff9c622ac
commit eb245aea50
58 changed files with 492 additions and 610 deletions

View File

@@ -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[]

View File

@@ -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>

View File

@@ -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 })
}

View File

@@ -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 = [
{

View File

@@ -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({

View File

@@ -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,
})

View File

@@ -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)),
)
}
}

View File

@@ -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',

View File

@@ -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()
})
}