mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 10:21:52 +00:00
update readme, fix startup sequence, integrate live backend
This commit is contained in:
committed by
Aiden McClelland
parent
12c44565ff
commit
7013364ae8
48
ui/README.md
48
ui/README.md
@@ -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`
|
||||
|
||||
@@ -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);">
|
||||
|
||||
@@ -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' },
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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 => {
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"patchDb": {
|
||||
"timeoutForMissingRevision": 5000,
|
||||
"poll": {
|
||||
"cooldown": 10000
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user