Welcome to Embassy
Get started by installing your first service.
diff --git a/ui/src/app/pages/apps-routes/app-list/app-list.page.ts b/ui/src/app/pages/apps-routes/app-list/app-list.page.ts
index daea2d6fe..3ed092d6c 100644
--- a/ui/src/app/pages/apps-routes/app-list/app-list.page.ts
+++ b/ui/src/app/pages/apps-routes/app-list/app-list.page.ts
@@ -21,11 +21,12 @@ export class AppListPage {
connectionFailure: boolean
pkgs: { [id: string]: PkgInfo } = { }
loading = true
+ empty = false
constructor (
private readonly config: ConfigService,
private readonly connectionService: ConnectionService,
- private readonly installPackageService: PackageLoadingService,
+ private readonly pkgLoading: PackageLoadingService,
public readonly patch: PatchDbService,
) { }
@@ -34,11 +35,7 @@ export class AppListPage {
this.patch.watch$('package-data')
.pipe(
filter(obj => {
- return obj &&
- (
- isEmptyObject(obj) ||
- Object.keys(obj).length !== Object.keys(this.pkgs).length
- )
+ return obj && Object.keys(obj).length !== Object.keys(this.pkgs).length
}),
)
.subscribe(pkgs => {
@@ -46,6 +43,8 @@ export class AppListPage {
const ids = Object.keys(pkgs)
+ this.empty = !ids.length
+
Object.keys(this.pkgs).forEach(id => {
if (!ids.includes(id)) {
this.pkgs[id].sub.unsubscribe()
@@ -59,7 +58,7 @@ export class AppListPage {
this.pkgs[id] = {
entry: pkgs[id],
primaryRendering: PrimaryRendering[renderPkgStatus(pkgs[id]).primary],
- installProgress: !isEmptyObject(pkgs[id]['install-progress']) ? this.installPackageService.transform(pkgs[id]['install-progress']) : undefined,
+ installProgress: !isEmptyObject(pkgs[id]['install-progress']) ? this.pkgLoading.transform(pkgs[id]['install-progress']) : undefined,
error: false,
sub: null,
}
@@ -69,7 +68,7 @@ export class AppListPage {
const statuses = renderPkgStatus(pkg)
const primaryRendering = PrimaryRendering[statuses.primary]
this.pkgs[id].entry = pkg
- this.pkgs[id].installProgress = !isEmptyObject(pkg['install-progress']) ? this.installPackageService.transform(pkg['install-progress']) : undefined
+ this.pkgs[id].installProgress = !isEmptyObject(pkg['install-progress']) ? this.pkgLoading.transform(pkg['install-progress']) : undefined
this.pkgs[id].primaryRendering = primaryRendering
this.pkgs[id].error = statuses.health === HealthStatus.Failure || [DependencyStatus.Issue, DependencyStatus.Critical].includes(statuses.dependency)
})
diff --git a/ui/src/app/pages/apps-routes/app-show/app-show.page.ts b/ui/src/app/pages/apps-routes/app-show/app-show.page.ts
index 4d68447b6..dbde1f305 100644
--- a/ui/src/app/pages/apps-routes/app-show/app-show.page.ts
+++ b/ui/src/app/pages/apps-routes/app-show/app-show.page.ts
@@ -13,8 +13,7 @@ import { DependencyStatus, HealthStatus, PrimaryRendering, PrimaryStatus, render
import { ConnectionFailure, ConnectionService } from 'src/app/services/connection.service'
import { ErrorToastService } from 'src/app/services/error-toast.service'
import { AppConfigPage } from 'src/app/modals/app-config/app-config.page'
-import { PackageLoadingService } from 'src/app/services/package-loading.service'
-import { ProgressData } from 'src/app/pipes/install-state.pipe'
+import { PackageLoadingService, ProgressData } from 'src/app/services/package-loading.service'
@Component({
selector: 'app-show',
diff --git a/ui/src/app/pages/server-routes/server-metrics/server-metrics.page.ts b/ui/src/app/pages/server-routes/server-metrics/server-metrics.page.ts
index 30b435a20..cfbce9de0 100644
--- a/ui/src/app/pages/server-routes/server-metrics/server-metrics.page.ts
+++ b/ui/src/app/pages/server-routes/server-metrics/server-metrics.page.ts
@@ -47,7 +47,10 @@ export class ServerMetricsPage {
async getMetrics (): Promise
{
try {
- this.metrics = await this.embassyApi.getServerMetrics({ })
+ const metrics = await this.embassyApi.getServerMetrics({ })
+ Object.entries(metrics).forEach(([key, val]) => {
+ this.metrics[key] = val
+ })
} catch (e) {
this.errToast.present(e)
this.stopDaemon()
diff --git a/ui/src/app/pipes/install-state.pipe.ts b/ui/src/app/pipes/install-state.pipe.ts
index bddc54231..6f000e120 100644
--- a/ui/src/app/pipes/install-state.pipe.ts
+++ b/ui/src/app/pipes/install-state.pipe.ts
@@ -1,4 +1,5 @@
import { Pipe, PipeTransform } from '@angular/core'
+import { PackageLoadingService, ProgressData } from '../services/package-loading.service'
import { InstallProgress } from '../services/patch-db/data-model'
@Pipe({
@@ -6,37 +7,11 @@ import { InstallProgress } from '../services/patch-db/data-model'
})
export class InstallState implements PipeTransform {
+ constructor (
+ private readonly pkgLoading: PackageLoadingService,
+ ) { }
+
transform (loadData: InstallProgress): ProgressData {
- let { downloaded, validated, unpacked, size, 'download-complete': downloadComplete, 'validation-complete': validationComplete, 'unpack-complete': unpackComplete } = loadData
- downloaded = downloadComplete ? size : downloaded
- validated = validationComplete ? size : validated
- unpacked = unpackComplete ? size : unpacked
-
- const downloadWeight = 1
- const validateWeight = .2
- const unpackWeight = .7
-
- const numerator = Math.floor(
- downloadWeight * downloaded +
- validateWeight * validated +
- unpackWeight * unpacked)
-
- const denominator = Math.floor(loadData.size * (downloadWeight + validateWeight + unpackWeight))
-
- return {
- totalProgress: Math.round(100 * numerator / denominator),
- downloadProgress: Math.round(100 * downloaded / size),
- validateProgress: Math.round(100 * validated / size),
- unpackProgress: Math.round(100 * unpacked / size),
- isComplete: downloadComplete && validationComplete && unpackComplete,
- }
+ return this.pkgLoading.transform(loadData)
}
}
-
-export interface ProgressData {
- totalProgress: number
- downloadProgress: number
- validateProgress: number
- unpackProgress: number
- isComplete: boolean
-}
\ No newline at end of file
diff --git a/ui/src/app/pkg-config/config-utilities.ts b/ui/src/app/pkg-config/config-utilities.ts
index e8ce7b14a..a7a0ba49a 100644
--- a/ui/src/app/pkg-config/config-utilities.ts
+++ b/ui/src/app/pkg-config/config-utilities.ts
@@ -6,7 +6,6 @@ export class Range {
minInclusive: boolean
maxInclusive: boolean
-
static from (s: string): Range {
const r = new Range()
r.minInclusive = s.startsWith('[')
@@ -18,11 +17,23 @@ export class Range {
}
checkIncludes (n: number) {
- if (this.hasMin() !== undefined && ((!this.minInclusive && this.min == n || (this.min > n)))) {
- throw new Error(`Value must be ${this.minMessage()}.`)
+ if (
+ this.hasMin() !== undefined &&
+ (
+ (this.min > n) ||
+ (!this.minInclusive && this.min == n)
+ )
+ ) {
+ throw new Error(this.minMessage())
}
- if (this.hasMax() && ((!this.maxInclusive && this.max == n || (this.max < n)))) {
- throw new Error(`Value must be ${this.maxMessage()}.`)
+ if (
+ this.hasMax() &&
+ (
+ (this.max < n) ||
+ (!this.maxInclusive && this.max == n)
+ )
+ ) {
+ throw new Error(this.maxMessage())
}
}
diff --git a/ui/src/app/services/api/api.fixures.ts b/ui/src/app/services/api/api.fixures.ts
index 84194e2ed..4d81d7617 100644
--- a/ui/src/app/services/api/api.fixures.ts
+++ b/ui/src/app/services/api/api.fixures.ts
@@ -979,7 +979,7 @@ export module Mock {
'warning': 'Chain will have to resync!',
'default': true,
},
- 'objectList': {
+ 'object-list': {
'name': 'Object List',
'type': 'list',
'subtype': 'object',
@@ -987,24 +987,24 @@ export module Mock {
'range': '[0,4]',
'default': [
{
- 'firstName': 'Admin',
- 'lastName': 'User',
+ 'first-name': 'Admin',
+ 'last-name': 'User',
'age': 40,
},
{
- 'firstName': 'Admin2',
- 'lastName': 'User',
+ 'first-name': 'Admin2',
+ 'last-name': 'User',
'age': 40,
},
],
// the outer spec here, at the list level, says that what's inside (the inner spec) pertains to its inner elements.
// it just so happens that ValueSpecObject's have the field { spec: ConfigSpec }
- // see 'unionList' below for a different example.
+ // see 'union-list' below for a different example.
'spec': {
- 'unique-by': 'lastName',
- 'display-as': `I'm {{lastName}}, {{firstName}} {{lastName}}`,
+ 'unique-by': 'last-name',
+ 'display-as': `I'm {{last-name}}, {{first-name}} {{last-name}}`,
'spec': {
- 'firstName': {
+ 'first-name': {
'name': 'First Name',
'type': 'string',
'description': 'User first name',
@@ -1013,7 +1013,7 @@ export module Mock {
'masked': false,
'copyable': false,
},
- 'lastName': {
+ 'last-name': {
'name': 'Last Name',
'type': 'string',
'description': 'User first name',
@@ -1040,7 +1040,7 @@ export module Mock {
},
},
},
- 'unionList': {
+ 'union-list': {
'name': 'Union List',
'type': 'list',
'subtype': 'union',
@@ -1105,7 +1105,7 @@ export module Mock {
'unique-by': 'preference',
},
},
- 'randomEnum': {
+ 'random-enum': {
'name': 'Random Enum',
'type': 'enum',
'value-names': {
@@ -1124,18 +1124,18 @@ export module Mock {
'option3',
],
},
- 'favoriteNumber': {
+ 'favorite-number': {
'name': 'Favorite Number',
'type': 'number',
'integral': false,
'description': 'Your favorite number of all time',
'warning': 'Once you set this number, it can never be changed without severe consequences.',
- 'nullable': false,
+ 'nullable': true,
'default': 7,
'range': '(-100,100]',
'units': 'BTC',
},
- 'unluckyNumbers': {
+ 'unlucky-numbers': {
'name': 'Unlucky Numbers',
'type': 'list',
'subtype': 'number',
@@ -1276,7 +1276,7 @@ export module Mock {
},
},
},
- 'bitcoinNode': {
+ 'bitcoin-node': {
'name': 'Bitcoin Node Settings',
'type': 'union',
'unique-by': null,
@@ -1324,9 +1324,9 @@ export module Mock {
'description': 'the default port for your Bitcoin node. default: 8333, testnet: 18333, regtest: 18444',
'nullable': false,
'default': 8333,
- 'range': '[0, 9999]',
+ 'range': '(0, 9998]',
},
- 'favoriteSlogan': {
+ 'favorite-slogan': {
'name': 'Favorite Slogan',
'type': 'string',
'description': 'You most favorite slogan in the whole world, used for paying you.',
@@ -1367,22 +1367,22 @@ export module Mock {
// actual config
config: {
testnet: false,
- objectList: [
+ 'object-list': [
{
- 'firstName': 'Admin',
- 'lastName': 'User',
+ 'first-name': 'Admin',
+ 'last-name': 'User',
'age': 40,
},
{
- 'firstName': 'Admin2',
- 'lastName': 'User',
+ 'first-name': 'Admin2',
+ 'last-name': 'User',
'age': 40,
},
],
- unionList: undefined,
- randomEnum: 'option1',
- favoriteNumber: 8,
- secondaryNumbers: undefined,
+ 'union-list': undefined,
+ 'random-enum': 'option1',
+ 'favorite-number': null,
+ 'secondary-numbers': undefined,
rpcsettings: {
laws: {
law1: 'The first law',
@@ -1395,7 +1395,7 @@ export module Mock {
advanced: {
notifications: ['email'],
},
- bitcoinNode: undefined,
+ 'bitcoin-node': undefined,
port: 5959,
rpcallowip: undefined,
rpcauth: ['matt: 8273gr8qwoidm1uid91jeh8y23gdio1kskmwejkdnm'],
@@ -1403,12 +1403,12 @@ export module Mock {
}
export const mockCupsDependentConfig = {
- randomEnum: 'option1',
+ 'random-enum': 'option1',
testnet: false,
- favoriteNumber: 8,
- secondaryNumbers: [13, 58, 20],
- objectList: [],
- unionList: [],
+ 'favorite-number': 8,
+ 'secondary-numbers': [13, 58, 20],
+ 'object-list': [],
+ 'union-list': [],
rpcsettings: {
laws: null,
rpcpass: null,
@@ -1418,7 +1418,7 @@ export module Mock {
advanced: {
notifications: [],
},
- bitcoinNode: { type: 'internal' },
+ 'bitcoin-Node': { type: 'internal' },
port: 5959,
rpcallowip: [],
rpcauth: ['matt: 8273gr8qwoidm1uid91jeh8y23gdio1kskmwejkdnm'],
diff --git a/ui/src/app/services/api/api.types.ts b/ui/src/app/services/api/api.types.ts
index 3aaaaa28f..ba4f692ed 100644
--- a/ui/src/app/services/api/api.types.ts
+++ b/ui/src/app/services/api/api.types.ts
@@ -175,11 +175,11 @@ export module RR {
export type StopPackageReq = WithExpire<{ id: string }> // package.stop
export type StopPackageRes = WithRevision
- export type DryRemovePackageReq = RemovePackageReq // package.remove.dry
- export type DryRemovePackageRes = Breakages
+ export type DryUninstallPackageReq = UninstallPackageReq // package.uninstall.dry
+ export type DryUninstallPackageRes = Breakages
- export type RemovePackageReq = WithExpire<{ id: string }> // package.remove
- export type RemovePackageRes = WithRevision
+ export type UninstallPackageReq = WithExpire<{ id: string }> // package.uninstall
+ export type UninstallPackageRes = WithRevision
export type DryConfigureDependencyReq = { 'dependency-id': string, 'dependent-id': string } // package.dependency.configure.dry
export type DryConfigureDependencyRes = object
diff --git a/ui/src/app/services/api/embassy-api.service.ts b/ui/src/app/services/api/embassy-api.service.ts
index 4e2060444..e5eb38f8b 100644
--- a/ui/src/app/services/api/embassy-api.service.ts
+++ b/ui/src/app/services/api/embassy-api.service.ts
@@ -171,11 +171,11 @@ export abstract class ApiService implements Source, Http {
() => this.stopPackageRaw(params),
)()
- abstract dryRemovePackage (params: RR.DryRemovePackageReq): Promise
+ abstract dryUninstallPackage (params: RR.DryUninstallPackageReq): Promise
- protected abstract removePackageRaw (params: RR.RemovePackageReq): Promise
- removePackage = (params: RR.RemovePackageReq) => this.syncResponse(
- () => this.removePackageRaw(params),
+ protected abstract uninstallPackageRaw (params: RR.UninstallPackageReq): Promise
+ uninstallPackage = (params: RR.UninstallPackageReq) => this.syncResponse(
+ () => this.uninstallPackageRaw(params),
)()
abstract dryConfigureDependency (params: RR.DryConfigureDependencyReq): Promise
diff --git a/ui/src/app/services/api/embassy-live-api.service.ts b/ui/src/app/services/api/embassy-live-api.service.ts
index b0ecb2ae3..e9a35d4e6 100644
--- a/ui/src/app/services/api/embassy-live-api.service.ts
+++ b/ui/src/app/services/api/embassy-live-api.service.ts
@@ -260,12 +260,12 @@ export class LiveApiService extends ApiService {
return this.http.rpcRequest({ method: 'package.stop', params })
}
- async dryRemovePackage (params: RR.DryRemovePackageReq): Promise {
- return this.http.rpcRequest({ method: 'package.remove.dry', params })
+ async dryUninstallPackage (params: RR.DryUninstallPackageReq): Promise {
+ return this.http.rpcRequest({ method: 'package.uninstall.dry', params })
}
- async removePackageRaw (params: RR.RemovePackageReq): Promise {
- return this.http.rpcRequest({ method: 'package.remove', params })
+ async uninstallPackageRaw (params: RR.UninstallPackageReq): Promise {
+ return this.http.rpcRequest({ method: 'package.uninstall', params })
}
async dryConfigureDependency (params: RR.DryConfigureDependencyReq): Promise {
diff --git a/ui/src/app/services/api/embassy-mock-api.service.ts b/ui/src/app/services/api/embassy-mock-api.service.ts
index 0fad3ad4b..7f0b6ee9f 100644
--- a/ui/src/app/services/api/embassy-mock-api.service.ts
+++ b/ui/src/app/services/api/embassy-mock-api.service.ts
@@ -452,12 +452,12 @@ export class MockApiService extends ApiService {
return res
}
- async dryRemovePackage (params: RR.DryRemovePackageReq): Promise {
+ async dryUninstallPackage (params: RR.DryUninstallPackageReq): Promise {
await pauseFor(2000)
return { }
}
- async removePackageRaw (params: RR.RemovePackageReq): Promise {
+ async uninstallPackageRaw (params: RR.UninstallPackageReq): Promise {
await pauseFor(2000)
const patch = [
{
diff --git a/ui/src/app/services/form.service.ts b/ui/src/app/services/form.service.ts
index d50cc5e27..1b8264a7e 100644
--- a/ui/src/app/services/form.service.ts
+++ b/ui/src/app/services/form.service.ts
@@ -153,12 +153,13 @@ function isFullUnion (spec: ValueSpecUnion | ListValueSpecUnion): spec is ValueS
export function numberInRange (stringRange: string): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
- if (!control.value) return null
+ const value = control.value
+ if (!value) return null
try {
- Range.from(stringRange).checkIncludes(control.value)
+ Range.from(stringRange).checkIncludes(value)
return null
} catch (e) {
- return { numberNotInRange: { value: getRangeMessage(stringRange) } }
+ return { numberNotInRange: { value: `Number must be ${e.message}` } }
}
}
}
@@ -181,30 +182,15 @@ export function isInteger (): ValidatorFn {
export function listInRange (stringRange: string): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
- const range = Range.from(stringRange)
- const min = range.integralMin()
- const max = range.integralMax()
- const length = control.value.length
- if ((min && length < min) || (max && length > max)) {
- return { listNotInRange: { value: getRangeMessage(stringRange, true) } }
- } else {
+ try {
+ Range.from(stringRange).checkIncludes(control.value.length)
return null
+ } catch (e) {
+ return { numberNotInRange: { value: `List must be ${e.message}` } }
}
}
}
-function getRangeMessage (stringRange: string, isList = false): string {
- const range = Range.from(stringRange)
- const min = range.integralMin()
- const max = range.integralMax()
- const messageStart = isList ? 'List length must be ' : 'Must be '
- const minMessage = !!min ? `greater than ${min}` : ''
- const and = !!min && !!max ? ' and ' : ''
- const maxMessage = !!max ? `less than ${max}` : ''
-
- return `${messageStart} ${minMessage}${and}${maxMessage}`
-}
-
export function listUnique (spec: ValueSpecList): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
for (let idx = 0; idx < control.value.length; idx++) {
@@ -391,48 +377,41 @@ function isUnion (spec: any): spec is ListValueSpecUnion {
return !!spec.tag
}
-const sampleUniqueBy: UniqueBy = {
- all: [
- 'last name',
- { any: [
- 'favorite color',
- null,
- ] },
- { any: [
- 'favorite color',
- 'first name',
- null,
- ] },
- ],
-}
-
-export function convertToNumberRecursive (configSpec: ConfigSpec, group: FormGroup) {
+export function convertValuesRecursive (configSpec: ConfigSpec, group: FormGroup) {
Object.entries(configSpec).forEach(([key, valueSpec]) => {
if (valueSpec.type === 'number') {
const control = group.get(key)
- control.setValue(Number(control.value))
+ control.setValue(control.value ? Number(control.value) : null)
+ } else if (valueSpec.type === 'string') {
+ const control = group.get(key)
+ if (!control.value) control.setValue(null)
} else if (valueSpec.type === 'object') {
- convertToNumberRecursive(valueSpec.spec, group.get(key) as FormGroup)
+ convertValuesRecursive(valueSpec.spec, group.get(key) as FormGroup)
} else if (valueSpec.type === 'union') {
const control = group.get(key) as FormGroup
const spec = valueSpec.variants[control.controls[valueSpec.tag.id].value]
- convertToNumberRecursive(spec, control)
+ convertValuesRecursive(spec, control)
} else if (valueSpec.type === 'list') {
const formArr = group.get(key) as FormArray
if (valueSpec.subtype === 'number') {
formArr.controls.forEach(control => {
- control.setValue(Number(control.value))
+ control.setValue(control.value ? Number(control.value) : null)
+ })
+ } else if (valueSpec.subtype === 'string') {
+ formArr.controls.forEach(control => {
+ if (!control.value) control.setValue(null)
+ control.setValue(control.value ? Number(control.value) : null)
})
} else if (valueSpec.subtype === 'object') {
formArr.controls.forEach((formGroup: FormGroup) => {
const objectSpec = valueSpec.spec as ListValueSpecObject
- convertToNumberRecursive(objectSpec.spec, formGroup)
+ convertValuesRecursive(objectSpec.spec, formGroup)
})
} else if (valueSpec.subtype === 'union') {
formArr.controls.forEach((formGroup: FormGroup) => {
const unionSpec = valueSpec.spec as ListValueSpecUnion
const spec = unionSpec.variants[formGroup.controls[unionSpec.tag.id].value]
- convertToNumberRecursive(spec, formGroup)
+ convertValuesRecursive(spec, formGroup)
})
}
}