local mocks and default back buttons

This commit is contained in:
Matt Hill
2021-11-30 21:02:43 -07:00
committed by Aiden McClelland
parent 098dc0a6d0
commit c99b75c0b4
28 changed files with 803 additions and 190 deletions

View File

@@ -12,20 +12,8 @@ npm --version
v8.0.0
```
### Running The Mock Development Server
`git clone https://github.com/Start9Labs/ws-example.git`
`cd ws-example`
`git submodule update --init --recursive`
`cargo run -- -vvv -c example-config.toml`
### Building Embassy UI
**In a new terminal window, from `embassy-os/ui` run:**
`git clone https://github.com/Start9Labs/embassy-os.git`
`cd embassy-os`
@@ -46,30 +34,13 @@ In `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**
**Start the development server**
`ionic serve`
### Updating Server Mocks
If you want to update mock data inside ws-example, you must do the following:
1. Stop the ws-example server
1. In es-example, run `rm embassy.db`
1. Delete `patch-db-cache` from your browser's Local Storage
1. Restart ws-example
1. Refresh the browser window

View File

@@ -51,7 +51,7 @@ import { GlobalErrorHandler } from './services/global-error-handler.service'
{
provide: ApiService,
useFactory: ApiServiceFactory,
deps: [ConfigService, HttpService],
deps: [ConfigService, HttpService, LocalStorageBootstrap],
},
{
provide: PatchDbService,

View File

@@ -1,7 +1,7 @@
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button></ion-back-button>
<ion-back-button defaultHref="embassy"></ion-back-button>
</ion-buttons>
<ion-title>{{ title }}</ion-title>
<ion-buttons slot="end">

View File

@@ -1,7 +1,7 @@
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button></ion-back-button>
<ion-back-button [defaultHref]="'/services/' + pkgId"></ion-back-button>
</ion-buttons>
<ion-title>Actions</ion-title>
</ion-toolbar>

View File

@@ -1,7 +1,7 @@
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button></ion-back-button>
<ion-back-button [defaultHref]="'/services/' + pkgId"></ion-back-button>
</ion-buttons>
<ion-title>Interfaces</ion-title>
</ion-toolbar>

View File

@@ -20,6 +20,7 @@ export class AppInterfacesPage {
@ViewChild(IonContent) content: IonContent
ui: LocalInterface | null
other: LocalInterface[] = []
pkgId: string
constructor (
private readonly route: ActivatedRoute,
@@ -27,8 +28,8 @@ export class AppInterfacesPage {
) { }
ngOnInit () {
const pkgId = this.route.snapshot.paramMap.get('pkgId')
const pkg = this.patch.getData()['package-data'][pkgId]
this.pkgId = this.route.snapshot.paramMap.get('pkgId')
const pkg = this.patch.getData()['package-data'][this.pkgId]
const interfaces = pkg.manifest.interfaces
const uiKey = getUiInterfaceKey(interfaces)

View File

@@ -1,7 +1,7 @@
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button></ion-back-button>
<ion-back-button [defaultHref]="'/services/' + pkgId"></ion-back-button>
</ion-buttons>
<ion-title>Logs</ion-title>
</ion-toolbar>

View File

@@ -16,9 +16,10 @@ export class AppLogsPage {
constructor (
private readonly route: ActivatedRoute,
private readonly embassyApi: ApiService,
) {
this.pkgId = this.route.snapshot.paramMap.get('pkgId')
) { }
ngOnInit () {
this.pkgId = this.route.snapshot.paramMap.get('pkgId')
}
fetchFetchLogs () {

View File

@@ -1,7 +1,7 @@
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button></ion-back-button>
<ion-back-button [defaultHref]="'/services/' + pkgId"></ion-back-button>
</ion-buttons>
<ion-title>Monitor</ion-title>
</ion-toolbar>

View File

@@ -1,7 +1,7 @@
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button></ion-back-button>
<ion-back-button [defaultHref]="'/services/' + pkgId"></ion-back-button>
</ion-buttons>
<ion-title>Properties</ion-title>
<ion-buttons *ngIf="!loading" slot="end">

View File

@@ -1,7 +1,7 @@
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button></ion-back-button>
<ion-back-button defaultHref="services"></ion-back-button>
</ion-buttons>
<ion-item lines="none">
<ion-avatar slot="start">

View File

@@ -1,7 +1,7 @@
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button></ion-back-button>
<ion-back-button [defaultHref]="'/marketplace/' + pkgId"></ion-back-button>
</ion-buttons>
<ion-title>Release Notes</ion-title>
</ion-toolbar>

View File

@@ -1,7 +1,7 @@
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button></ion-back-button>
<ion-back-button defaultHref="marketplace"></ion-back-button>
</ion-buttons>
<ion-title>Marketplace Listing</ion-title>
</ion-toolbar>

View File

@@ -2,7 +2,7 @@
<ion-toolbar>
<ion-title>LAN Settings</ion-title>
<ion-buttons slot="start">
<ion-back-button></ion-back-button>
<ion-back-button defaultHref="embassy"></ion-back-button>
</ion-buttons>
</ion-toolbar>
</ion-header>

View File

@@ -1,7 +1,7 @@
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button></ion-back-button>
<ion-back-button defaultHref="embassy"></ion-back-button>
</ion-buttons>
<ion-title>Preferences</ion-title>
</ion-toolbar>

View File

@@ -12,7 +12,7 @@
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button></ion-back-button>
<ion-back-button defaultHref="embassy"></ion-back-button>
</ion-buttons>
<ion-title>Backup Progress</ion-title>
</ion-toolbar>

View File

@@ -1,7 +1,7 @@
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button></ion-back-button>
<ion-back-button defaultHref="embassy"></ion-back-button>
</ion-buttons>
<ion-title>Logs</ion-title>
</ion-toolbar>

View File

@@ -1,7 +1,7 @@
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button></ion-back-button>
<ion-back-button defaultHref="embassy"></ion-back-button>
</ion-buttons>
<ion-title>Monitor</ion-title>
</ion-toolbar>

View File

@@ -1,7 +1,7 @@
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button></ion-back-button>
<ion-back-button defaultHref="embassy"></ion-back-button>
</ion-buttons>
<ion-title>About</ion-title>
</ion-toolbar>

View File

@@ -1,7 +1,7 @@
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button></ion-back-button>
<ion-back-button defaultHref="embassy"></ion-back-button>
</ion-buttons>
<ion-title>Active Sessions</ion-title>
</ion-toolbar>

View File

@@ -1,7 +1,7 @@
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button></ion-back-button>
<ion-back-button defaultHref="embassy"></ion-back-button>
</ion-buttons>
<ion-title>SSH Keys</ion-title>
</ion-toolbar>

View File

@@ -1,7 +1,7 @@
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button></ion-back-button>
<ion-back-button defaultHref="embassy"></ion-back-button>
</ion-buttons>
<ion-title>WiFi Settings</ion-title>
</ion-toolbar>

View File

@@ -2,10 +2,11 @@ import { HttpService } from '../http.service'
import { MockApiService } from './embassy-mock-api.service'
import { LiveApiService } from './embassy-live-api.service'
import { ConfigService } from '../config.service'
import { LocalStorageBootstrap } from '../patch-db/local-storage-bootstrap'
export function ApiServiceFactory (config: ConfigService, http: HttpService) {
export function ApiServiceFactory (config: ConfigService, http: HttpService, bootstrapper: LocalStorageBootstrap) {
if (config.mocks.enabled) {
return new MockApiService(http)
return new MockApiService(bootstrapper)
} else {
return new LiveApiService(http)
}

View File

@@ -5,12 +5,12 @@ import { DataModel } from 'src/app/services/patch-db/data-model'
import { RequestError } from '../http.service'
export abstract class ApiService implements Source<DataModel>, Http<DataModel> {
protected readonly sync = new Subject<Update<DataModel>>()
protected readonly sync$ = new Subject<Update<DataModel>>()
/** PatchDb Source interface. Post/Patch requests provide a source of patches to the db. */
// sequenceStream '_' is not used by the live api, but is overridden by the mock
watch$ (_?: Store<DataModel>): Observable<Update<DataModel>> {
return this.sync.asObservable()
return this.sync$.asObservable()
}
connectionMade$ = new Subject<void>()
@@ -201,12 +201,12 @@ export abstract class ApiService implements Source<DataModel>, Http<DataModel> {
return f(a)
.catch((e: RequestError) => {
if (e.revision) this.sync.next(e.revision)
if (e.revision) this.sync$.next(e.revision)
throw e
})
.then(({ response, revision }) => {
this.connectionMade$.next()
if (revision) this.sync.next(revision)
if (revision) this.sync$.next(revision)
return response
})
}

View File

@@ -1,22 +1,27 @@
import { Injectable } from '@angular/core'
import { pauseFor } from '../../util/misc.util'
import { ApiService } from './embassy-api.service'
import { PatchOp } from 'patch-db-client'
import { DependencyErrorType, InstallProgress, PackageDataEntry, PackageMainStatus, PackageState, ServerStatus } from 'src/app/services/patch-db/data-model'
import { PatchOp, Update, Operation, RemoveOperation } from 'patch-db-client'
import { DataModel, DependencyErrorType, InstallProgress, PackageDataEntry, PackageMainStatus, PackageState, ServerStatus } from 'src/app/services/patch-db/data-model'
import { Log, RR, WithRevision } from './api.types'
import { parsePropertiesPermissive } from 'src/app/util/properties.util'
import { Mock } from './api.fixures'
import { HttpService } from '../http.service'
import markdown from 'raw-loader!src/assets/markdown/md-sample.md'
import { Operation } from 'fast-json-patch'
import { BehaviorSubject } from 'rxjs'
import { LocalStorageBootstrap } from '../patch-db/local-storage-bootstrap'
import { mockPatchData } from './mock-patch'
@Injectable()
export class MockApiService extends ApiService {
readonly mockPatch$ = new BehaviorSubject<Update<DataModel>>(undefined)
private readonly revertTime = 4000
sequence: number
constructor (
private readonly http: HttpService,
) { super() }
private readonly bootstrapper: LocalStorageBootstrap,
) {
super()
}
async getStatic (url: string): Promise<string> {
await pauseFor(2000)
@@ -26,22 +31,35 @@ export class MockApiService extends ApiService {
// db
async getRevisions (since: number): Promise<RR.GetRevisionsRes> {
return this.http.rpcRequest<RR.GetRevisionsRes>({ method: 'db.revisions', params: { since } })
return this.getDump()
}
async getDump (): Promise<RR.GetDumpRes> {
return this.http.rpcRequest<RR.GetDumpRes>({ method: 'db.dump' })
const cache = await this.bootstrapper.init()
return {
id: cache.sequence,
value: cache.data,
expireId: null,
}
}
async setDbValueRaw (params: RR.SetDBValueReq): Promise<RR.SetDBValueRes> {
await pauseFor(2000)
return this.http.rpcRequest<WithRevision<null>>({ method: 'db.put.ui', params })
const patch = [
{
op: PatchOp.REPLACE,
path: '/ui' + params.pointer,
value: params.value,
},
]
return this.withRevision(patch)
}
// auth
async login (params: RR.LoginReq): Promise<RR.loginRes> {
await pauseFor(2000)
this.mockPatch$.next({ id: 1, value: mockPatchData, expireId: null })
return null
}
@@ -71,7 +89,8 @@ export class MockApiService extends ApiService {
value: params.value,
},
]
return this.http.rpcRequest<WithRevision<null>>({ method: 'db.patch', params: { patch } })
return this.withRevision(patch)
}
async getServerLogs (params: RR.GetServerLogsReq): Promise<RR.GetServerLogsRes> {
@@ -107,6 +126,10 @@ export class MockApiService extends ApiService {
downloaded: 0,
}
setTimeout(() => {
this.updateOSProgress(initialProgress.size)
}, 500)
const patch = [
{
op: PatchOp.REPLACE,
@@ -119,12 +142,8 @@ export class MockApiService extends ApiService {
value: initialProgress,
},
]
const res = await this.http.rpcRequest<RR.UpdateServerRes>({ method: 'db.patch', params: { patch } })
res.response = 'updating'
this.updateOSProgress(initialProgress.size)
return res
return this.withRevision(patch, 'updating')
}
async restartServer (params: RR.RestartServerReq): Promise<RR.RestartServerRes> {
@@ -198,11 +217,8 @@ export class MockApiService extends ApiService {
value: 0,
},
]
const { revision } = await this.http.rpcRequest<WithRevision<RR.GetNotificationsRes>>({ method: 'db.patch', params: { patch } }) as WithRevision<null>
return {
response: Mock.Notifications,
revision,
}
return this.withRevision(patch, Mock.Notifications)
}
async deleteNotification (params: RR.DeleteNotificationReq): Promise<RR.DeleteNotificationRes> {
@@ -264,55 +280,48 @@ export class MockApiService extends ApiService {
async createBackupRaw (params: RR.CreateBackupReq): Promise<RR.CreateBackupRes> {
await pauseFor(2000)
const path = '/server-info/status'
let patch = [
{
op: PatchOp.REPLACE,
path,
value: ServerStatus.BackingUp,
},
]
const res = await this.http.rpcRequest<WithRevision<null>>({ method: 'db.patch', params: { patch } })
const ids = ['bitcoind', 'lnd']
setTimeout(async () => {
for (let i = 0; i < ids.length; i++) {
const appPath = `/package-data/${ids[i]}/installed/status/main/status`
let appPatch = [
const appPatch = [
{
op: PatchOp.REPLACE,
path: appPath,
value: PackageMainStatus.BackingUp,
},
]
this.http.rpcRequest<WithRevision<null>>({ method: 'db.patch', params: { patch: appPatch } })
this.updateMock(appPatch)
await pauseFor(8000)
appPatch = [
{
op: PatchOp.REPLACE,
path: appPath,
value: PackageMainStatus.Stopped,
},
]
this.http.rpcRequest<WithRevision<null>>({ method: 'db.patch', params: { patch: appPatch } })
appPatch[0].value = PackageMainStatus.Stopped
this.updateMock(appPatch)
}
await pauseFor(1000)
// set server back to running
patch = [
const lastPatch = [
{
op: PatchOp.REPLACE,
path,
value: ServerStatus.Running,
},
]
this.http.rpcRequest<WithRevision<null>>({ method: 'db.patch', params: { patch } })
}, 200)
this.updateMock(lastPatch)
}, 500)
return res
const originalPatch = [
{
op: PatchOp.REPLACE,
path,
value: ServerStatus.BackingUp,
},
]
return this.withRevision(originalPatch)
}
// drives
@@ -362,6 +371,10 @@ export class MockApiService extends ApiService {
'unpack-complete': false,
}
setTimeout(async () => {
this.updateProgress(params.id, initialProgress)
}, 1000)
const pkg: PackageDataEntry = {
...Mock.LocalPkgs[params.id],
state: PackageState.Installing,
@@ -376,12 +389,7 @@ export class MockApiService extends ApiService {
value: pkg,
},
]
const res = await this.http.rpcRequest<WithRevision<null>>({ method: 'db.patch', params: { patch } })
setTimeout(async () => {
this.updateProgress(params.id, initialProgress)
}, 1000)
return res
return this.withRevision(patch)
}
async dryUpdatePackage (params: RR.DryUpdatePackageReq): Promise<RR.DryUpdatePackageRes> {
@@ -411,7 +419,7 @@ export class MockApiService extends ApiService {
value: true,
},
]
return this.http.rpcRequest<WithRevision<null>>({ method: 'db.patch', params: { patch } })
return this.withRevision(patch)
}
async restorePackagesRaw (params: RR.RestorePackagesReq): Promise<RR.RestorePackagesRes> {
@@ -440,12 +448,13 @@ export class MockApiService extends ApiService {
}, 2000)
return {
op: 'add',
op: PatchOp.ADD,
path: `/package-data/${id}`,
value: pkg,
}
})
return this.http.rpcRequest<WithRevision<null>>({ method: 'db.patch', params: { patch } })
return this.withRevision(patch)
}
async executePackageAction (params: RR.ExecutePackageActionReq): Promise<RR.ExecutePackageActionRes> {
@@ -459,18 +468,7 @@ export class MockApiService extends ApiService {
await pauseFor(2000)
setTimeout(async () => {
const patch0 = [
{
op: PatchOp.REPLACE,
path: path + '/status',
value: PackageMainStatus.Starting,
},
]
await this.http.rpcRequest<WithRevision<null>>({ method: 'db.patch', params: { patch: patch0 } })
await pauseFor(4000)
console.log('run')
const patch1 = [
const patch2 = [
{
op: PatchOp.REPLACE,
path: path + '/status',
@@ -482,9 +480,9 @@ export class MockApiService extends ApiService {
value: new Date().toISOString(),
},
]
await this.http.rpcRequest<WithRevision<null>>({ method: 'db.patch', params: { patch: patch1 } })
this.updateMock(patch2)
const patch2 = [
const patch3 = [
{
op: PatchOp.REPLACE,
path: path + '/health',
@@ -498,12 +496,11 @@ export class MockApiService extends ApiService {
},
},
]
await this.http.rpcRequest<WithRevision<null>>({ method: 'db.patch', params: { patch: patch2 } })
this.updateMock(patch3)
await pauseFor(2000)
const patch3 = [
const patch4 = [
{
op: PatchOp.REPLACE,
path: path + '/health',
@@ -528,11 +525,18 @@ export class MockApiService extends ApiService {
},
},
]
await this.http.rpcRequest<WithRevision<null>>({ method: 'db.patch', params: { patch: patch3 } })
this.updateMock(patch4)
}, 2000)
return { response: null}
const originalPatch = [
{
op: PatchOp.REPLACE,
path: path + '/status',
value: PackageMainStatus.Starting,
},
]
return this.withRevision(originalPatch)
}
async dryStopPackage (params: RR.DryStopPackageReq): Promise<RR.DryStopPackageRes> {
@@ -550,6 +554,18 @@ export class MockApiService extends ApiService {
async stopPackageRaw (params: RR.StopPackageReq): Promise<RR.StopPackageRes> {
await pauseFor(2000)
const path = `/package-data/${params.id}/installed/status/main`
setTimeout(() => {
const patch2 = [
{
op: PatchOp.REPLACE,
path: path + '/status',
value: PackageMainStatus.Stopped,
},
]
this.updateMock(patch2)
}, this.revertTime)
const patch = [
{
op: PatchOp.REPLACE,
@@ -562,19 +578,8 @@ export class MockApiService extends ApiService {
value: { },
},
]
const res = await this.http.rpcRequest<WithRevision<null>>({ method: 'db.patch', params: { patch } })
setTimeout(() => {
const patch = [
{
op: PatchOp.REPLACE,
path: path + '/status',
value: PackageMainStatus.Stopped,
},
]
this.http.rpcRequest<WithRevision<null>>({ method: 'db.patch', params: { patch } })
}, this.revertTime)
return res
return this.withRevision(patch)
}
async dryUninstallPackage (params: RR.DryUninstallPackageReq): Promise<RR.DryUninstallPackageRes> {
@@ -584,6 +589,17 @@ export class MockApiService extends ApiService {
async uninstallPackageRaw (params: RR.UninstallPackageReq): Promise<RR.UninstallPackageRes> {
await pauseFor(2000)
setTimeout(async () => {
const patch2 = [
{
op: PatchOp.REMOVE,
path: `/package-data/${params.id}`,
} as RemoveOperation,
]
this.updateMock(patch2)
}, this.revertTime)
const patch = [
{
op: PatchOp.REPLACE,
@@ -591,35 +607,19 @@ export class MockApiService extends ApiService {
value: PackageState.Removing,
},
]
let res: any
res = await this.http.rpcRequest<WithRevision<null>>({ method: 'db.patch', params: { patch } })
setTimeout(async () => {
const patch = [
{
op: PatchOp.REMOVE,
path: `/package-data/${params.id}`,
},
]
this.http.rpcRequest<WithRevision<null>>({ method: 'db.patch', params: { patch } })
}, this.revertTime)
return res
return this.withRevision(patch)
}
async deleteRecoveredPackageRaw (params: RR.DeleteRecoveredPackageReq): Promise<RR.DeleteAllNotificationsRes> {
async deleteRecoveredPackageRaw (params: RR.DeleteRecoveredPackageReq): Promise<RR.DeleteRecoveredPackageRes> {
await pauseFor(2000)
let res: any
const patch = [
{
op: PatchOp.REMOVE,
path: `/recovered-packages/${params.id}`,
},
} as RemoveOperation,
]
res = await this.http.rpcRequest<WithRevision<null>>({ method: 'db.patch', params: { patch } })
return res
return this.withRevision(patch)
}
async dryConfigureDependency (params: RR.DryConfigureDependencyReq): Promise<RR.DryConfigureDependencyRes> {
@@ -631,7 +631,7 @@ export class MockApiService extends ApiService {
}
}
private async updateProgress (id: string, initialProgress: InstallProgress) {
private async updateProgress (id: string, initialProgress: InstallProgress): Promise<void> {
const phases = [
{ progress: 'downloaded', completion: 'download-complete'},
{ progress: 'validated', completion: 'validation-complete'},
@@ -653,10 +653,10 @@ export class MockApiService extends ApiService {
op: PatchOp.REMOVE,
path: `/package-data/${id}/install-progress`,
},
{
op: PatchOp.REMOVE,
path: `/recovered-packages/${id}`,
},
// {
// op: PatchOp.REMOVE,
// path: `/recovered-packages/${id}`,
// } as RemoveOperation,
]
} else {
patch = [
@@ -667,23 +667,19 @@ export class MockApiService extends ApiService {
},
]
}
try {
await this.http.rpcRequest<WithRevision<null>>({ method: 'db.patch', params: { patch } })
} catch (e) {
console.error('Insufficient Mocks, happens when installing a service that does not exist in recovered-package')
}
this.updateMock(patch)
}
}
setTimeout(() => {
const patch = [
const patch2 = [
{
op: PatchOp.REPLACE,
path: `/package-data/${id}`,
value: Mock.LocalPkgs[id],
},
]
this.http.rpcRequest<WithRevision<null>>({ method: 'db.patch', params: { patch } })
this.updateMock(patch2)
}, 1000)
}
@@ -699,20 +695,20 @@ export class MockApiService extends ApiService {
value: downloaded,
},
]
await this.http.rpcRequest<WithRevision<null>>({ method: 'db.patch', params: { patch } })
this.updateMock(patch)
}
const patch = [
const patch2 = [
{
op: PatchOp.REPLACE,
path: `/server-info/update-progress/downloaded`,
value: size,
},
]
await this.http.rpcRequest<WithRevision<null>>({ method: 'db.patch', params: { patch } })
this.updateMock(patch2)
setTimeout(async () => {
const patch = [
const patch3: Operation[] = [
{
op: PatchOp.REPLACE,
path: '/server-info/status',
@@ -723,18 +719,48 @@ export class MockApiService extends ApiService {
path: '/server-info/update-progress',
},
]
await this.http.rpcRequest<WithRevision<null>>({ method: 'db.patch', params: { patch } })
this.updateMock(patch3)
// quickly revert server to "running" for continued testing
const patch2 = [
await pauseFor(100)
const patch4 = [
{
op: PatchOp.REPLACE,
path: '/server-info/status',
value: ServerStatus.Running,
},
]
this.http.rpcRequest<WithRevision<null>>({ method: 'db.patch', params: { patch: patch2 } })
this.updateMock(patch4)
}, 1000)
}
private async updateMock (patch: Operation[]): Promise<void> {
if (!this.sequence) {
const { sequence } = await this.bootstrapper.init()
this.sequence = sequence
}
const revision = {
id: ++this.sequence,
patch,
expireId: null,
}
console.log('REVISION', revision)
this.mockPatch$.next(revision)
}
private async withRevision<T> (patch: Operation[], response: T = null): Promise<WithRevision<T>> {
if (!this.sequence) {
const { sequence } = await this.bootstrapper.init()
this.sequence = sequence
}
const revision = {
id: ++this.sequence,
patch,
expireId: null,
}
console.log('REVISION', revision)
return { response, revision }
}
}

View File

@@ -0,0 +1,614 @@
import { DataModel, DependencyErrorType, DependencySeverity, DockerIoFormat, HealthResult, Manifest, PackageMainStatus, PackageState, ServerStatus } from 'src/app/services/patch-db/data-model'
export const mockPatchData: DataModel = {
'ui': {
'name': 'Matt\'s Embassy',
'auto-check-updates': true,
'pkg-order': [],
'ack-welcome': '1.0.0',
'ack-share-stats': false,
},
'server-info': {
'id': 'embassy-abcdefgh',
'version': '0.3.0',
'last-backup': null,
'status': ServerStatus.Running,
'lan-address': 'https://embassy-abcdefgh.local',
'tor-address': 'http://myveryownspecialtoraddress.onion',
'eos-marketplace': 'https://beta-registry-0-3.start9labs.com',
'package-marketplace': null,
'share-stats': false,
'unread-notification-count': 4,
// 'password-hash': '$argon2d$v=19$m=1024,t=1,p=1$YXNkZmFzZGZhc2RmYXNkZg$Ceev1I901G6UwU+hY0sHrFZ56D+o+LNJ',
'eos-version-compat': '>=0.3.0',
},
'recovered-packages': {
'btc-rpc-proxy': {
'title': 'Bitcoin Proxy',
'icon': 'assets/img/service-icons/btc-rpc-proxy.png',
'version': '0.2.2',
},
},
'package-data': {
'bitcoind': {
'state': PackageState.Installed,
'static-files': {
'license': '/public/package-data/bitcoind/0.20.0/LICENSE.md',
'icon': '/assets/img/service-icons/bitcoind.png',
'instructions': '/public/package-data/bitcoind/0.20.0/INSTRUCTIONS.md',
},
'manifest': {
'id': 'bitcoind',
'title': 'Bitcoin Core',
'version': '0.20.0',
'description': {
'short': 'A Bitcoin full node by Bitcoin Core.',
'long': 'Bitcoin is a decentralized consensus protocol and settlement network.',
},
'release-notes': 'Taproot, Schnorr, and more.',
'license': 'MIT',
'wrapper-repo': 'https://github.com/start9labs/bitcoind-wrapper',
'upstream-repo': 'https://github.com/bitcoin/bitcoin',
'support-site': 'https://bitcoin.org',
'marketing-site': 'https://bitcoin.org',
'donation-url': 'https://start9.com',
'alerts': {
'install': 'Bitcoin can take over a week to sync.',
'uninstall': 'Chain state will be lost, as will any funds stored on your Bitcoin Core waller that have not been backed up.',
'restore': null,
'start': null,
'stop': 'Stopping Bitcoin is bad for your health.',
},
'main': {
'type': 'docker',
'image': '',
'system': true,
'entrypoint': '',
'args': [],
'mounts': { },
'io-format': DockerIoFormat.Yaml,
'inject': false,
'shm-size': '',
},
'health-checks': {
'chain-state': { 'name': 'Chain State', 'description': 'Checks the chainstate' },
'ephemeral-health-check': { 'name': 'Ephemeral Health Check', 'description': 'Checks to see if your new user registrations are on. If they are but you\'re not expecting any new user signups, you should disable this in Config, as anyone who knows your onion URL can create accounts on your server.' },
'p2p-interface': { 'name': 'P2P Interface', 'description': 'Checks to see if your new user registrations are on. If they are but you\'re not expecting any new user signups, you should disable this in Config, as anyone who knows your onion URL can create accounts on your server.' },
'rpc-interface': { 'name': 'RPC Interface', 'description': 'Checks the RPC Interface' },
'unnecessary-health-check': { 'name': 'Unneccessary Health Check', 'description': 'Is totally not necessary to do this health check.' },
} as any,
'config': {
'get': { },
'set': { },
} as any,
'volumes': { },
'min-os-version': '0.2.12',
'interfaces': {
'ui': {
'name': 'Node Visualizer',
'description': 'Web application for viewing information about your node and the Bitcoin network.',
'ui': true,
'tor-config': {
'port-mapping': { },
},
'lan-config': { },
'protocols': [],
},
'rpc': {
'name': 'RPC',
'description': 'Used by wallets to interact with your Bitcoin Core node.',
'ui': false,
'tor-config': {
'port-mapping': { },
},
'lan-config': { },
'protocols': [],
},
'p2p': {
'name': 'P2P',
'description': 'Used by other Bitcoin nodes to communicate and interact with your node.',
'ui': false,
'tor-config': {
'port-mapping': { },
},
'lan-config': { },
'protocols': [],
},
},
'backup': {
'create': {
'type': 'docker',
'image': '',
'system': true,
'entrypoint': '',
'args': [],
'mounts': { },
'io-format': DockerIoFormat.Yaml,
'inject': false,
'shm-size': '',
},
'restore': {
'type': 'docker',
'image': '',
'system': true,
'entrypoint': '',
'args': [],
'mounts': { },
'io-format': DockerIoFormat.Yaml,
'inject': false,
'shm-size': '',
},
},
'migrations': null,
'actions': {
'resync': {
'name': 'Resync Blockchain',
'description': 'Use this to resync the Bitcoin blockchain from genesis',
'warning': 'This will take a couple of days.',
'allowed-statuses': [
PackageMainStatus.Running,
PackageMainStatus.Stopped,
],
'implementation': {
'type': 'docker',
'image': '',
'system': true,
'entrypoint': '',
'args': [],
'mounts': { },
'io-format': DockerIoFormat.Yaml,
'inject': false,
'shm-size': '',
},
'input-spec': {
'reason': {
'type': 'string',
'name': 'Re-sync Reason',
'description': 'Your reason for re-syncing. Why are you doing this?',
'nullable': false,
'masked': false,
'copyable': false,
'pattern': '^[a-zA-Z]+$',
'pattern-description': 'Must contain only letters.',
},
'name': {
'type': 'string',
'name': 'Your Name',
'description': 'Tell the class your name.',
'nullable': true,
'masked': false,
'copyable': false,
'pattern': null,
'pattern-description': null,
'warning': 'You may loose all your money by providing your name.',
},
'notifications': {
'name': 'Notification Preferences',
'type': 'list',
'subtype': 'enum',
'description': 'how you want to be notified',
'range': '[1,3]',
'default': [
'email',
],
'spec': {
'value-names': {
'email': 'Email',
'text': 'Text',
'call': 'Call',
'push': 'Push',
'webhook': 'Webhook',
},
'values': [
'email',
'text',
'call',
'push',
'webhook',
],
},
},
'days-ago': {
'type': 'number',
'name': 'Days Ago',
'description': 'Number of days to re-sync.',
'nullable': false,
'default': 100,
'range': '[0, 9999]',
'integral': true,
},
'top-speed': {
'type': 'number',
'name': 'Top Speed',
'description': 'The fastest you can possibly run.',
'nullable': false,
'default': null,
'range': '[-1000, 1000]',
'integral': false,
'units': 'm/s',
},
'testnet': {
'name': 'Testnet',
'type': 'boolean',
'description': 'determines whether your node is running on testnet or mainnet',
'warning': 'Chain will have to resync!',
'default': false,
},
'randomEnum': {
'name': 'Random Enum',
'type': 'enum',
'value-names': {
'null': 'Null',
'good': 'Good',
'bad': 'Bad',
'ugly': 'Ugly',
},
'default': 'null',
'description': 'This is not even real.',
'warning': 'Be careful changing this!',
'values': [
'null',
'good',
'bad',
'ugly',
],
},
'emergency-contact': {
'name': 'Emergency Contact',
'type': 'object',
'unique-by': null,
'description': 'The person to contact in case of emergency.',
'spec': {
'name': {
'type': 'string',
'name': 'Name',
'description': null,
'nullable': false,
'masked': false,
'copyable': false,
'pattern': '^[a-zA-Z]+$',
'pattern-description': 'Must contain only letters.',
},
'email': {
'type': 'string',
'name': 'Email',
'description': null,
'nullable': false,
'masked': false,
'copyable': true,
},
},
},
'ips': {
'name': 'Whitelist IPs',
'type': 'list',
'subtype': 'string',
'description': 'external ip addresses that are authorized to access your Bitcoin node',
'warning': 'Any IP you allow here will have RPC access to your Bitcoin node.',
'range': '[1,10]',
'default': [
'192.168.1.1',
],
'spec': {
'pattern': '^[0-9]{1,3}([,.][0-9]{1,3})?$',
'pattern-description': 'Must be a valid IP address',
'masked': false,
'copyable': false,
},
},
'bitcoinNode': {
'name': 'Bitcoin Node Settings',
'type': 'union',
'unique-by': null,
'description': 'The node settings',
'default': 'internal',
'warning': 'Careful changing this',
'tag': {
'id': 'type',
'name': 'Type',
'variant-names': {
'internal': 'Internal',
'external': 'External',
},
},
'variants': {
'internal': {
'lan-address': {
'name': 'LAN Address',
'type': 'pointer',
'subtype': 'package',
'target': 'lan-address',
'package-id': 'bitcoind',
'description': 'the lan address',
'interface': '',
},
'friendly-name': {
'name': 'Friendly Name',
'type': 'string',
'description': 'the lan address',
'nullable': true,
'masked': false,
'copyable': false,
},
},
'external': {
'public-domain': {
'name': 'Public Domain',
'type': 'string',
'description': 'the public address of the node',
'nullable': false,
'default': 'bitcoinnode.com',
'pattern': '.*',
'pattern-description': 'anything',
'masked': false,
'copyable': true,
},
},
},
},
},
},
},
'permissions': { },
'dependencies': { },
},
'installed': {
'manifest': { } as Manifest,
'last-backup': null,
'status': {
'configured': true,
'main': {
'status': PackageMainStatus.Running,
'started': '2021-06-14T20:49:17.774Z',
'health': {
'ephemeral-health-check': {
'result': HealthResult.Starting,
},
'chain-state': {
'result': HealthResult.Loading,
'message': 'Bitcoin is syncing from genesis',
},
'p2p-interface': {
'result': HealthResult.Success,
},
'rpc-interface': {
'result': HealthResult.Failure,
'error': 'RPC interface unreachable.',
},
'unnecessary-health-check': {
'result': HealthResult.Disabled,
},
},
},
'dependency-errors': { },
},
'interface-addresses': {
'ui': {
'tor-address': 'bitcoind-ui-address.onion',
'lan-address': 'bitcoind-ui-address.local',
},
'rpc': {
'tor-address': 'bitcoind-rpc-address.onion',
'lan-address': 'bitcoind-rpc-address.local',
},
'p2p': {
'tor-address': 'bitcoind-p2p-address.onion',
'lan-address': 'bitcoind-p2p-address.local',
},
},
'system-pointers': [],
'current-dependents': {
'lnd': {
'pointers': [],
'health-checks': [],
},
},
'current-dependencies': { },
'dependency-info': { },
},
},
'lnd': {
'state': PackageState.Installed,
'static-files': {
'license': '/public/package-data/lnd/0.11.1/LICENSE.md',
'icon': '/assets/img/service-icons/lnd.png',
'instructions': '/public/package-data/lnd/0.11.1/INSTRUCTIONS.md',
},
'manifest': {
'id': 'lnd',
'title': 'Lightning Network Daemon',
'version': '0.11.1',
'description': {
'short': 'A bolt spec compliant client.',
'long': 'More info about LND. More info about LND. More info about LND.',
},
'release-notes': 'Dual funded channels!',
'license': 'MIT',
'wrapper-repo': 'https://github.com/start9labs/lnd-wrapper',
'upstream-repo': 'https://github.com/lightningnetwork/lnd',
'support-site': 'https://lightning.engineering/',
'marketing-site': 'https://lightning.engineering/',
'donation-url': null,
'alerts': {
'install': null,
'uninstall': null,
'restore': 'If this is a duplicate instance of the same LND node, you may loose your funds.',
'start': 'Starting LND is good for your health.',
'stop': null,
},
'main': {
'type': 'docker',
'image': '',
'system': true,
'entrypoint': '',
'args': [],
'mounts': { },
'io-format': DockerIoFormat.Yaml,
'inject': false,
'shm-size': '',
},
'health-checks': { },
'config': {
'get': null,
'set': null,
},
'volumes': { },
'min-os-version': '0.2.12',
'interfaces': {
'rpc': {
'name': 'RPC interface',
'description': 'Good for connecting to your node at a distance.',
'ui': true,
'tor-config': {
'port-mapping': { },
},
'lan-config': {
'44': {
'ssl': true,
'mapping': 33,
},
},
'protocols': [],
},
'grpc': {
'name': 'GRPC',
'description': 'Certain wallet use grpc.',
'ui': false,
'tor-config': {
'port-mapping': { },
},
'lan-config': {
'66': {
'ssl': true,
'mapping': 55,
},
},
'protocols': [],
},
},
'backup': {
'create': {
'type': 'docker',
'image': '',
'system': true,
'entrypoint': '',
'args': [],
'mounts': { },
'io-format': DockerIoFormat.Yaml,
'inject': false,
'shm-size': '',
},
'restore': {
'type': 'docker',
'image': '',
'system': true,
'entrypoint': '',
'args': [],
'mounts': { },
'io-format': DockerIoFormat.Yaml,
'inject': false,
'shm-size': '',
},
},
'migrations': null,
'actions': {
'resync': {
'name': 'Resync Network Graph',
'description': 'Your node will resync its network graph.',
'warning': 'This will take a couple hours.',
'allowed-statuses': [
PackageMainStatus.Running,
],
'implementation': {
'type': 'docker',
'image': '',
'system': true,
'entrypoint': '',
'args': [],
'mounts': { },
'io-format': DockerIoFormat.Yaml,
'inject': false,
'shm-size': '',
},
'input-spec': null,
},
},
'permissions': { },
'dependencies': {
'bitcoind': {
'version': '=0.21.0',
'description': 'LND needs bitcoin to live.',
'requirement': {
'type': 'opt-out',
'how': 'You can use an external node from your Embassy if you prefer.',
},
'config': null,
'severity': DependencySeverity.Critical,
},
'btc-rpc-proxy': {
'version': '>=0.2.2',
'description': 'As long as Bitcoin is pruned, LND needs Bitcoin Proxy to fetch block over the P2P network.',
'requirement': {
'type': 'opt-in',
'how': 'To use Proxy\'s user management system, go to LND config and select Bitcoin Proxy under Bitcoin config.',
},
'config': null,
'severity': DependencySeverity.Warning,
},
},
},
'installed': {
'manifest': { } as Manifest,
'last-backup': null,
'status': {
'configured': true,
'main': {
'status': PackageMainStatus.Stopped,
},
'dependency-errors': {
'btc-rpc-proxy': {
'type': DependencyErrorType.ConfigUnsatisfied,
'error': 'This is a config unsatisfied error',
},
},
},
'interface-addresses': {
'rpc': {
'tor-address': 'lnd-rpc-address.onion',
'lan-address': 'lnd-rpc-address.local',
},
'grpc': {
'tor-address': 'lnd-grpc-address.onion',
'lan-address': 'lnd-grpc-address.local',
},
},
'system-pointers': [],
'current-dependents': { },
'current-dependencies': {
'bitcoind': {
'pointers': [],
'health-checks': [],
},
'btc-rpc-proxy': {
'pointers': [],
'health-checks': [],
},
},
'dependency-info': {
'bitcoind': {
'manifest': {
'title': 'Bitcoin Core',
} as Manifest,
'icon': 'assets/img/service-icons/bitcoind.png',
},
'btc-rpc-proxy': {
'manifest': {
'title': 'Bitcoin Proxy',
} as Manifest,
'icon': 'assets/img/service-icons/btc-rpc-proxy.png',
},
},
},
},
},
}

View File

@@ -1,10 +1,13 @@
import { PollSource, Source, WebsocketSource } from 'patch-db-client'
import { MockSource, PollSource, Source, WebsocketSource } from 'patch-db-client'
import { ConfigService } from 'src/app/services/config.service'
import { DataModel } from './data-model'
import { LocalStorageBootstrap } from './local-storage-bootstrap'
import { PatchDbService } from './patch-db.service'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { AuthService } from '../auth.service'
import { MockApiService } from '../api/embassy-mock-api.service'
import { filter } from 'rxjs/operators'
import { exists } from 'src/app/util/misc.util'
export function PatchDbServiceFactory (
config: ConfigService,
@@ -18,11 +21,7 @@ export function PatchDbServiceFactory (
let source: Source<DataModel>
if (mocks.enabled) {
if (mocks.connection === 'poll') {
source = new PollSource({ ...poll }, embassyApi)
} else {
source = new WebsocketSource(`ws://localhost:${config.mocks.wsPort}/db`)
}
source = new MockSource((embassyApi as MockApiService).mockPatch$.pipe(filter(exists)))
} else {
if (!supportsWebSockets) {
source = new PollSource({ ...poll }, embassyApi)