update readme, fix startup sequence, integrate live backend

This commit is contained in:
Matt Hill
2021-06-18 15:54:45 -06:00
committed by Aiden McClelland
parent 12c44565ff
commit 7013364ae8
15 changed files with 111 additions and 86 deletions

View File

@@ -2,14 +2,58 @@
## Setup Instructions
**Make sure you have git, node, and npm installed**
**Make sure you have git, node, npm, and rust installed**
`npm i -g @ionic/cli`
`git clone https://github.com/Start9Labs/patch-db.git`
`git clone https://github.com/Start9Labs/ws-example.git`
`git clone https://github.com/Start9Labs/embassy-os.git`
`cd embassy-os/ui`
`git clone https://github.com/Start9Labs/rpc-toolkit.git`
`git clone https://github.com/dr-bonez/yajrc`
Then open `patch-db`, `ws-example`, and `embassy-os`, in separate tabs.
### patch-db
**Sync submodules**
`git submodule update --init --recursive`
### ws-example
**Start the server**
`cargo run -- -vvv -c example-config.toml`
### embassy-os
`cd ui/`
`npm i`
In `ui-config.json`, edit the "mocks" section to look like the following:
```
"mocks": {
"enabled": true,
"connection": "ws",
"rpcPort": "5959",
"wsPort": "5960",
"maskAs": "tor",
"skipStartupAlerts": true
}
```
Valid values for "connection" are `ws` and `poll`.
Valid values for "maskAs" are `tor` and `lan`.
You can also enable or disable startup alerts.
**Start the client**
`ionic serve`

View File

@@ -1,8 +1,5 @@
<ion-app>
<ion-spinner *ngIf="!patch.initialized" class="center" name="lines" color="warning"></ion-spinner>
<ion-split-pane *ngIf="patch.initialized" [disabled]="!showMenu" (ionSplitPaneVisible)="splitPaneVisible($event)" contentId="main-content">
<ion-split-pane *ngIf="patch.patchDb?.store" [disabled]="!showMenu" (ionSplitPaneVisible)="splitPaneVisible($event)" contentId="main-content">
<ion-menu contentId="main-content" type="overlay">
<ion-header>
<ion-toolbar style="--background: var(--ion-background-color);">

View File

@@ -92,7 +92,7 @@ export class WizardBaker {
action,
verb: 'updating',
title,
fetchBreakages: () => this.apiService.dryUpdatePackage({ id, version }).then( ({ breakages }) => breakages ),
fetchBreakages: () => this.apiService.dryUpdatePackage({ id, version }).then(breakages => breakages),
},
},
bottomBar: {
@@ -192,7 +192,7 @@ export class WizardBaker {
action,
verb: 'downgrading',
title,
fetchBreakages: () => this.apiService.dryUpdatePackage({ id, version }).then( ({ breakages }) => breakages ),
fetchBreakages: () => this.apiService.dryUpdatePackage({ id, version }).then(breakages => breakages),
},
},
bottomBar: {
@@ -248,7 +248,7 @@ export class WizardBaker {
action,
verb: 'uninstalling',
title,
fetchBreakages: () => this.apiService.dryRemovePackage({ id }).then( ({ breakages }) => breakages ),
fetchBreakages: () => this.apiService.dryRemovePackage({ id }).then(breakages => breakages ),
},
},
bottomBar: { cancel: { whileLoading: { }, afterLoading: { text: 'Cancel' } }, next: 'Uninstall' },

View File

@@ -2,7 +2,6 @@ import { Bootstrapper, DBCache } from 'patch-db-client'
import { DataModel } from './data-model'
import { Injectable } from '@angular/core'
import { Storage } from '@ionic/storage'
import { ApiService } from 'src/app/services/api/api.service'
@Injectable({
providedIn: 'root',
@@ -12,22 +11,14 @@ export class LocalStorageBootstrap implements Bootstrapper<DataModel> {
constructor (
private readonly storage: Storage,
private readonly apiService: ApiService,
) { }
async init (): Promise<DBCache<DataModel>> {
let cache = await this.storage.get(LocalStorageBootstrap.CONTENT_KEY) as DBCache<DataModel>
if (!cache || cache.sequence === 0) {
console.log('No cached data, getting dump from server')
const { id, value } = await this.apiService.getDump()
cache.sequence = id
cache.data = value
await this.update(cache)
}
return cache
const cache: DBCache<DataModel> = await this.storage.get(LocalStorageBootstrap.CONTENT_KEY)
return cache || { sequence: 0, data: { } }
}
async update (cache: DBCache<DataModel>): Promise<void> {
return this.storage.set(LocalStorageBootstrap.CONTENT_KEY, cache)
await this.storage.set(LocalStorageBootstrap.CONTENT_KEY, cache)
}
}

View File

@@ -11,7 +11,7 @@ export function PatchDbModelFactory (
http: ApiService,
): PatchDbModel {
const { mocks, patchDb: { poll, timeoutForMissingRevision }, isConsulate } = config
const { mocks, patchDb: { poll }, isConsulate } = config
let source: Source<DataModel>
@@ -31,5 +31,5 @@ export function PatchDbModelFactory (
}
}
return new PatchDbModel({ sources: [source, http], bootstrapper, http, timeoutForMissingRevision })
return new PatchDbModel(bootstrapper, source)
}

View File

@@ -1,49 +1,61 @@
import { Inject, Injectable, InjectionToken } from '@angular/core'
import { PatchDB, PatchDbConfig, Store } from 'patch-db-client'
import { Bootstrapper, PatchDB, Source, Store } from 'patch-db-client'
import { Observable, of, Subscription } from 'rxjs'
import { catchError, finalize } from 'rxjs/operators'
import { catchError, debounceTime } from 'rxjs/operators'
import { DataModel } from './data-model'
export const PATCH_CONFIG = new InjectionToken<PatchDbConfig<DataModel>>('app.config')
export const BOOTSTRAPPER = new InjectionToken<Bootstrapper<DataModel>>('app.config')
export const PATCH_SOURCE = new InjectionToken<Source<DataModel>>('app.config')
@Injectable({
providedIn: 'root',
})
export class PatchDbModel {
private patchDb: PatchDB<DataModel>
private syncSub: Subscription
initialized = false
patchDb: PatchDB<DataModel>
private patchSub: Subscription
constructor (
@Inject(PATCH_CONFIG) private readonly conf: PatchDbConfig<DataModel>,
@Inject(BOOTSTRAPPER) private readonly bootstrapper: Bootstrapper<DataModel>,
@Inject(PATCH_SOURCE) private readonly source: Source<DataModel>,
) { }
async init (): Promise<void> {
if (this.patchDb) return console.warn('Cannot re-init patchDbModel')
const cache = await this.bootstrapper.init()
this.patchDb = new PatchDB(this.source, cache)
}
start (): void {
// make sure everything is stopped before initializing
this.stop()
try {
this.patchDb = await PatchDB.init<DataModel>(this.conf)
this.initialized = true
this.patchSub = this.patchDb.sync$()
.pipe(debounceTime(500))
.subscribe({
next: cache => {
console.log('saving cache: ', cache.sequence)
this.bootstrapper.update(cache)
},
error: e => {
console.error('Critical, patch-db-sync sub error', e)
this.start()
},
complete: () => {
console.error('Critical, patch-db-sync sub complete')
},
})
} catch (e) {
console.log('Failed to initialize PatchDB', e)
}
}
start (): void {
if (this.syncSub) this.stop()
this.syncSub = this.patchDb.sync$().subscribe({
error: e => console.error('Critical, patch-db-sync sub error', e),
complete: () => console.error('Critical, patch-db-sync sub complete'),
})
}
stop (): void {
if (this.syncSub) {
this.syncSub.unsubscribe()
this.syncSub = undefined
if (this.patchSub) {
this.patchSub.unsubscribe()
this.patchSub = undefined
}
}
watch$: Store<DataModel>['watch$'] = (...args: (string | number)[]): Observable<DataModel> => {
watch$: Store < DataModel > ['watch$'] = (...args: (string | number)[]): Observable<DataModel> => {
// console.log('WATCHING')
return this.patchDb.store.watch$(...(args as [])).pipe(
catchError(e => {

View File

@@ -183,9 +183,9 @@ export class AppConfigPage {
spinner: 'lines',
cssClass: 'loader',
}).displayDuringAsync(async () => {
const { breakages } = await this.apiService.drySetPackageConfig({ id: pkg.manifest.id, config: this.config })
const breakages = await this.apiService.drySetPackageConfig({ id: pkg.manifest.id, config: this.config })
if (breakages.length) {
if (!isEmptyObject(breakages.length)) {
const { cancelled } = await wizardModal(
this.modalController,
this.wizardBaker.configure({

View File

@@ -8,7 +8,7 @@
</ion-header>
<ion-content style="position: relative">
<ng-container *ngrxLet="patch.watch$('package-data') as pkgs">
<ng-container *ngIf="patch.watch$('package-data') | ngrxPush as pkgs">
<div *ngIf="pkgs | empty; else list" class="ion-text-center ion-padding">
<div style="display: flex; flex-direction: column; justify-content: center; height: 40vh">
@@ -23,7 +23,7 @@
<ng-template #list>
<ion-grid>
<ion-row *ngrxLet="connectionService.monitor$() as connection">
<ion-row *ngIf="connectionService.monitor$() | ngrxPush as connection">
<ion-col *ngFor="let pkg of pkgs | keyvalue : asIsOrder" sizeXs="4" sizeSm="3" sizeMd="2" sizeLg="2">
<ion-card class="installed-card" style="position:relative" [routerLink]="['/services', 'installed', (pkg.value | manifest).id]">
<div class="launch-container" *ngIf="pkg.value | hasUi">

View File

@@ -10,6 +10,7 @@ import { PackageDataEntry } from 'src/app/models/patch-db/data-model'
styleUrls: ['./app-installed-list.page.scss'],
})
export class AppInstalledListPage {
pkgs: PackageDataEntry[] = []
constructor (
private readonly config: ConfigService,

View File

@@ -2,7 +2,7 @@ import { Component, ViewChild } from '@angular/core'
import { AlertController, NavController, ModalController, IonContent, PopoverController } from '@ionic/angular'
import { ApiService } from 'src/app/services/api/api.service'
import { ActivatedRoute, NavigationExtras } from '@angular/router'
import { chill, pauseFor } from 'src/app/util/misc.util'
import { chill, isEmptyObject, pauseFor } from 'src/app/util/misc.util'
import { LoaderService } from 'src/app/services/loader.service'
import { Observable, of, Subscription } from 'rxjs'
import { wizardModal } from 'src/app/components/install-wizard/install-wizard.component'
@@ -73,9 +73,9 @@ export class AppInstalledShowPage {
spinner: 'lines',
cssClass: 'loader',
}).displayDuringAsync(async () => {
const { breakages } = await this.apiService.dryStopPackage({ id })
const breakages = await this.apiService.dryStopPackage({ id })
if (breakages.length) {
if (isEmptyObject(breakages.length)) {
const { cancelled } = await wizardModal(
this.modalCtrl,
this.wizardBaker.stop({

View File

@@ -1,4 +1,4 @@
import { Dump, Operation, Revision } from 'patch-db-client'
import { Dump, Revision } from 'patch-db-client'
import { PackagePropertiesVersioned } from 'src/app/util/properties.util'
import { ConfigSpec } from 'src/app/pkg-config/config-types'
import { DataModel, DependencyError, Manifest, URL } from 'src/app/models/patch-db/data-model'
@@ -113,13 +113,13 @@ export module RR {
export type InstallPackageRes = WithRevision<null>
export type DryUpdatePackageReq = { id: string, version: string } // package.update.dry
export type DryUpdatePackageRes = BreakageRes
export type DryUpdatePackageRes = Breakages
export type GetPackageConfigReq = { id: string } // package.config.get
export type GetPackageConfigRes = { spec: ConfigSpec, config: object }
export type DrySetPackageConfigReq = { id: string, config: object } // package.config.set.dry
export type DrySetPackageConfigRes = BreakageRes
export type DrySetPackageConfigRes = Breakages
export type SetPackageConfigReq = WithExpire<DrySetPackageConfigReq> // package.config.set
export type SetPackageConfigRes = WithRevision<null>
@@ -134,13 +134,13 @@ export module RR {
export type StartPackageRes = WithRevision<null>
export type DryStopPackageReq = StopPackageReq // package.stop.dry
export type DryStopPackageRes = BreakageRes
export type DryStopPackageRes = Breakages
export type StopPackageReq = WithExpire<{ id: string }> // package.stop
export type StopPackageRes = WithRevision<null>
export type DryRemovePackageReq = RemovePackageReq // package.remove.dry
export type DryRemovePackageRes = BreakageRes
export type DryRemovePackageRes = Breakages
export type RemovePackageReq = WithExpire<{ id: string }> // package.remove
export type RemovePackageRes = WithRevision<null>
@@ -198,11 +198,6 @@ export interface AvailableShow {
}
}
export interface BreakageRes {
patch: Operation[],
breakages: Breakages
}
export interface Breakages {
[id: string]: TaggedDependencyError
}

View File

@@ -178,11 +178,11 @@ export abstract class ApiService implements Source<DataModel>, Http<DataModel> {
// state change you'd like to enact prior to request and expired when request terminates.
private syncResponse<T, F extends (...args: any[]) => Promise<{ response: T, revision?: Revision }>> (f: F, temp?: Operation): (...args: Parameters<F>) => Promise<T> {
return (...a) => {
let expireId = undefined
if (temp) {
expireId = uuid.v4()
this.sync.next({ patch: [temp], expiredBy: expireId })
}
// let expireId = undefined
// if (temp) {
// expireId = uuid.v4()
// this.sync.next({ patch: [temp], expiredBy: expireId })
// }
return f(a).then(({ response, revision }) => {
if (revision) this.sync.next(revision)

View File

@@ -331,10 +331,7 @@ export class MockApiService extends ApiService {
async dryUpdatePackage (params: RR.DryUpdatePackageReq): Promise<RR.DryUpdatePackageRes> {
await pauseFor(2000)
return {
patch: [],
breakages: { },
}
return { }
}
async getPackageConfig (params: RR.GetPackageConfigReq): Promise<RR.GetPackageConfigRes> {
@@ -344,10 +341,7 @@ export class MockApiService extends ApiService {
async drySetPackageConfig (params: RR.DrySetPackageConfigReq): Promise<RR.DrySetPackageConfigRes> {
await pauseFor(2000)
return {
patch: [],
breakages: { },
}
return { }
}
async setPackageConfigRaw (params: RR.SetPackageConfigReq): Promise<RR.SetPackageConfigRes> {
@@ -421,10 +415,7 @@ export class MockApiService extends ApiService {
async dryStopPackage (params: RR.DryStopPackageReq): Promise<RR.DryStopPackageRes> {
await pauseFor(2000)
return {
patch: [],
breakages: { },
}
return { }
}
async stopPackageRaw (params: RR.StopPackageReq): Promise<RR.StopPackageRes> {
@@ -447,10 +438,7 @@ export class MockApiService extends ApiService {
async dryRemovePackage (params: RR.DryRemovePackageReq): Promise<RR.DryRemovePackageRes> {
await pauseFor(2000)
return {
patch: [],
breakages: { },
}
return { }
}
async removePackageRaw (params: RR.RemovePackageReq): Promise<RR.RemovePackageRes> {

View File

@@ -8,8 +8,6 @@ type UiConfig = {
poll: {
cooldown: number /* in ms */
}
// Wait this long (ms) before asking BE for a dump when out of order messages are received
timeoutForMissingRevision: number
}
api: {
url: string

View File

@@ -1,6 +1,5 @@
{
"patchDb": {
"timeoutForMissingRevision": 5000,
"poll": {
"cooldown": 10000
}