mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 20:14:49 +00:00
refactor loaders, better err toasts, rework Embassy tab organization
This commit is contained in:
committed by
Aiden McClelland
parent
2ff9c622ac
commit
eb245aea50
@@ -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()
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user