From cb7790ccbaf9e5eb447bd58528b688892d72780b Mon Sep 17 00:00:00 2001 From: Matt Hill Date: Tue, 7 Mar 2023 14:37:14 -0700 Subject: [PATCH] update FE types and unify sideload page with marketplace show --- .../src/pages/show/about/about.component.scss | 4 - .../pages/show/package/package.component.html | 4 +- frontend/projects/marketplace/src/types.ts | 12 +- .../projects/shared/src/util/misc.util.ts | 2 + .../ui/src/app/app/menu/menu.component.ts | 5 +- .../any-link/any-link.component.module.ts | 1 - .../components/status/status.component.html | 10 +- .../app/components/status/status.component.ts | 1 - .../modals/app-config/app-config.page.html | 11 +- .../app/modals/app-config/app-config.page.ts | 12 +- .../backup-select/backup-select.page.ts | 4 +- .../app-actions/app-actions.module.ts | 8 +- .../app-actions/app-actions.page.html | 26 +- .../app-actions/app-actions.page.ts | 112 +-- .../app-interfaces-item.component.html | 60 +- .../app-interfaces/app-interfaces.module.ts | 8 +- .../app-interfaces/app-interfaces.page.html | 19 +- .../app-interfaces/app-interfaces.page.ts | 106 ++- .../app-list-pkg/app-list-pkg.component.html | 25 +- .../app-list-pkg/app-list-pkg.component.ts | 9 +- .../apps-routes/app-list/app-list.module.ts | 2 - .../apps-routes/app-show/app-show.module.ts | 8 +- .../app-show-additional.component.html | 2 +- .../app-show-additional.component.ts | 8 +- .../app-show-header.component.html | 2 +- .../app-show-status.component.html | 15 +- .../app-show-status.component.ts | 16 +- .../app-show/pipes/to-buttons.pipe.ts | 8 +- .../app-show/pipes/to-dependencies.pipe.ts | 61 +- .../developer-menu/form-info.ts | 2 +- .../marketplace-show-components.module.ts | 46 ++ .../marketplace-show-controls.component.html | 6 +- .../marketplace-show-controls.component.ts | 0 .../marketplace-show-controls.page.scss | 0 .../marketplace-show-dependent.component.html | 4 +- .../marketplace-show-dependent.component.scss | 0 .../marketplace-show-dependent.component.ts | 0 .../marketplace-show-header.component.html | 0 .../marketplace-show-header.component.ts | 0 .../marketplace-show.module.ts | 18 +- .../backing-up/backing-up.component.html | 4 +- .../server-routes/sideload/sideload.module.ts | 12 + .../server-routes/sideload/sideload.page.html | 146 ++-- .../server-routes/sideload/sideload.page.scss | 49 +- .../server-routes/sideload/sideload.page.ts | 134 ++-- .../ui/src/app/pages/updates/updates.page.ts | 8 +- .../app/pipes/launchable/launchable.module.ts | 8 - .../app/pipes/launchable/launchable.pipe.ts | 22 - .../projects/ui/src/app/pipes/ui/ui.pipe.ts | 6 +- .../ui/src/app/services/api/api.fixures.ts | 636 +++--------------- .../ui/src/app/services/api/api.types.ts | 3 +- .../services/api/embassy-mock-api.service.ts | 17 +- .../ui/src/app/services/api/mock-patch.ts | 491 ++------------ .../ui/src/app/services/config.service.ts | 68 +- .../src/app/services/patch-db/data-model.ts | 205 ++---- .../services/pkg-status-rendering.service.ts | 4 +- .../src/app/services/ui-launcher.service.ts | 29 +- 57 files changed, 699 insertions(+), 1780 deletions(-) create mode 100644 frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-show/components/marketplace-show-components.module.ts rename frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-show/{ => components}/marketplace-show-controls/marketplace-show-controls.component.html (81%) rename frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-show/{ => components}/marketplace-show-controls/marketplace-show-controls.component.ts (100%) rename frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-show/{ => components}/marketplace-show-controls/marketplace-show-controls.page.scss (100%) rename frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-show/{ => components}/marketplace-show-dependent/marketplace-show-dependent.component.html (84%) rename frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-show/{ => components}/marketplace-show-dependent/marketplace-show-dependent.component.scss (100%) rename frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-show/{ => components}/marketplace-show-dependent/marketplace-show-dependent.component.ts (100%) rename frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-show/{ => components}/marketplace-show-header/marketplace-show-header.component.html (100%) rename frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-show/{ => components}/marketplace-show-header/marketplace-show-header.component.ts (100%) delete mode 100644 frontend/projects/ui/src/app/pipes/launchable/launchable.module.ts delete mode 100644 frontend/projects/ui/src/app/pipes/launchable/launchable.pipe.ts diff --git a/frontend/projects/marketplace/src/pages/show/about/about.component.scss b/frontend/projects/marketplace/src/pages/show/about/about.component.scss index c889b8ac7..e69de29bb 100644 --- a/frontend/projects/marketplace/src/pages/show/about/about.component.scss +++ b/frontend/projects/marketplace/src/pages/show/about/about.component.scss @@ -1,4 +0,0 @@ -.all-notes { - position: absolute; - right: 10px; -} diff --git a/frontend/projects/marketplace/src/pages/show/package/package.component.html b/frontend/projects/marketplace/src/pages/show/package/package.component.html index ca853030e..6007ce65b 100644 --- a/frontend/projects/marketplace/src/pages/show/package/package.component.html +++ b/frontend/projects/marketplace/src/pages/show/package/package.component.html @@ -3,8 +3,8 @@

{{ pkg.manifest.title }}

{{ pkg.manifest.version | displayEmver }}

-

- Released: {{ pkg['published-at'] | date: 'medium' }} +

+ Released: {{ published | date : 'medium' }}

diff --git a/frontend/projects/marketplace/src/types.ts b/frontend/projects/marketplace/src/types.ts index 8187d3bc0..d079985e5 100644 --- a/frontend/projects/marketplace/src/types.ts +++ b/frontend/projects/marketplace/src/types.ts @@ -23,7 +23,7 @@ export interface MarketplacePkg { icon: Url license: Url instructions: Url - manifest: MarketplaceManifest + manifest: Manifest categories: string[] versions: string[] 'dependency-metadata': { @@ -38,7 +38,7 @@ export interface DependencyMetadata { hidden: boolean } -export interface MarketplaceManifest { +export interface Manifest { id: string title: string version: string @@ -52,7 +52,7 @@ export interface MarketplaceManifest { } replaces?: string[] 'release-notes': string - license: string // type of license + license: string // name of license 'wrapper-repo': Url 'upstream-repo': Url 'support-site': Url @@ -65,10 +65,11 @@ export interface MarketplaceManifest { start: string | null stop: string | null } - dependencies: Record> + dependencies: Record + 'os-version': string } -export interface Dependency { +export interface Dependency { version: string requirement: | { @@ -83,5 +84,4 @@ export interface Dependency { type: 'required' } description: string | null - config: T } diff --git a/frontend/projects/shared/src/util/misc.util.ts b/frontend/projects/shared/src/util/misc.util.ts index 33d7a276d..b79411daf 100644 --- a/frontend/projects/shared/src/util/misc.util.ts +++ b/frontend/projects/shared/src/util/misc.util.ts @@ -53,3 +53,5 @@ export function toUrl(text: string | null | undefined): string { return '' } } + +export type WithId = T & { id: string } diff --git a/frontend/projects/ui/src/app/app/menu/menu.component.ts b/frontend/projects/ui/src/app/app/menu/menu.component.ts index abe5d89b7..49f4f674e 100644 --- a/frontend/projects/ui/src/app/app/menu/menu.component.ts +++ b/frontend/projects/ui/src/app/app/menu/menu.component.ts @@ -94,10 +94,7 @@ export class MenuComponent { Object.entries(marketplace).reduce((list, [_, store]) => { store?.packages.forEach(({ manifest: { id, version } }) => { if ( - this.emver.compare( - version, - local[id]?.installed?.manifest.version || '', - ) === 1 + this.emver.compare(version, local[id]?.manifest.version || '') === 1 ) list.add(id) }) diff --git a/frontend/projects/ui/src/app/components/any-link/any-link.component.module.ts b/frontend/projects/ui/src/app/components/any-link/any-link.component.module.ts index 109b77f06..4ea67eb50 100644 --- a/frontend/projects/ui/src/app/components/any-link/any-link.component.module.ts +++ b/frontend/projects/ui/src/app/components/any-link/any-link.component.module.ts @@ -1,7 +1,6 @@ import { NgModule } from '@angular/core' import { CommonModule } from '@angular/common' import { RouterModule } from '@angular/router' - import { AnyLinkComponent } from './any-link.component' @NgModule({ diff --git a/frontend/projects/ui/src/app/components/status/status.component.html b/frontend/projects/ui/src/app/components/status/status.component.html index 50ffa4860..c5868e99e 100644 --- a/frontend/projects/ui/src/app/components/status/status.component.html +++ b/frontend/projects/ui/src/app/components/status/status.component.html @@ -9,13 +9,6 @@ {{ (connected$ | async) ? rendering.display : 'Unknown' }} - this may take a while Installing - {{ progress }} + + {{ progress }}

diff --git a/frontend/projects/ui/src/app/components/status/status.component.ts b/frontend/projects/ui/src/app/components/status/status.component.ts index 14ece402c..28077098b 100644 --- a/frontend/projects/ui/src/app/components/status/status.component.ts +++ b/frontend/projects/ui/src/app/components/status/status.component.ts @@ -21,7 +21,6 @@ export class StatusComponent { @Input() style?: string = 'regular' @Input() weight?: string = 'normal' @Input() installProgress?: InstallProgress - @Input() sigtermTimeout?: string | null = null readonly connected$ = this.connectionService.connected$ diff --git a/frontend/projects/ui/src/app/modals/app-config/app-config.page.html b/frontend/projects/ui/src/app/modals/app-config/app-config.page.html index 6772e9f6d..3b70f65cc 100644 --- a/frontend/projects/ui/src/app/modals/app-config/app-config.page.html +++ b/frontend/projects/ui/src/app/modals/app-config/app-config.page.html @@ -20,7 +20,7 @@ - {{ loadingError }} + {{ loadingError }} @@ -59,13 +59,14 @@

{{ pkg.manifest.title }} + {{ pkg.manifest.title }} +

@@ -81,7 +82,7 @@ - +

No config options for {{ pkg.manifest.title }} {{ @@ -112,7 +113,7 @@ diff --git a/frontend/projects/ui/src/app/modals/app-config/app-config.page.ts b/frontend/projects/ui/src/app/modals/app-config/app-config.page.ts index 76b416c95..1a8f7bb26 100644 --- a/frontend/projects/ui/src/app/modals/app-config/app-config.page.ts +++ b/frontend/projects/ui/src/app/modals/app-config/app-config.page.ts @@ -17,6 +17,7 @@ import { InputSpec } from 'start-sdk/types/config-types' import { DataModel, PackageDataEntry, + PackageState, } from 'src/app/services/patch-db/data-model' import { PatchDB } from 'patch-db-client' import { UntypedFormGroup } from '@angular/forms' @@ -36,10 +37,10 @@ import { Breakages } from 'src/app/services/api/api.types' }) export class AppConfigPage { @Input() pkgId!: string - @Input() dependentInfo?: DependentInfo pkg!: PackageDataEntry + loadingText = '' configSpec?: InputSpec @@ -53,8 +54,6 @@ export class AppConfigPage { saving = false loadingError: string | IonicSafeString = '' - hasOptions = false - constructor( private readonly embassyApi: ApiService, private readonly errToast: ErrorToastService, @@ -68,10 +67,9 @@ export class AppConfigPage { async ngOnInit() { try { const pkg = await getPackage(this.patch, this.pkgId) - if (!pkg) return - this.pkg = pkg + if (!pkg?.installed?.['has-config']) return - if (!this.pkg.manifest.config) return + this.pkg = pkg let newConfig: object | undefined let patch: Operation[] | undefined @@ -104,8 +102,6 @@ export class AppConfigPage { newConfig || this.original, ) - this.hasOptions = false - if (patch) { this.diff = this.getDiff(patch) this.markDirty(patch) diff --git a/frontend/projects/ui/src/app/modals/backup-select/backup-select.page.ts b/frontend/projects/ui/src/app/modals/backup-select/backup-select.page.ts index 032c0c840..bff2bfd1b 100644 --- a/frontend/projects/ui/src/app/modals/backup-select/backup-select.page.ts +++ b/frontend/projects/ui/src/app/modals/backup-select/backup-select.page.ts @@ -1,6 +1,6 @@ import { Component } from '@angular/core' import { ModalController } from '@ionic/angular' -import { map, take } from 'rxjs/operators' +import { map } from 'rxjs/operators' import { DataModel, PackageState } from 'src/app/services/patch-db/data-model' import { PatchDB } from 'patch-db-client' import { firstValueFrom } from 'rxjs' @@ -36,7 +36,7 @@ export class BackupSelectPage { return { id, title, - icon: pkg['static-files'].icon, + icon: pkg.icon, disabled: pkg.state !== PackageState.Installed, checked: pkg.state === PackageState.Installed, } diff --git a/frontend/projects/ui/src/app/pages/apps-routes/app-actions/app-actions.module.ts b/frontend/projects/ui/src/app/pages/apps-routes/app-actions/app-actions.module.ts index 5f30abc0e..10927d60a 100644 --- a/frontend/projects/ui/src/app/pages/apps-routes/app-actions/app-actions.module.ts +++ b/frontend/projects/ui/src/app/pages/apps-routes/app-actions/app-actions.module.ts @@ -2,7 +2,11 @@ import { NgModule } from '@angular/core' import { CommonModule } from '@angular/common' import { Routes, RouterModule } from '@angular/router' import { IonicModule } from '@ionic/angular' -import { AppActionsPage, AppActionsItemComponent } from './app-actions.page' +import { + AppActionsPage, + AppActionsItemComponent, + GroupActionsPipe, +} from './app-actions.page' import { QRComponentModule } from 'src/app/components/qr/qr.component.module' import { SharedPipesModule } from '@start9labs/shared' import { GenericFormPageModule } from 'src/app/modals/generic-form/generic-form.module' @@ -25,6 +29,6 @@ const routes: Routes = [ GenericFormPageModule, ActionSuccessPageModule, ], - declarations: [AppActionsPage, AppActionsItemComponent], + declarations: [AppActionsPage, AppActionsItemComponent, GroupActionsPipe], }) export class AppActionsPageModule {} diff --git a/frontend/projects/ui/src/app/pages/apps-routes/app-actions/app-actions.page.html b/frontend/projects/ui/src/app/pages/apps-routes/app-actions/app-actions.page.html index adcfcb829..beb8bb339 100644 --- a/frontend/projects/ui/src/app/pages/apps-routes/app-actions/app-actions.page.html +++ b/frontend/projects/ui/src/app/pages/apps-routes/app-actions/app-actions.page.html @@ -21,17 +21,19 @@ > - - Actions for {{ pkg.manifest.title }} - - + + Actions for {{ pkg.manifest.title }} +

+ +
+ diff --git a/frontend/projects/ui/src/app/pages/apps-routes/app-actions/app-actions.page.ts b/frontend/projects/ui/src/app/pages/apps-routes/app-actions/app-actions.page.ts index 68809574e..aed071eed 100644 --- a/frontend/projects/ui/src/app/pages/apps-routes/app-actions/app-actions.page.ts +++ b/frontend/projects/ui/src/app/pages/apps-routes/app-actions/app-actions.page.ts @@ -1,4 +1,10 @@ -import { ChangeDetectionStrategy, Component, Input } from '@angular/core' +import { + ChangeDetectionStrategy, + Component, + Input, + Pipe, + PipeTransform, +} from '@angular/core' import { ActivatedRoute } from '@angular/router' import { ApiService } from 'src/app/services/api/embassy-api.service' import { @@ -12,12 +18,18 @@ import { Action, DataModel, PackageDataEntry, - PackageMainStatus, + PackageState, } from 'src/app/services/patch-db/data-model' import { GenericFormPage } from 'src/app/modals/generic-form/generic-form.page' -import { isEmptyObject, ErrorToastService, getPkgId } from '@start9labs/shared' +import { + isEmptyObject, + ErrorToastService, + getPkgId, + WithId, +} from '@start9labs/shared' import { ActionSuccessPage } from 'src/app/modals/action-success/action-success.page' import { hasCurrentDeps } from 'src/app/util/has-deps' +import { filter } from 'rxjs' @Component({ selector: 'app-actions', @@ -27,7 +39,9 @@ import { hasCurrentDeps } from 'src/app/util/has-deps' }) export class AppActionsPage { readonly pkgId = getPkgId(this.route) - readonly pkg$ = this.patch.watch$('package-data', this.pkgId) + readonly pkg$ = this.patch + .watch$('package-data', this.pkgId) + .pipe(filter(pkg => pkg.state === PackageState.Installed)) constructor( private readonly route: ActivatedRoute, @@ -40,28 +54,27 @@ export class AppActionsPage { private readonly patch: PatchDB, ) {} - async handleAction( - pkg: PackageDataEntry, - action: { key: string; value: Action }, - ) { - const status = pkg.installed?.status - if ( - status && - (action.value['allowed-statuses'] as PackageMainStatus[]).includes( - status.main.status, - ) - ) { - if (!isEmptyObject(action.value['input-spec'] || {})) { + async handleAction(action: WithId) { + if (action.disabled) { + const alert = await this.alertCtrl.create({ + header: 'Forbidden', + message: action.disabled, + buttons: ['OK'], + cssClass: 'alert-error-message enter-click', + }) + await alert.present() + } else { + if (!isEmptyObject(action['input-spec'] || {})) { const modal = await this.modalCtrl.create({ component: GenericFormPage, componentProps: { - title: action.value.name, - spec: action.value['input-spec'], + title: action.name, + spec: action['input-spec'], buttons: [ { text: 'Execute', handler: (value: any) => { - return this.executeAction(action.key, value) + return this.executeAction(action.id, value) }, isSubmit: true, }, @@ -72,9 +85,9 @@ export class AppActionsPage { } else { const alert = await this.alertCtrl.create({ header: 'Confirm', - message: `Are you sure you want to execute action "${ - action.value.name - }"? ${action.value.warning || ''}`, + message: `Are you sure you want to execute action "${action.name}"? ${ + action.warning || '' + }`, buttons: [ { text: 'Cancel', @@ -83,7 +96,7 @@ export class AppActionsPage { { text: 'Execute', handler: () => { - this.executeAction(action.key) + this.executeAction(action.id) }, cssClass: 'enter-click', }, @@ -91,31 +104,6 @@ export class AppActionsPage { }) await alert.present() } - } else { - const statuses = [...action.value['allowed-statuses']] - const last = statuses.pop() - let statusesStr = statuses.join(', ') - let error = '' - if (statuses.length) { - if (statuses.length > 1) { - // oxford comma - statusesStr += ',' - } - statusesStr += ` or ${last}` - } else if (last) { - statusesStr = `${last}` - } else { - error = `There is no status for which this action may be run. This is a bug. Please file an issue with the service maintainer.` - } - const alert = await this.alertCtrl.create({ - header: 'Forbidden', - message: - error || - `Action "${action.value.name}" can only be executed when service is ${statusesStr}`, - buttons: ['OK'], - cssClass: 'alert-error-message enter-click', - }) - await alert.present() } } @@ -224,3 +212,31 @@ interface LocalAction { export class AppActionsItemComponent { @Input() action!: LocalAction } + +@Pipe({ + name: 'groupActions', +}) +export class GroupActionsPipe implements PipeTransform { + transform( + actions: PackageDataEntry['actions'], + ): Array>> | null { + if (!actions) return null + const noGroup = 'noGroup' + const grouped = Object.entries(actions).reduce< + Record[]> + >((groups, [id, action]) => { + const actionWithId = { id, ...action } + const groupKey = action.group || noGroup + if (!groups[groupKey]) { + groups[groupKey] = [actionWithId] + } else { + groups[groupKey].push(actionWithId) + } + return groups + }, {}) + + return Object.values(grouped).map(group => + group.sort((a, b) => a.name.localeCompare(b.name)), + ) + } +} diff --git a/frontend/projects/ui/src/app/pages/apps-routes/app-interfaces/app-interfaces-item.component.html b/frontend/projects/ui/src/app/pages/apps-routes/app-interfaces/app-interfaces-item.component.html index 932c1fd0a..0e5942b6a 100644 --- a/frontend/projects/ui/src/app/pages/apps-routes/app-interfaces/app-interfaces-item.component.html +++ b/frontend/projects/ui/src/app/pages/apps-routes/app-interfaces/app-interfaces-item.component.html @@ -1,72 +1,34 @@ - + -

{{ interface.def.name }}

-

{{ interface.def.description }}

+

{{ addressInfo.name }}

+

{{ addressInfo.description }}

-
- - +
+ -

Tor Address

-

{{ tor }}

+

{{ address | addressType }}

+

{{ address }}

- + - + - +
- - - -

Tor Address

-

Service does not use a Tor Address

-
-
- - - - -

LAN Address

-

{{ lan }}

-
- - - - - - - - - - - -
- - - -

LAN Address

-

N/A

-
-
diff --git a/frontend/projects/ui/src/app/pages/apps-routes/app-interfaces/app-interfaces.module.ts b/frontend/projects/ui/src/app/pages/apps-routes/app-interfaces/app-interfaces.module.ts index a9a2ddebc..0a8781a1a 100644 --- a/frontend/projects/ui/src/app/pages/apps-routes/app-interfaces/app-interfaces.module.ts +++ b/frontend/projects/ui/src/app/pages/apps-routes/app-interfaces/app-interfaces.module.ts @@ -3,10 +3,10 @@ import { CommonModule } from '@angular/common' import { Routes, RouterModule } from '@angular/router' import { IonicModule } from '@ionic/angular' import { SharedPipesModule } from '@start9labs/shared' - import { AppInterfacesItemComponent, AppInterfacesPage, + AddressTypePipe, } from './app-interfaces.page' const routes: Routes = [ @@ -23,6 +23,10 @@ const routes: Routes = [ RouterModule.forChild(routes), SharedPipesModule, ], - declarations: [AppInterfacesPage, AppInterfacesItemComponent], + declarations: [ + AppInterfacesPage, + AppInterfacesItemComponent, + AddressTypePipe, + ], }) export class AppInterfacesPageModule {} diff --git a/frontend/projects/ui/src/app/pages/apps-routes/app-interfaces/app-interfaces.page.html b/frontend/projects/ui/src/app/pages/apps-routes/app-interfaces/app-interfaces.page.html index 16c6a5bd6..3178172a8 100644 --- a/frontend/projects/ui/src/app/pages/apps-routes/app-interfaces/app-interfaces.page.html +++ b/frontend/projects/ui/src/app/pages/apps-routes/app-interfaces/app-interfaces.page.html @@ -9,18 +9,11 @@ - - - User Interface - - - - - - Machine Interfaces -
- -
-
+
+ +
diff --git a/frontend/projects/ui/src/app/pages/apps-routes/app-interfaces/app-interfaces.page.ts b/frontend/projects/ui/src/app/pages/apps-routes/app-interfaces/app-interfaces.page.ts index 20c480496..c3f46d466 100644 --- a/frontend/projects/ui/src/app/pages/apps-routes/app-interfaces/app-interfaces.page.ts +++ b/frontend/projects/ui/src/app/pages/apps-routes/app-interfaces/app-interfaces.page.ts @@ -1,80 +1,38 @@ -import { Component, Input } from '@angular/core' +import { + ChangeDetectionStrategy, + Component, + Input, + Pipe, + PipeTransform, +} from '@angular/core' import { ActivatedRoute } from '@angular/router' import { ModalController, ToastController } from '@ionic/angular' import { getPkgId, copyToClipboard } from '@start9labs/shared' -import { getUiInterfaceKey } from 'src/app/services/config.service' -import { - DataModel, - InstalledPackageDataEntry, - InterfaceDef, -} from 'src/app/services/patch-db/data-model' +import { AddressInfo, DataModel } from 'src/app/services/patch-db/data-model' import { PatchDB } from 'patch-db-client' import { QRComponent } from 'src/app/components/qr/qr.component' -import { getPackage } from '../../../util/get-package-data' - -interface LocalInterface { - def: InterfaceDef - addresses: InstalledPackageDataEntry['interface-addresses'][string] -} +import { map } from 'rxjs' @Component({ selector: 'app-interfaces', templateUrl: './app-interfaces.page.html', styleUrls: ['./app-interfaces.page.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class AppInterfacesPage { - ui?: LocalInterface - other: LocalInterface[] = [] readonly pkgId = getPkgId(this.route) + readonly addressInfo$ = this.patch + .watch$('package-data', this.pkgId, 'installed', 'address-info') + .pipe( + map(addressInfo => + Object.values(addressInfo).sort((a, b) => a.name.localeCompare(b.name)), + ), + ) constructor( private readonly route: ActivatedRoute, private readonly patch: PatchDB, ) {} - - async ngOnInit() { - const pkg = await getPackage(this.patch, this.pkgId) - if (!pkg) return - - const interfaces = pkg.manifest.interfaces - const uiKey = getUiInterfaceKey(interfaces) - - if (!pkg.installed) return - - const addressesMap = pkg.installed['interface-addresses'] - - if (uiKey) { - const uiAddresses = addressesMap[uiKey] - this.ui = { - def: interfaces[uiKey], - addresses: { - 'lan-address': uiAddresses['lan-address'] - ? 'https://' + uiAddresses['lan-address'] - : '', - 'tor-address': uiAddresses['tor-address'] - ? 'http://' + uiAddresses['tor-address'] - : '', - }, - } - } - - this.other = Object.keys(interfaces) - .filter(key => key !== uiKey) - .map(key => { - const addresses = addressesMap[key] - return { - def: interfaces[key], - addresses: { - 'lan-address': addresses['lan-address'] - ? 'https://' + addresses['lan-address'] - : '', - 'tor-address': addresses['tor-address'] - ? 'http://' + addresses['tor-address'] - : '', - }, - } - }) - } } @Component({ @@ -84,7 +42,7 @@ export class AppInterfacesPage { }) export class AppInterfacesItemComponent { @Input() - interface!: LocalInterface + addressInfo!: AddressInfo constructor( private readonly toastCtrl: ToastController, @@ -122,3 +80,31 @@ export class AppInterfacesItemComponent { await toast.present() } } + +@Pipe({ + name: 'addressType', +}) +export class AddressTypePipe implements PipeTransform { + transform(address: string): string { + if (isValidIpv4(address)) return 'IPv4' + if (isValidIpv6(address)) return 'IPv6' + + const hostname = new URL(address).hostname + if (hostname.endsWith('.onion')) return 'Tor' + if (hostname.endsWith('.local')) return 'Local' + + return 'Custom' + } +} + +function isValidIpv4(address: string): boolean { + const regexExp = + /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/ + return regexExp.test(address) +} + +function isValidIpv6(address: string): boolean { + const regexExp = + /(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))/gi + return regexExp.test(address) +} diff --git a/frontend/projects/ui/src/app/pages/apps-routes/app-list/app-list-pkg/app-list-pkg.component.html b/frontend/projects/ui/src/app/pages/apps-routes/app-list/app-list-pkg/app-list-pkg.component.html index 037f7cc07..ba122782d 100644 --- a/frontend/projects/ui/src/app/pages/apps-routes/app-list/app-list-pkg/app-list-pkg.component.html +++ b/frontend/projects/ui/src/app/pages/apps-routes/app-list/app-list-pkg/app-list-pkg.component.html @@ -7,7 +7,7 @@ > - +

{{ manifest.title }}

@@ -17,17 +17,18 @@ [installProgress]="pkg.entry['install-progress']" weight="bold" size="small" - [sigtermTimeout]="manifest.main['sigterm-timeout']" >
- - - + + + + +
diff --git a/frontend/projects/ui/src/app/pages/apps-routes/app-list/app-list-pkg/app-list-pkg.component.ts b/frontend/projects/ui/src/app/pages/apps-routes/app-list/app-list-pkg/app-list-pkg.component.ts index 3302e182e..945283386 100644 --- a/frontend/projects/ui/src/app/pages/apps-routes/app-list/app-list-pkg/app-list-pkg.component.ts +++ b/frontend/projects/ui/src/app/pages/apps-routes/app-list/app-list-pkg/app-list-pkg.component.ts @@ -1,5 +1,8 @@ import { ChangeDetectionStrategy, Component, Input } from '@angular/core' -import { PackageMainStatus } from 'src/app/services/patch-db/data-model' +import { + InstalledPackageInfo, + PackageMainStatus, +} from 'src/app/services/patch-db/data-model' import { PkgInfo } from 'src/app/util/get-package-info' import { UiLauncherService } from 'src/app/services/ui-launcher.service' @@ -20,9 +23,9 @@ export class AppListPkgComponent { ) } - launchUi(e: Event): void { + launchUi(e: Event, addressInfo: InstalledPackageInfo['address-info']): void { e.stopPropagation() e.preventDefault() - this.launcherService.launch(this.pkg.entry) + this.launcherService.launch(addressInfo) } } diff --git a/frontend/projects/ui/src/app/pages/apps-routes/app-list/app-list.module.ts b/frontend/projects/ui/src/app/pages/apps-routes/app-list/app-list.module.ts index 89998c9bf..e809cb61e 100644 --- a/frontend/projects/ui/src/app/pages/apps-routes/app-list/app-list.module.ts +++ b/frontend/projects/ui/src/app/pages/apps-routes/app-list/app-list.module.ts @@ -11,7 +11,6 @@ import { } from '@start9labs/shared' import { BadgeMenuComponentModule } from 'src/app/components/badge-menu-button/badge-menu.component.module' import { StatusComponentModule } from 'src/app/components/status/status.component.module' -import { LaunchablePipeModule } from 'src/app/pipes/launchable/launchable.module' import { UiPipeModule } from 'src/app/pipes/ui/ui.module' import { AppListIconComponent } from './app-list-icon/app-list-icon.component' import { AppListPkgComponent } from './app-list-pkg/app-list-pkg.component' @@ -31,7 +30,6 @@ const routes: Routes = [ StatusComponentModule, EmverPipesModule, TextSpinnerComponentModule, - LaunchablePipeModule, UiPipeModule, IonicModule, RouterModule.forChild(routes), diff --git a/frontend/projects/ui/src/app/pages/apps-routes/app-show/app-show.module.ts b/frontend/projects/ui/src/app/pages/apps-routes/app-show/app-show.module.ts index 8cd19f502..6768eeb80 100644 --- a/frontend/projects/ui/src/app/pages/apps-routes/app-show/app-show.module.ts +++ b/frontend/projects/ui/src/app/pages/apps-routes/app-show/app-show.module.ts @@ -3,10 +3,13 @@ import { CommonModule } from '@angular/common' import { Routes, RouterModule } from '@angular/router' import { IonicModule } from '@ionic/angular' import { AppShowPage } from './app-show.page' -import { EmverPipesModule, ResponsiveColModule, SharedPipesModule } from '@start9labs/shared' +import { + EmverPipesModule, + ResponsiveColModule, + SharedPipesModule, +} from '@start9labs/shared' import { StatusComponentModule } from 'src/app/components/status/status.component.module' import { AppConfigPageModule } from 'src/app/modals/app-config/app-config.module' -import { LaunchablePipeModule } from 'src/app/pipes/launchable/launchable.module' import { UiPipeModule } from 'src/app/pipes/ui/ui.module' import { AppShowHeaderComponent } from './components/app-show-header/app-show-header.component' import { AppShowProgressComponent } from './components/app-show-progress/app-show-progress.component' @@ -51,7 +54,6 @@ const routes: Routes = [ RouterModule.forChild(routes), AppConfigPageModule, EmverPipesModule, - LaunchablePipeModule, UiPipeModule, ResponsiveColModule, SharedPipesModule, diff --git a/frontend/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-additional/app-show-additional.component.html b/frontend/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-additional/app-show-additional.component.html index 81fe8fb84..acea37992 100644 --- a/frontend/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-additional/app-show-additional.component.html +++ b/frontend/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-additional/app-show-additional.component.html @@ -44,7 +44,7 @@ detail="false" > -

Marketing Site

+

Website

{{ manifest['marketing-site'] || 'Not provided' }}

diff --git a/frontend/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-additional/app-show-additional.component.ts b/frontend/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-additional/app-show-additional.component.ts index 501898428..3b4e84787 100644 --- a/frontend/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-additional/app-show-additional.component.ts +++ b/frontend/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-additional/app-show-additional.component.ts @@ -35,10 +35,16 @@ export class AppShowAdditionalComponent { } async presentModalLicense() { + const { id, version } = this.pkg.manifest + const modal = await this.modalCtrl.create({ componentProps: { title: 'License', - content: from(this.api.getStatic(this.pkg['static-files']['license'])), + content: from( + this.api.getStatic( + `/public/package-data/${id}/${version}/LICENSE.md`, + ), + ), }, component: MarkdownComponent, }) diff --git a/frontend/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-header/app-show-header.component.html b/frontend/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-header/app-show-header.component.html index 17dcd1be1..ff3602986 100644 --- a/frontend/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-header/app-show-header.component.html +++ b/frontend/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-header/app-show-header.component.html @@ -4,7 +4,7 @@
- +

@@ -31,7 +30,7 @@ - Launch UI + Open UI diff --git a/frontend/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-status/app-show-status.component.ts b/frontend/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-status/app-show-status.component.ts index 59812e243..742303fcc 100644 --- a/frontend/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-status/app-show-status.component.ts +++ b/frontend/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-status/app-show-status.component.ts @@ -6,11 +6,11 @@ import { PrimaryStatus, } from 'src/app/services/pkg-status-rendering.service' import { + AddressInfo, DataModel, - InterfaceDef, + InstalledPackageInfo, PackageDataEntry, PackageState, - Status, } from 'src/app/services/patch-db/data-model' import { ErrorToastService } from '@start9labs/shared' import { AlertController, LoadingController } from '@ionic/angular' @@ -56,12 +56,12 @@ export class AppShowStatusComponent { return this.pkg.manifest.id } - get interfaces(): Record { - return this.pkg.manifest.interfaces || {} + get addressInfo(): Record { + return this.pkg.installed!['address-info'] } - get pkgStatus(): Status | null { - return this.pkg.installed?.status || null + get isConfigured(): boolean { + return this.pkg.installed!.status.configured } get isInstalled(): boolean { @@ -76,8 +76,8 @@ export class AppShowStatusComponent { return this.status.primary === PrimaryStatus.Stopped } - launchUi(): void { - this.launcherService.launch(this.pkg) + launchUi(addressInfo: InstalledPackageInfo['address-info']): void { + this.launcherService.launch(addressInfo) } async presentModalConfig(): Promise { diff --git a/frontend/projects/ui/src/app/pages/apps-routes/app-show/pipes/to-buttons.pipe.ts b/frontend/projects/ui/src/app/pages/apps-routes/app-show/pipes/to-buttons.pipe.ts index eeb00b435..955840564 100644 --- a/frontend/projects/ui/src/app/pages/apps-routes/app-show/pipes/to-buttons.pipe.ts +++ b/frontend/projects/ui/src/app/pages/apps-routes/app-show/pipes/to-buttons.pipe.ts @@ -98,15 +98,19 @@ export class ToButtonsPipe implements PipeTransform { } private async presentModalInstructions(pkg: PackageDataEntry) { + const { id, version } = pkg.manifest + this.apiService - .setDbValue(['ack-instructions', pkg.manifest.id], true) + .setDbValue(['ack-instructions', id], true) .catch(e => console.error('Failed to mark instructions as seen', e)) const modal = await this.modalCtrl.create({ componentProps: { title: 'Instructions', content: from( - this.apiService.getStatic(pkg['static-files']['instructions']), + this.apiService.getStatic( + `/public/package-data/${id}/${version}/INSTRUCTIONS.md`, + ), ), }, component: MarkdownComponent, diff --git a/frontend/projects/ui/src/app/pages/apps-routes/app-show/pipes/to-dependencies.pipe.ts b/frontend/projects/ui/src/app/pages/apps-routes/app-show/pipes/to-dependencies.pipe.ts index bd81ef183..6cde7d929 100644 --- a/frontend/projects/ui/src/app/pages/apps-routes/app-show/pipes/to-dependencies.pipe.ts +++ b/frontend/projects/ui/src/app/pages/apps-routes/app-show/pipes/to-dependencies.pipe.ts @@ -3,11 +3,11 @@ import { NavigationExtras } from '@angular/router' import { NavController } from '@ionic/angular' import { DependencyErrorType, - InstalledPackageDataEntry, PackageDataEntry, } from 'src/app/services/patch-db/data-model' import { DependentInfo } from 'src/app/types/dependent-info' import { ModalService } from 'src/app/services/modal.service' +import { Manifest } from '@start9labs/marketplace' export interface DependencyInfo { id: string @@ -32,40 +32,32 @@ export class ToDependenciesPipe implements PipeTransform { if (!pkg.installed) return [] return Object.keys(pkg.installed['current-dependencies']) - .filter(id => !!pkg.manifest.dependencies[id]) - .map(id => this.setDepValues(pkg.installed!, id)) + .filter(depId => !!pkg.manifest.dependencies[depId]) + .map(depId => this.setDepValues(pkg, depId)) } - private setDepValues( - pkg: InstalledPackageDataEntry, - id: string, - ): DependencyInfo { + private setDepValues(pkg: PackageDataEntry, depId: string): DependencyInfo { let errorText = '' let actionText = 'View' let action: () => any = () => - this.navCtrl.navigateForward(`/services/${id}`) + this.navCtrl.navigateForward(`/services/${depId}`) - const error = pkg.status['dependency-errors'][id] + const error = pkg.installed!.status['dependency-errors'][depId] if (error) { // health checks failed - if ( - [ - DependencyErrorType.InterfaceHealthChecksFailed, - DependencyErrorType.HealthChecksFailed, - ].includes(error.type) - ) { + if (error.type === DependencyErrorType.HealthChecksFailed) { errorText = 'Health check failed' // not installed } else if (error.type === DependencyErrorType.NotInstalled) { errorText = 'Not installed' actionText = 'Install' - action = () => this.fixDep(pkg, 'install', id) + action = () => this.fixDep(pkg, 'install', depId) // incorrect version } else if (error.type === DependencyErrorType.IncorrectVersion) { errorText = 'Incorrect version' actionText = 'Update' - action = () => this.fixDep(pkg, 'update', id) + action = () => this.fixDep(pkg, 'update', depId) // not running } else if (error.type === DependencyErrorType.NotRunning) { errorText = 'Not running' @@ -74,19 +66,19 @@ export class ToDependenciesPipe implements PipeTransform { } else if (error.type === DependencyErrorType.ConfigUnsatisfied) { errorText = 'Config not satisfied' actionText = 'Auto config' - action = () => this.fixDep(pkg, 'configure', id) + action = () => this.fixDep(pkg, 'configure', depId) } else if (error.type === DependencyErrorType.Transitive) { errorText = 'Dependency has a dependency issue' } errorText = `${errorText}. ${pkg.manifest.title} will not work as expected.` } - const depInfo = pkg['dependency-info'][id] + const depInfo = pkg.installed!['dependency-info'][depId] return { - id, - version: pkg.manifest.dependencies[id].version, - title: depInfo?.manifest?.title || id, + id: depId, + version: pkg.manifest.dependencies[depId].version, + title: depInfo?.title || depId, icon: depInfo?.icon || '', errorText, actionText, @@ -95,28 +87,25 @@ export class ToDependenciesPipe implements PipeTransform { } async fixDep( - pkg: InstalledPackageDataEntry, + pkg: PackageDataEntry, action: 'install' | 'update' | 'configure', - id: string, + depId: string, ): Promise { switch (action) { case 'install': case 'update': - return this.installDep(pkg, id) + return this.installDep(pkg.manifest, depId) case 'configure': - return this.configureDep(pkg, id) + return this.configureDep(pkg.manifest, depId) } } - private async installDep( - pkg: InstalledPackageDataEntry, - depId: string, - ): Promise { - const version = pkg.manifest.dependencies[depId].version + private async installDep(manifest: Manifest, depId: string): Promise { + const version = manifest.dependencies[depId].version const dependentInfo: DependentInfo = { - id: pkg.manifest.id, - title: pkg.manifest.title, + id: manifest.id, + title: manifest.title, version, } const navigationExtras: NavigationExtras = { @@ -130,12 +119,12 @@ export class ToDependenciesPipe implements PipeTransform { } private async configureDep( - pkg: InstalledPackageDataEntry, + manifest: Manifest, dependencyId: string, ): Promise { const dependentInfo: DependentInfo = { - id: pkg.manifest.id, - title: pkg.manifest.title, + id: manifest.id, + title: manifest.title, } await this.modalService.presentModalConfig({ diff --git a/frontend/projects/ui/src/app/pages/developer-routes/developer-menu/form-info.ts b/frontend/projects/ui/src/app/pages/developer-routes/developer-menu/form-info.ts index 157e3ef1e..fca73a5b3 100644 --- a/frontend/projects/ui/src/app/pages/developer-routes/developer-menu/form-info.ts +++ b/frontend/projects/ui/src/app/pages/developer-routes/developer-menu/form-info.ts @@ -180,7 +180,7 @@ export function getBasicInfoSpec(devData: DevProjectData): InputSpec { }, 'marketing-site': { type: 'string', - name: 'Marketing Site', + name: 'Website', description: 'URL to the marketing site / channel for the project', placeholder: 'e.g. start9.com', pattern: null, diff --git a/frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-show/components/marketplace-show-components.module.ts b/frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-show/components/marketplace-show-components.module.ts new file mode 100644 index 000000000..2c47125b9 --- /dev/null +++ b/frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-show/components/marketplace-show-components.module.ts @@ -0,0 +1,46 @@ +import { NgModule } from '@angular/core' +import { CommonModule } from '@angular/common' +import { IonicModule } from '@ionic/angular' +import { RouterModule } from '@angular/router' +import { + SharedPipesModule, + EmverPipesModule, + MarkdownPipeModule, + TextSpinnerComponentModule, +} from '@start9labs/shared' +import { + PackageModule, + AboutModule, + AdditionalModule, + DependenciesModule, +} from '@start9labs/marketplace' +import { MarketplaceShowHeaderComponent } from './marketplace-show-header/marketplace-show-header.component' +import { MarketplaceShowDependentComponent } from './marketplace-show-dependent/marketplace-show-dependent.component' +import { MarketplaceShowControlsComponent } from './marketplace-show-controls/marketplace-show-controls.component' + +@NgModule({ + declarations: [ + MarketplaceShowHeaderComponent, + MarketplaceShowControlsComponent, + MarketplaceShowDependentComponent, + ], + imports: [ + CommonModule, + IonicModule, + RouterModule, + TextSpinnerComponentModule, + SharedPipesModule, + EmverPipesModule, + MarkdownPipeModule, + PackageModule, + AboutModule, + DependenciesModule, + AdditionalModule, + ], + exports: [ + MarketplaceShowHeaderComponent, + MarketplaceShowControlsComponent, + MarketplaceShowDependentComponent, + ], +}) +export class MarketplaceShowComponentsModule {} diff --git a/frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show-controls/marketplace-show-controls.component.html b/frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-show/components/marketplace-show-controls/marketplace-show-controls.component.html similarity index 81% rename from frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show-controls/marketplace-show-controls.component.html rename to frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-show/components/marketplace-show-controls/marketplace-show-controls.component.html index b150d2d4f..64bb3db0b 100644 --- a/frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show-controls/marketplace-show-controls.component.html +++ b/frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-show/components/marketplace-show-controls/marketplace-show-controls.component.html @@ -10,7 +10,7 @@
{{ title }} version {{ version | displayEmver }} is compatible. {{ title }} version {{ version | displayEmver }} is NOT compatible. diff --git a/frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show-dependent/marketplace-show-dependent.component.scss b/frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-show/components/marketplace-show-dependent/marketplace-show-dependent.component.scss similarity index 100% rename from frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show-dependent/marketplace-show-dependent.component.scss rename to frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-show/components/marketplace-show-dependent/marketplace-show-dependent.component.scss diff --git a/frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show-dependent/marketplace-show-dependent.component.ts b/frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-show/components/marketplace-show-dependent/marketplace-show-dependent.component.ts similarity index 100% rename from frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show-dependent/marketplace-show-dependent.component.ts rename to frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-show/components/marketplace-show-dependent/marketplace-show-dependent.component.ts diff --git a/frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show-header/marketplace-show-header.component.html b/frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-show/components/marketplace-show-header/marketplace-show-header.component.html similarity index 100% rename from frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show-header/marketplace-show-header.component.html rename to frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-show/components/marketplace-show-header/marketplace-show-header.component.html diff --git a/frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show-header/marketplace-show-header.component.ts b/frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-show/components/marketplace-show-header/marketplace-show-header.component.ts similarity index 100% rename from frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show-header/marketplace-show-header.component.ts rename to frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-show/components/marketplace-show-header/marketplace-show-header.component.ts diff --git a/frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show.module.ts b/frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show.module.ts index 3cef27e61..1905ffba8 100644 --- a/frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show.module.ts +++ b/frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show.module.ts @@ -16,9 +16,7 @@ import { } from '@start9labs/marketplace' import { MarketplaceStatusModule } from '../marketplace-status/marketplace-status.module' import { MarketplaceShowPage } from './marketplace-show.page' -import { MarketplaceShowHeaderComponent } from './marketplace-show-header/marketplace-show-header.component' -import { MarketplaceShowDependentComponent } from './marketplace-show-dependent/marketplace-show-dependent.component' -import { MarketplaceShowControlsComponent } from './marketplace-show-controls/marketplace-show-controls.component' +import { MarketplaceShowComponentsModule } from './components/marketplace-show-components.module' const routes: Routes = [ { @@ -41,18 +39,8 @@ const routes: Routes = [ AboutModule, DependenciesModule, AdditionalModule, + MarketplaceShowComponentsModule, ], - declarations: [ - MarketplaceShowPage, - MarketplaceShowHeaderComponent, - MarketplaceShowControlsComponent, - MarketplaceShowDependentComponent, - ], - exports: [ - MarketplaceShowPage, - MarketplaceShowHeaderComponent, - MarketplaceShowControlsComponent, - MarketplaceShowDependentComponent, - ], + declarations: [MarketplaceShowPage], }) export class MarketplaceShowPageModule {} diff --git a/frontend/projects/ui/src/app/pages/server-routes/server-backup/backing-up/backing-up.component.html b/frontend/projects/ui/src/app/pages/server-routes/server-backup/backing-up/backing-up.component.html index 922baf8f2..b3ffd149b 100644 --- a/frontend/projects/ui/src/app/pages/server-routes/server-backup/backing-up/backing-up.component.html +++ b/frontend/projects/ui/src/app/pages/server-routes/server-backup/backing-up/backing-up.component.html @@ -15,9 +15,9 @@ - + - {{ pkg.value.manifest.title }} + {{ pkg.value.manifest.title }} - - -
- -

Upload .s9pk package file

-

- Tip: switch to LAN for faster uploads. -

- - - - + + +
+

+ + Invalid package file +

+ Try again
- - -
-

- - - {{ uploadState?.message }} -

-
-
-
- - - -
-
- -

{{ toUpload.manifest.title }}

-

{{ toUpload.manifest.version | displayEmver }}

-
-
-
- - Try again - - - - Upload & Install + + + + +
+
+ + - +
+ + + + + + + +
+ + +
+ +

Upload .s9pk package file

+

+ + Tip: switch to LAN for faster uploads. + +

+ + + + +
+
diff --git a/frontend/projects/ui/src/app/pages/server-routes/sideload/sideload.page.scss b/frontend/projects/ui/src/app/pages/server-routes/sideload/sideload.page.scss index 5f76ed073..3155e4585 100644 --- a/frontend/projects/ui/src/app/pages/server-routes/sideload/sideload.page.scss +++ b/frontend/projects/ui/src/app/pages/server-routes/sideload/sideload.page.scss @@ -2,11 +2,6 @@ vertical-align: initial; } -.area { - flex-direction: column; - justify-content: center; -} - .drop-area { display: flex; background-color: rgba(24, 24, 24, 0.5); @@ -18,7 +13,7 @@ border-color: var(--ion-color-dark); color: var(--ion-color-dark); border-radius: 5px; - margin: 60px; + margin: 20px; padding: 30px; min-height: 600px; @@ -47,45 +42,3 @@ color: var(--ion-color-dark); } } - -.box { - display: flex; - justify-content: space-evenly -} - -.card { - background: radial-gradient(var(--ion-color-step-100), transparent); - min-width: 200px; - max-width: 200px; - height: auto; - display: flex; - flex-direction: column; - justify-content: center; - margin: 20px 20px 40px 20px; - border-style: solid; - border-color: var(--ion-color-step-100); - border-radius: 7px; - padding: 4px 8px 8px 8px; - - .row { - width: auto; - - &_end { - align-self: end; - - ion-button { - width: 80%; - } - } - } - - img { - width: 60px; - text-align: center; - } - - h2, - p { - text-align: center; - } -} diff --git a/frontend/projects/ui/src/app/pages/server-routes/sideload/sideload.page.ts b/frontend/projects/ui/src/app/pages/server-routes/sideload/sideload.page.ts index 47d113eca..4674b7bb2 100644 --- a/frontend/projects/ui/src/app/pages/server-routes/sideload/sideload.page.ts +++ b/frontend/projects/ui/src/app/pages/server-routes/sideload/sideload.page.ts @@ -1,10 +1,10 @@ import { Component } from '@angular/core' import { isPlatform, LoadingController, NavController } from '@ionic/angular' import { ApiService } from 'src/app/services/api/embassy-api.service' -import { Manifest } from 'src/app/services/patch-db/data-model' +import { Manifest, MarketplacePkg } from '@start9labs/marketplace' import { ConfigService } from 'src/app/services/config.service' -import cbor from 'cbor' import { ErrorToastService } from '@start9labs/shared' +import cbor from 'cbor' interface Positions { [key: string]: [bigint, bigint] // [position, length] @@ -20,20 +20,12 @@ const VERSION = new Uint8Array([1]) }) export class SideloadPage { isMobile = isPlatform(window, 'ios') || isPlatform(window, 'android') - toUpload: { - manifest: Manifest | null - icon: string | null - file: File | null - } = { - manifest: null, - icon: null, - file: null, + pkgData?: { + pkg: MarketplacePkg + file: File } onTor = this.config.isTor() - uploadState?: { - invalid: boolean - message: string - } + invalid = false constructor( private readonly loadingCtrl: LoadingController, @@ -53,63 +45,60 @@ export class SideloadPage { this.setFile(files) } - async setFile(files?: File[]) { - if (!files || !files.length) return - const file = files[0] - if (!file) return - this.toUpload.file = file - this.uploadState = await this.validateS9pk(file) - } - - async validateS9pk(file: File) { - const magic = new Uint8Array(await blobToBuffer(file.slice(0, 2))) - const version = new Uint8Array(await blobToBuffer(file.slice(2, 3))) - if (compare(magic, MAGIC) && compare(version, VERSION)) { - await this.parseS9pk(file) - return { - invalid: false, - message: 'A valid package file has been detected!', - } - } else { - return { - invalid: true, - message: 'Invalid package file', - } - } - } - - clearToUpload() { - this.toUpload.file = null - this.toUpload.manifest = null - this.toUpload.icon = null + clear() { + this.pkgData = undefined + this.invalid = false } async handleUpload() { + if (!this.pkgData) return const loader = await this.loadingCtrl.create({ message: 'Uploading package', cssClass: 'loader', }) await loader.present() + + const { pkg, file } = this.pkgData + try { const guid = await this.api.sideloadPackage({ - manifest: this.toUpload.manifest!, - icon: this.toUpload.icon!, - size: this.toUpload.file!.size, + manifest: pkg.manifest, + icon: pkg.icon, + size: file.size, }) - this.api - .uploadPackage(guid, this.toUpload.file!) - .catch(e => console.error(e)) + this.api.uploadPackage(guid, file).catch(e => console.error(e)) this.navCtrl.navigateRoot('/services') } catch (e: any) { this.errToast.present(e) } finally { loader.dismiss() - this.clearToUpload() + this.clear() } } - async parseS9pk(file: File) { + private async setFile(files?: File[]) { + if (!files || !files.length) return + const file = files[0] + if (!file) return + + await this.validateS9pk(file) + } + + private async validateS9pk(file: File) { + const magic = new Uint8Array(await blobToBuffer(file.slice(0, 2))) + const version = new Uint8Array(await blobToBuffer(file.slice(2, 3))) + if (compare(magic, MAGIC) && compare(version, VERSION)) { + this.pkgData = { + pkg: await this.parseS9pk(file), + file, + } + } else { + this.invalid = true + } + } + + private async parseS9pk(file: File): Promise { const positions: Positions = {} // magic=2bytes, version=1bytes, pubkey=32bytes, signature=64bytes, toc_length=4bytes = 103byte is starting point let start = 103 @@ -119,30 +108,51 @@ export class SideloadPage { ).getUint32(0, false) await getPositions(start, end, file, positions, tocLength as any) - await this.getManifest(positions, file) - await this.getIcon(positions, file) + const manifest = await this.getAsset(positions, file, 'manifest') + const [icon] = await Promise.all([ + this.getIcon(positions, file, manifest), + // this.getAsset(positions, file, 'license'), + // this.getAsset(positions, file, 'instructions'), + ]) + + return { + manifest, + icon, + license: '', + instructions: '', + categories: [], + versions: [], + 'dependency-metadata': {}, + 'published-at': '', + } } - async getManifest(positions: Positions, file: Blob) { + private async getAsset( + positions: Positions, + file: Blob, + asset: 'manifest' | 'license' | 'instructions', + ): Promise { const data = await blobToBuffer( file.slice( - Number(positions['manifest'][0]), - Number(positions['manifest'][0]) + Number(positions['manifest'][1]), + Number(positions[asset][0]), + Number(positions[asset][0]) + Number(positions[asset][1]), ), ) - this.toUpload.manifest = await cbor.decode(data, true) + return cbor.decode(data, true) } - async getIcon(positions: Positions, file: Blob) { - const contentType = `image/${this.toUpload.manifest?.assets.icon - .split('.') - .pop()}` + private async getIcon( + positions: Positions, + file: Blob, + manifest: Manifest, + ): Promise { + const contentType = `image/${manifest.assets.icon.split('.').pop()}` const data = file.slice( Number(positions['icon'][0]), Number(positions['icon'][0]) + Number(positions['icon'][1]), contentType, ) - this.toUpload.icon = await blobToDataURL(data) + return blobToDataURL(data) } } diff --git a/frontend/projects/ui/src/app/pages/updates/updates.page.ts b/frontend/projects/ui/src/app/pages/updates/updates.page.ts index b199d31fc..109e2f824 100644 --- a/frontend/projects/ui/src/app/pages/updates/updates.page.ts +++ b/frontend/projects/ui/src/app/pages/updates/updates.page.ts @@ -9,7 +9,7 @@ import { MarketplaceService } from 'src/app/services/marketplace.service' import { AbstractMarketplaceService, Marketplace, - MarketplaceManifest, + Manifest, MarketplacePkg, StoreIdentity, } from '@start9labs/marketplace' @@ -64,7 +64,7 @@ export class UpdatesPage { } async tryUpdate( - manifest: MarketplaceManifest, + manifest: Manifest, url: string, local: PackageDataEntry, e: Event, @@ -83,7 +83,7 @@ export class UpdatesPage { } } - private async dryUpdate(manifest: MarketplaceManifest, url: string) { + private async dryUpdate(manifest: Manifest, url: string) { const loader = await this.loadingCtrl.create({ message: 'Checking dependent services...', }) @@ -181,7 +181,7 @@ export class FilterUpdatesPipe implements PipeTransform { ({ manifest }) => this.emver.compare( manifest.version, - local[manifest.id]?.installed?.manifest.version || '', + local[manifest.id]?.manifest.version || '', // @TODO this won't work, need old version ) === 1, ) } diff --git a/frontend/projects/ui/src/app/pipes/launchable/launchable.module.ts b/frontend/projects/ui/src/app/pipes/launchable/launchable.module.ts deleted file mode 100644 index 9d516789f..000000000 --- a/frontend/projects/ui/src/app/pipes/launchable/launchable.module.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { NgModule } from '@angular/core' -import { LaunchablePipe } from './launchable.pipe' - -@NgModule({ - declarations: [LaunchablePipe], - exports: [LaunchablePipe], -}) -export class LaunchablePipeModule {} diff --git a/frontend/projects/ui/src/app/pipes/launchable/launchable.pipe.ts b/frontend/projects/ui/src/app/pipes/launchable/launchable.pipe.ts deleted file mode 100644 index be1d5218d..000000000 --- a/frontend/projects/ui/src/app/pipes/launchable/launchable.pipe.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Pipe, PipeTransform } from '@angular/core' -import { - InterfaceDef, - PackageMainStatus, - PackageState, -} from 'src/app/services/patch-db/data-model' -import { ConfigService } from '../../services/config.service' - -@Pipe({ - name: 'isLaunchable', -}) -export class LaunchablePipe implements PipeTransform { - constructor(private configService: ConfigService) {} - - transform( - state: PackageState, - status: PackageMainStatus, - interfaces: Record, - ): boolean { - return this.configService.isLaunchable(state, status, interfaces) - } -} diff --git a/frontend/projects/ui/src/app/pipes/ui/ui.pipe.ts b/frontend/projects/ui/src/app/pipes/ui/ui.pipe.ts index 9d46bfd86..03718360e 100644 --- a/frontend/projects/ui/src/app/pipes/ui/ui.pipe.ts +++ b/frontend/projects/ui/src/app/pipes/ui/ui.pipe.ts @@ -1,12 +1,12 @@ import { Pipe, PipeTransform } from '@angular/core' -import { InterfaceDef } from '../../services/patch-db/data-model' +import { InstalledPackageInfo } from 'src/app/services/patch-db/data-model' import { hasUi } from '../../services/config.service' @Pipe({ name: 'hasUi', }) export class UiPipe implements PipeTransform { - transform(interfaces: Record): boolean { - return hasUi(interfaces) + transform(addressInfo: InstalledPackageInfo['address-info']): boolean { + return hasUi(addressInfo) } } diff --git a/frontend/projects/ui/src/app/services/api/api.fixures.ts b/frontend/projects/ui/src/app/services/api/api.fixures.ts index e27f10c4c..9f57229fd 100644 --- a/frontend/projects/ui/src/app/services/api/api.fixures.ts +++ b/frontend/projects/ui/src/app/services/api/api.fixures.ts @@ -1,7 +1,5 @@ import { DependencyErrorType, - DockerIoFormat, - Manifest, PackageDataEntry, PackageMainStatus, PackageState, @@ -9,7 +7,11 @@ import { } from 'src/app/services/patch-db/data-model' import { Metric, RR, NotificationLevel, ServerNotifications } from './api.types' import { BTC_ICON, LND_ICON, PROXY_ICON } from './api-icons' -import { DependencyMetadata, MarketplacePkg } from '@start9labs/marketplace' +import { + DependencyMetadata, + MarketplacePkg, + Manifest, +} from '@start9labs/marketplace' import { Log } from '@start9labs/shared' export module Mock { @@ -17,6 +19,7 @@ export module Mock { 'backup-progress': null, 'update-progress': null, updated: true, + 'shutting-down': false, } export const MarketplaceEos: RR.GetMarketplaceEosRes = { version: '0.3.4.3', @@ -50,16 +53,11 @@ export module Mock { short: 'A Bitcoin full node by Bitcoin Core.', long: 'Bitcoin is a decentralized consensus protocol and settlement network.', }, - replaces: ['banks', 'governments'], - 'release-notes': 'Taproot, Schnorr, and more.', assets: { icon: 'icon.png', - license: 'LICENSE.md', - instructions: 'INSTRUCTIONS.md', - docker_images: 'image.tar', - assets: './assets', - scripts: './scripts', }, + replaces: ['banks', 'governments'], + 'release-notes': 'Taproot, Schnorr, and more.', license: 'MIT', 'wrapper-repo': 'https://github.com/start9labs/bitcoind-wrapper', 'upstream-repo': 'https://github.com/bitcoin/bitcoin', @@ -74,299 +72,8 @@ export module Mock { start: 'Starting Bitcoin is good for your health.', stop: null, }, - main: { - type: 'docker', - image: '', - system: true, - entrypoint: '', - args: [], - mounts: {}, - 'io-format': DockerIoFormat.Yaml, - inject: false, - 'shm-size': '', - 'sigterm-timeout': '1ms', - }, - config: { - get: null, - set: null, - }, - 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': '', - 'sigterm-timeout': null, - }, - restore: { - type: 'docker', - image: '', - system: true, - entrypoint: '', - args: [], - mounts: {}, - 'io-format': DockerIoFormat.Yaml, - inject: false, - 'shm-size': '', - 'sigterm-timeout': null, - }, - }, - 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': '', - 'sigterm-timeout': null, - }, - 'input-spec': { - reason: { - type: 'string', - name: 'Re-sync Reason', - description: 'Your reason for re-syncing. Why are you doing this?', - placeholder: null, - nullable: false, - masked: false, - pattern: '^[a-zA-Z]+$', - 'pattern-description': 'Must contain only letters.', - textarea: false, - warning: null, - default: null, - }, - name: { - type: 'string', - name: 'Your Name', - description: 'Tell the class your name.', - nullable: true, - masked: false, - warning: 'You may loose all your money by providing your name.', - placeholder: null, - pattern: null, - 'pattern-description': null, - textarea: false, - default: null, - }, - notifications: { - name: 'Notification Preferences', - type: 'list', - subtype: 'enum', - description: 'how you want to be notified', - warning: null, - 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, - units: null, - placeholder: null, - warning: null, - }, - 'top-speed': { - type: 'number', - name: 'Top Speed', - description: 'The fastest you can possibly run.', - nullable: false, - range: '[-1000, 1000]', - integral: false, - units: 'm/s', - placeholder: null, - warning: null, - default: null, - }, - 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', - description: 'The person to contact in case of emergency.', - warning: null, - spec: { - name: { - type: 'string', - name: 'Name', - description: null, - nullable: false, - masked: false, - pattern: '^[a-zA-Z]+$', - 'pattern-description': 'Must contain only letters.', - placeholder: null, - textarea: false, - warning: null, - default: null, - }, - email: { - type: 'string', - name: 'Email', - description: null, - nullable: false, - masked: false, - placeholder: null, - pattern: null, - 'pattern-description': null, - textarea: false, - warning: null, - default: null, - }, - }, - }, - 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: { - placeholder: null, - pattern: '^[0-9]{1,3}([,.][0-9]{1,3})?$', - 'pattern-description': 'Must be a valid IP address', - masked: false, - }, - }, - bitcoinNode: { - type: 'union', - default: 'internal', - tag: { - id: 'type', - 'variant-names': { - internal: 'Internal', - external: 'External', - }, - name: 'Bitcoin Node Settings', - description: 'The node settings', - warning: 'Careful changing this', - }, - variants: { - internal: { - 'friendly-name': { - name: 'Friendly Name', - type: 'string', - description: 'the lan address', - nullable: true, - masked: false, - placeholder: null, - pattern: null, - 'pattern-description': null, - textarea: false, - warning: null, - default: null, - }, - }, - 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, - placeholder: null, - textarea: false, - warning: null, - }, - }, - }, - }, - }, - }, - }, dependencies: {}, + 'os-version': '0.4.0', } export const MockManifestLnd: Manifest = { @@ -377,15 +84,10 @@ export module Mock { short: 'A bolt spec compliant client.', long: 'More info about LND. More info about LND. More info about LND.', }, - 'release-notes': 'Dual funded channels!', assets: { icon: 'icon.png', - license: 'LICENSE.md', - instructions: 'INSTRUCTIONS.md', - docker_images: 'image.tar', - assets: './assets', - scripts: './scripts', }, + 'release-notes': 'Dual funded channels!', license: 'MIT', 'wrapper-repo': 'https://github.com/start9labs/lnd-wrapper', 'upstream-repo': 'https://github.com/lightningnetwork/lnd', @@ -400,104 +102,6 @@ export module Mock { 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': '', - 'sigterm-timeout': '10000µs', - }, - 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': '', - 'sigterm-timeout': null, - }, - restore: { - type: 'docker', - image: '', - system: true, - entrypoint: '', - args: [], - mounts: {}, - 'io-format': DockerIoFormat.Yaml, - inject: false, - 'shm-size': '', - 'sigterm-timeout': null, - }, - }, - 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': '', - 'sigterm-timeout': null, - }, - 'input-spec': null, - }, - }, dependencies: { bitcoind: { version: '=0.21.0', @@ -506,7 +110,6 @@ export module Mock { type: 'opt-out', how: 'You can use an external node from your server if you prefer.', }, - config: null, }, 'btc-rpc-proxy': { version: '>=0.2.2', @@ -516,9 +119,9 @@ export module Mock { type: 'opt-in', how: `To use Proxy's user management system, go to LND config and select Bitcoin Proxy under Bitcoin config.`, }, - config: null, }, }, + 'os-version': '0.4.0', } export const MockManifestBitcoinProxy: Manifest = { @@ -530,15 +133,10 @@ export module Mock { short: 'A super charger for your Bitcoin node.', long: 'More info about Bitcoin Proxy. More info about Bitcoin Proxy. More info about Bitcoin Proxy.', }, - 'release-notes': 'Even better support for Bitcoin and wallets!', assets: { icon: 'icon.png', - license: 'LICENSE.md', - instructions: 'INSTRUCTIONS.md', - docker_images: 'image.tar', - assets: './assets', - scripts: './scripts', }, + 'release-notes': 'Even better support for Bitcoin and wallets!', license: 'MIT', 'wrapper-repo': 'https://github.com/start9labs/btc-rpc-proxy-wrapper', 'upstream-repo': 'https://github.com/Kixunil/btc-rpc-proxy', @@ -552,66 +150,6 @@ export module Mock { start: null, stop: null, }, - main: { - type: 'docker', - image: '', - system: true, - entrypoint: '', - args: [''], - mounts: {}, - 'io-format': DockerIoFormat.Yaml, - inject: false, - 'shm-size': '', - 'sigterm-timeout': '1m', - }, - config: { get: {} as any, set: {} as any }, - volumes: {}, - 'min-os-version': '0.2.12', - interfaces: { - rpc: { - name: 'RPC interface', - description: 'Good for connecting to your node at a distance.', - ui: false, - 'tor-config': { - 'port-mapping': {}, - }, - 'lan-config': { - 44: { - ssl: true, - mapping: 33, - }, - }, - protocols: [], - }, - }, - backup: { - create: { - type: 'docker', - image: '', - system: true, - entrypoint: '', - args: [''], - mounts: {}, - 'io-format': DockerIoFormat.Yaml, - inject: false, - 'shm-size': '', - 'sigterm-timeout': null, - }, - restore: { - type: 'docker', - image: '', - system: true, - entrypoint: '', - args: [''], - mounts: {}, - 'io-format': DockerIoFormat.Yaml, - inject: false, - 'shm-size': '', - 'sigterm-timeout': null, - }, - }, - migrations: null, - actions: {}, dependencies: { bitcoind: { version: '>=0.20.0', @@ -619,34 +157,9 @@ export module Mock { requirement: { type: 'required', }, - config: { - check: { - type: 'docker', - image: 'alpine', - system: true, - entrypoint: 'true', - args: [], - mounts: {}, - 'io-format': DockerIoFormat.Cbor, - inject: false, - 'shm-size': '10m', - 'sigterm-timeout': null, - }, - 'auto-configure': { - type: 'docker', - image: 'alpine', - system: true, - entrypoint: 'cat', - args: [], - mounts: {}, - 'io-format': DockerIoFormat.Cbor, - inject: false, - 'shm-size': '10m', - 'sigterm-timeout': null, - }, - }, }, }, + 'os-version': '0.4.0', } export const BitcoinDep: DependencyMetadata = { @@ -1993,14 +1506,9 @@ export module Mock { export const bitcoind: PackageDataEntry = { state: PackageState.Installed, - 'static-files': { - license: '/public/package-data/bitcoind/0.20.0/LICENSE.md', - icon: '/assets/img/service-icons/bitcoind.svg', - instructions: '/public/package-data/bitcoind/0.20.0/INSTRUCTIONS.md', - }, manifest: MockManifestBitcoind, + icon: '/assets/img/service-icons/bitcoind.png', installed: { - manifest: MockManifestBitcoind, 'last-backup': null, status: { configured: true, @@ -2011,35 +1519,62 @@ export module Mock { }, 'dependency-errors': {}, }, - 'interface-addresses': { - ui: { - 'tor-address': 'bitcoind-ui-address.onion', - 'lan-address': 'bitcoind-ui-address.local', - }, + 'address-info': { rpc: { - 'tor-address': 'bitcoind-rpc-address.onion', - 'lan-address': 'bitcoind-rpc-address.local', + name: 'Bitcoin RPC', + description: `Bitcoin's RPC interface`, + addresses: [ + 'http://bitcoind-rpc-address.onion', + 'https://bitcoind-rpc-address.local', + 'https://192.168.1.1:8332', + ], + ui: true, }, p2p: { - 'tor-address': 'bitcoind-p2p-address.onion', - 'lan-address': 'bitcoind-p2p-address.local', + name: 'Bitcoin P2P', + description: `Bitcoin's P2P interface`, + addresses: [ + 'bitcoin://bitcoind-rpc-address.onion', + 'bitcoin://192.168.1.1:8333', + ], + ui: true, }, }, 'current-dependencies': {}, 'dependency-info': {}, 'marketplace-url': 'https://registry.start9.com/', 'developer-key': 'developer-key', + 'has-config': true, + }, + actions: { + resync: { + name: 'Resync Blockchain', + description: 'Use this to resync the Bitcoin blockchain from genesis', + warning: 'This will take a couple of days.', + disabled: null, + group: null, + 'input-spec': { + reason: { + type: 'string', + name: 'Re-sync Reason', + description: 'Your reason for re-syncing. Why are you doing this?', + placeholder: null, + nullable: false, + masked: false, + pattern: '^[a-zA-Z]+$', + 'pattern-description': 'Must contain only letters.', + textarea: false, + warning: null, + default: null, + }, + }, + }, }, - 'install-progress': undefined, } export const bitcoinProxy: PackageDataEntry = { state: PackageState.Installed, - 'static-files': { - license: '/public/package-data/btc-rpc-proxy/0.20.0/LICENSE.md', - icon: '/assets/img/service-icons/btc-rpc-proxy.png', - instructions: '/public/package-data/btc-rpc-proxy/0.20.0/INSTRUCTIONS.md', - }, + icon: '/assets/img/service-icons/btc-rpc-proxy.png', manifest: MockManifestBitcoinProxy, installed: { 'last-backup': null, @@ -2050,11 +1585,15 @@ export module Mock { }, 'dependency-errors': {}, }, - manifest: MockManifestBitcoinProxy, - 'interface-addresses': { + 'address-info': { rpc: { - 'tor-address': 'bitcoinproxy-rpc-address.onion', - 'lan-address': 'bitcoinproxy-rpc-address.local', + name: 'Proxy RPC addresses', + description: `Use these addresses to access Proxy's RPC interface`, + addresses: [ + 'http://bitcoinproxy-rpc-address.onion', + 'https://bitcoinproxy-rpc-address.local', + ], + ui: false, }, }, 'current-dependencies': { @@ -2064,23 +1603,20 @@ export module Mock { }, 'dependency-info': { bitcoind: { - manifest: Mock.MockManifestBitcoind, + title: 'Bitcoin Core', icon: 'assets/img/service-icons/bitcoind.svg', }, }, 'marketplace-url': 'https://registry.start9.com/', 'developer-key': 'developer-key', + 'has-config': true, }, - 'install-progress': undefined, + actions: {}, } export const lnd: PackageDataEntry = { state: PackageState.Installed, - 'static-files': { - license: '/public/package-data/lnd/0.11.0/LICENSE.md', - icon: '/assets/img/service-icons/lnd.png', - instructions: '/public/package-data/lnd/0.11.0/INSTRUCTIONS.md', - }, + icon: '/assets/img/service-icons/lnd.png', manifest: MockManifestLnd, installed: { 'last-backup': null, @@ -2095,15 +1631,26 @@ export module Mock { }, }, }, - manifest: MockManifestLnd, - 'interface-addresses': { - rpc: { - 'tor-address': 'lnd-rpc-address.onion', - 'lan-address': 'lnd-rpc-address.local', + 'address-info': { + ui: { + name: 'Web UI', + description: 'The browser web interface for LND', + addresses: [ + 'http://lnd-ui-address.onion', + 'https://lnd-ui-address.local', + 'https://192.168.1.1:3449', + ], + ui: true, }, grpc: { - 'tor-address': 'lnd-grpc-address.onion', - 'lan-address': 'lnd-grpc-address.local', + name: 'gRPC', + description: 'For connecting to LND gRPC interface', + addresses: [ + 'http://lnd-grpc-address.onion', + 'https://lnd-grpc-address.local', + 'https://192.168.1.1:3449', + ], + ui: true, }, }, 'current-dependencies': { @@ -2116,23 +1663,24 @@ export module Mock { }, 'dependency-info': { bitcoind: { - manifest: Mock.MockManifestBitcoind, + title: 'Bitcoin Core', icon: 'assets/img/service-icons/bitcoind.svg', }, 'btc-rpc-proxy': { - manifest: Mock.MockManifestBitcoinProxy, + title: 'Bitcoin Proxy', icon: 'assets/img/service-icons/btc-rpc-proxy.png', }, }, 'marketplace-url': 'https://registry.start9.com/', 'developer-key': 'developer-key', + 'has-config': true, }, - 'install-progress': undefined, + actions: {}, } export const LocalPkgs: { [key: string]: PackageDataEntry } = { - bitcoind: bitcoind, + bitcoind, 'btc-rpc-proxy': bitcoinProxy, - lnd: lnd, + lnd, } } diff --git a/frontend/projects/ui/src/app/services/api/api.types.ts b/frontend/projects/ui/src/app/services/api/api.types.ts index 1fb2e09fb..b2b519b3a 100644 --- a/frontend/projects/ui/src/app/services/api/api.types.ts +++ b/frontend/projects/ui/src/app/services/api/api.types.ts @@ -1,11 +1,10 @@ import { Dump, Revision } from 'patch-db-client' -import { MarketplacePkg, StoreInfo } from '@start9labs/marketplace' +import { MarketplacePkg, StoreInfo, Manifest } from '@start9labs/marketplace' import { PackagePropertiesVersioned } from 'src/app/util/properties.util' import { InputSpec } from 'start-sdk/types/config-types' import { DataModel, DependencyError, - Manifest, } from 'src/app/services/patch-db/data-model' import { StartOSDiskInfo, LogsRes, ServerLogsReq } from '@start9labs/shared' diff --git a/frontend/projects/ui/src/app/services/api/embassy-mock-api.service.ts b/frontend/projects/ui/src/app/services/api/embassy-mock-api.service.ts index 340bb83bf..5aa51c02e 100644 --- a/frontend/projects/ui/src/app/services/api/embassy-mock-api.service.ts +++ b/frontend/projects/ui/src/app/services/api/embassy-mock-api.service.ts @@ -15,7 +15,6 @@ import { PackageDataEntry, PackageMainStatus, PackageState, - ServerStatus, } from 'src/app/services/patch-db/data-model' import { CifsBackupTarget, RR } from './api.types' import { parsePropertiesPermissive } from 'src/app/util/properties.util' @@ -999,11 +998,11 @@ export class MockApiService extends ApiService { this.mockRevision(patch2) setTimeout(async () => { - const patch3: Operation[] = [ + const patch3: Operation[] = [ { op: PatchOp.REPLACE, - path: '/server-info/status', - value: ServerStatus.Updated, + path: '/server-info/status-info/updated', + value: true, }, { op: PatchOp.REMOVE, @@ -1011,16 +1010,6 @@ export class MockApiService extends ApiService { }, ] this.mockRevision(patch3) - // quickly revert server to "running" for continued testing - await pauseFor(100) - const patch4 = [ - { - op: PatchOp.REPLACE, - path: '/server-info/status', - value: ServerStatus.Running, - }, - ] - this.mockRevision(patch4) // set patch indicating update is complete await pauseFor(100) const patch6 = [ diff --git a/frontend/projects/ui/src/app/services/api/mock-patch.ts b/frontend/projects/ui/src/app/services/api/mock-patch.ts index 5f76884e4..31d648942 100644 --- a/frontend/projects/ui/src/app/services/api/mock-patch.ts +++ b/frontend/projects/ui/src/app/services/api/mock-patch.ts @@ -1,13 +1,10 @@ import { DataModel, DependencyErrorType, - DockerIoFormat, HealthResult, - Manifest, PackageMainStatus, PackageState, } from 'src/app/services/patch-db/data-model' -import { Mock } from './api.fixures' import { BUILT_IN_WIDGETS } from '../../pages/widgets/built-in/widgets' export const mockPatchData: DataModel = { @@ -68,6 +65,7 @@ export const mockPatchData: DataModel = { 'backup-progress': null, updated: false, 'update-progress': null, + 'shutting-down': false, }, hostname: 'random-words', pubkey: 'npub1sg6plzptd64u62a878hep2kev88swjh3tw00gjsfl8f237lmu63q0uf63m', @@ -78,11 +76,7 @@ export const mockPatchData: DataModel = { 'package-data': { bitcoind: { state: PackageState.Installed, - 'static-files': { - license: '/public/package-data/bitcoind/0.20.0/LICENSE.md', - icon: '/assets/img/service-icons/bitcoind.svg', - instructions: '/public/package-data/bitcoind/0.20.0/INSTRUCTIONS.md', - }, + icon: '/assets/img/service-icons/bitcoind.svg', manifest: { id: 'bitcoind', title: 'Bitcoin Core', @@ -92,15 +86,10 @@ export const mockPatchData: DataModel = { short: 'A Bitcoin full node by Bitcoin Core.', long: 'Bitcoin is a decentralized consensus protocol and settlement network.', }, - 'release-notes': 'Taproot, Schnorr, and more.', assets: { icon: 'icon.png', - license: 'LICENSE.md', - instructions: 'INSTRUCTIONS.md', - docker_images: 'image.tar', - assets: './assets', - scripts: './scripts', }, + 'release-notes': 'Taproot, Schnorr, and more.', license: 'MIT', 'wrapper-repo': 'https://github.com/start9labs/bitcoind-wrapper', 'upstream-repo': 'https://github.com/bitcoin/bitcoin', @@ -115,308 +104,10 @@ export const mockPatchData: DataModel = { start: 'Starting Bitcoin is good for your health.', stop: null, }, - main: { - type: 'docker', - image: '', - system: true, - entrypoint: '', - args: [], - mounts: {}, - 'io-format': DockerIoFormat.Yaml, - inject: false, - 'shm-size': '', - 'sigterm-timeout': '.49m', - }, - 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': '', - 'sigterm-timeout': null, - }, - restore: { - type: 'docker', - image: '', - system: true, - entrypoint: '', - args: [], - mounts: {}, - 'io-format': DockerIoFormat.Yaml, - inject: false, - 'shm-size': '', - 'sigterm-timeout': null, - }, - }, - 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': '', - 'sigterm-timeout': null, - }, - 'input-spec': { - reason: { - type: 'string', - name: 'Re-sync Reason', - description: - 'Your reason for re-syncing. Why are you doing this?', - nullable: false, - masked: false, - pattern: '^[a-zA-Z]+$', - 'pattern-description': 'Must contain only letters.', - placeholder: null, - textarea: false, - warning: null, - default: null, - }, - name: { - type: 'string', - name: 'Your Name', - description: 'Tell the class your name.', - nullable: true, - masked: false, - warning: 'You may loose all your money by providing your name.', - placeholder: null, - pattern: null, - 'pattern-description': null, - textarea: false, - default: null, - }, - notifications: { - name: 'Notification Preferences', - type: 'list', - subtype: 'enum', - description: 'how you want to be notified', - warning: null, - 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, - units: null, - placeholder: null, - warning: null, - }, - 'top-speed': { - type: 'number', - name: 'Top Speed', - description: 'The fastest you can possibly run.', - nullable: false, - range: '[-1000, 1000]', - integral: false, - units: 'm/s', - placeholder: null, - warning: null, - default: null, - }, - 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', - description: 'The person to contact in case of emergency.', - warning: null, - spec: { - name: { - type: 'string', - name: 'Name', - description: null, - nullable: false, - masked: false, - pattern: '^[a-zA-Z]+$', - 'pattern-description': 'Must contain only letters.', - placeholder: null, - textarea: false, - warning: null, - default: null, - }, - email: { - type: 'string', - name: 'Email', - description: null, - nullable: false, - masked: false, - placeholder: null, - pattern: null, - 'pattern-description': null, - textarea: false, - warning: null, - default: null, - }, - }, - }, - 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, - placeholder: null, - }, - }, - bitcoinNode: { - type: 'union', - default: 'internal', - tag: { - id: 'type', - 'variant-names': { - internal: 'Internal', - external: 'External', - }, - name: 'Bitcoin Node Settings', - description: 'The node settings', - warning: 'Careful changing this', - }, - variants: { - internal: { - 'friendly-name': { - name: 'Friendly Name', - type: 'string', - description: 'the lan address', - nullable: true, - masked: false, - placeholder: null, - pattern: null, - 'pattern-description': null, - textarea: false, - warning: null, - default: null, - }, - }, - 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, - placeholder: null, - textarea: false, - warning: null, - }, - }, - }, - }, - }, - }, - }, dependencies: {}, + 'os-version': '0.4.0', }, installed: { - manifest: { - ...Mock.MockManifestBitcoind, - version: '0.20.0', - }, 'last-backup': null, status: { configured: true, @@ -446,38 +137,43 @@ export const mockPatchData: DataModel = { 'unnecessary-health-check': { name: 'Totally Unnecessary', result: HealthResult.Disabled, + reason: 'You disabled this on purpose', }, }, }, 'dependency-errors': {}, }, - 'interface-addresses': { - ui: { - 'tor-address': 'bitcoind-ui-address.onion', - 'lan-address': 'bitcoind-ui-address.local', - }, + 'address-info': { rpc: { - 'tor-address': 'bitcoind-rpc-address.onion', - 'lan-address': 'bitcoind-rpc-address.local', + name: 'Bitcoin RPC', + description: `Bitcoin's RPC interface`, + addresses: [ + 'http://bitcoind-rpc-address.onion', + 'https://bitcoind-rpc-address.local', + 'https://192.168.1.1:8332', + ], + ui: true, }, p2p: { - 'tor-address': 'bitcoind-p2p-address.onion', - 'lan-address': 'bitcoind-p2p-address.local', + name: 'Bitcoin P2P', + description: `Bitcoin's P2P interface`, + addresses: [ + 'bitcoin://bitcoind-rpc-address.onion', + 'bitcoin://192.168.1.1:8333', + ], + ui: true, }, }, 'current-dependencies': {}, 'dependency-info': {}, 'marketplace-url': 'https://registry.start9.com/', 'developer-key': 'developer-key', + 'has-config': true, }, }, 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', - }, + icon: '/assets/img/service-icons/lnd.png', manifest: { id: 'lnd', title: 'Lightning Network Daemon', @@ -486,15 +182,10 @@ export const mockPatchData: DataModel = { short: 'A bolt spec compliant client.', long: 'More info about LND. More info about LND. More info about LND.', }, - 'release-notes': 'Dual funded channels!', assets: { icon: 'icon.png', - license: 'LICENSE.md', - instructions: 'INSTRUCTIONS.md', - docker_images: 'image.tar', - assets: './assets', - scripts: './scripts', }, + 'release-notes': 'Dual funded channels!', license: 'MIT', 'wrapper-repo': 'https://github.com/start9labs/lnd-wrapper', 'upstream-repo': 'https://github.com/lightningnetwork/lnd', @@ -509,104 +200,6 @@ export const mockPatchData: DataModel = { 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': '', - 'sigterm-timeout': '0.5s', - }, - 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': '', - 'sigterm-timeout': null, - }, - restore: { - type: 'docker', - image: '', - system: true, - entrypoint: '', - args: [], - mounts: {}, - 'io-format': DockerIoFormat.Yaml, - inject: false, - 'shm-size': '', - 'sigterm-timeout': null, - }, - }, - 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': '', - 'sigterm-timeout': null, - }, - 'input-spec': null, - }, - }, dependencies: { bitcoind: { version: '=0.21.0', @@ -615,7 +208,6 @@ export const mockPatchData: DataModel = { type: 'opt-out', how: 'You can use an external node from your server if you prefer.', }, - config: null, }, 'btc-rpc-proxy': { version: '>=0.2.2', @@ -625,9 +217,9 @@ export const mockPatchData: DataModel = { type: 'opt-in', how: `To use Proxy's user management system, go to LND config and select Bitcoin Proxy under Bitcoin config.`, }, - config: null, }, }, + 'os-version': '0.4.0', }, installed: { manifest: { @@ -647,14 +239,26 @@ export const mockPatchData: DataModel = { }, }, }, - 'interface-addresses': { - rpc: { - 'tor-address': 'lnd-rpc-address.onion', - 'lan-address': 'lnd-rpc-address.local', + 'address-info': { + ui: { + name: 'Web UI', + description: 'The browser web interface for LND', + addresses: [ + 'http://lnd-ui-address.onion', + 'https://lnd-ui-address.local', + 'https://192.168.1.1:3449', + ], + ui: true, }, grpc: { - 'tor-address': 'lnd-grpc-address.onion', - 'lan-address': 'lnd-grpc-address.local', + name: 'gRPC', + description: 'For connecting to LND gRPC interface', + addresses: [ + 'http://lnd-grpc-address.onion', + 'https://lnd-grpc-address.local', + 'https://192.168.1.1:3449', + ], + ui: true, }, }, 'current-dependencies': { @@ -667,20 +271,17 @@ export const mockPatchData: DataModel = { }, 'dependency-info': { bitcoind: { - manifest: { - title: 'Bitcoin Core', - } as Manifest, + title: 'Bitcoin Core', icon: 'assets/img/service-icons/bitcoind.svg', }, 'btc-rpc-proxy': { - manifest: { - title: 'Bitcoin Proxy', - } as Manifest, + title: 'Bitcoin Proxy', icon: 'assets/img/service-icons/btc-rpc-proxy.png', }, }, 'marketplace-url': 'https://registry.start9.com/', 'developer-key': 'developer-key', + 'has-config': true, }, }, }, diff --git a/frontend/projects/ui/src/app/services/config.service.ts b/frontend/projects/ui/src/app/services/config.service.ts index eb8194729..c6d25bf98 100644 --- a/frontend/projects/ui/src/app/services/config.service.ts +++ b/frontend/projects/ui/src/app/services/config.service.ts @@ -2,10 +2,8 @@ import { DOCUMENT } from '@angular/common' import { Inject, Injectable } from '@angular/core' import { WorkspaceConfig } from '@start9labs/shared' import { - InterfaceDef, - PackageDataEntry, + InstalledPackageInfo, PackageMainStatus, - PackageState, } from 'src/app/services/patch-db/data-model' const { @@ -52,56 +50,12 @@ export class ConfigService { isSecure(): boolean { return window.isSecureContext || this.isTor() } - - isLaunchable( - state: PackageState, - status: PackageMainStatus, - interfaces: Record, - ): boolean { - return ( - state === PackageState.Installed && - status === PackageMainStatus.Running && - hasUi(interfaces) - ) - } - - launchableURL(pkg: PackageDataEntry): string { - if (this.isLan() && hasLanUi(pkg.manifest.interfaces)) { - return `https://${lanUiAddress(pkg)}` - } else { - return `http://${torUiAddress(pkg)}` - } - } } -export function hasTorUi(interfaces: Record): boolean { - const int = getUiInterfaceValue(interfaces) - return !!int?.['tor-config'] -} - -export function hasLanUi(interfaces: Record): boolean { - const int = getUiInterfaceValue(interfaces) - return !!int?.['lan-config'] -} - -export function torUiAddress({ - manifest, - installed, -}: PackageDataEntry): string { - const key = getUiInterfaceKey(manifest.interfaces) - return installed ? installed['interface-addresses'][key]['tor-address'] : '' -} - -export function lanUiAddress({ - manifest, - installed, -}: PackageDataEntry): string { - const key = getUiInterfaceKey(manifest.interfaces) - return installed ? installed['interface-addresses'][key]['lan-address'] : '' -} - -export function hasUi(interfaces: Record): boolean { - return hasTorUi(interfaces) || hasLanUi(interfaces) +export function hasUi( + addressInfo: InstalledPackageInfo['address-info'], +): boolean { + return !!Object.values(addressInfo).find(a => a.ui) } export function removeProtocol(str: string): string { @@ -113,15 +67,3 @@ export function removeProtocol(str: string): string { export function removePort(str: string): string { return str.split(':')[0] } - -export function getUiInterfaceKey( - interfaces: Record, -): string { - return Object.keys(interfaces).find(key => interfaces[key].ui) || '' -} - -export function getUiInterfaceValue( - interfaces: Record, -): InterfaceDef | null { - return Object.values(interfaces).find(i => i.ui) || null -} diff --git a/frontend/projects/ui/src/app/services/patch-db/data-model.ts b/frontend/projects/ui/src/app/services/patch-db/data-model.ts index 89912bf5f..be4d2e98c 100644 --- a/frontend/projects/ui/src/app/services/patch-db/data-model.ts +++ b/frontend/projects/ui/src/app/services/patch-db/data-model.ts @@ -1,6 +1,6 @@ import { InputSpec } from 'start-sdk/types/config-types' import { Url } from '@start9labs/shared' -import { MarketplaceManifest } from '@start9labs/marketplace' +import { Manifest } from '@start9labs/marketplace' import { BasicInfo } from 'src/app/pages/developer-routes/developer-menu/form-info' export interface DataModel { @@ -11,7 +11,7 @@ export interface DataModel { export interface UIData { name: string | null - 'ack-welcome': string // eOS emver + 'ack-welcome': string // emver marketplace: UIMarketplaceData dev: DevData gaming: { @@ -95,176 +95,98 @@ export interface ServerStatusInfo { } updated: boolean 'update-progress': { size: number | null; downloaded: number } | null -} - -export enum ServerStatus { - Running = 'running', - Updated = 'updated', - BackingUp = 'backing-up', + 'shutting-down': boolean } export interface PackageDataEntry { state: PackageState - 'static-files': { - license: Url - instructions: Url - icon: Url - } manifest: Manifest - installed?: InstalledPackageDataEntry // exists when: installed, updating - 'install-progress'?: InstallProgress // exists when: installing, updating + icon: string + installed?: InstalledPackageInfo // when: installed + actions?: Record // when: installed + 'install-progress'?: InstallProgress // when: installing, updating, restoring } +// export type PackageDataEntry = +// | PackageDataEntryInstalled +// | PackageDataEntryNeedsUpdate +// | PackageDataEntryRemoving +// | PackageDataEntryRestoring +// | PackageDataEntryUpdating +// | PackageDataEntryInstalling + +// export type PackageDataEntryBase = { +// manifest: Manifest +// icon: Url +// } + +// export interface PackageDataEntryInstalled extends PackageDataEntryBase { +// state: PackageState.Installed +// installed: InstalledPackageInfo +// actions: Record +// } + +// export interface PackageDataEntryNeedsUpdate extends PackageDataEntryBase { +// state: PackageState.NeedsUpdate +// } + +// export interface PackageDataEntryRemoving extends PackageDataEntryBase { +// state: PackageState.Removing +// } + +// export interface PackageDataEntryRestoring extends PackageDataEntryBase { +// state: PackageState.Restoring +// 'install-progress': InstallProgress +// } + +// export interface PackageDataEntryUpdating extends PackageDataEntryBase { +// state: PackageState.Updating +// 'install-progress': InstallProgress +// } + +// export interface PackageDataEntryInstalling extends PackageDataEntryBase { +// state: PackageState.Installing +// 'install-progress': InstallProgress +// } + export enum PackageState { Installing = 'installing', Installed = 'installed', Updating = 'updating', Removing = 'removing', Restoring = 'restoring', + NeedsUpdate = 'needs-update', } -export interface InstalledPackageDataEntry { +export interface InstalledPackageInfo { status: Status - manifest: Manifest 'last-backup': string | null - 'current-dependencies': { [id: string]: CurrentDependencyInfo } - 'dependency-info': { - [id: string]: { - manifest: Manifest - icon: Url - } - } - 'interface-addresses': { - [id: string]: { 'tor-address': string; 'lan-address': string } - } + 'current-dependencies': Record + 'dependency-info': Record + 'address-info': Record 'marketplace-url': string | null 'developer-key': string + 'has-config': boolean } export interface CurrentDependencyInfo { 'health-checks': string[] // array of health check IDs } -export interface Manifest extends MarketplaceManifest { - assets: { - license: string // filename - instructions: string // filename - icon: string // filename - docker_images: string // filename - assets: string // path to assets folder - scripts: string // path to scripts folder - } - main: ActionImpl - config: ConfigActions | null - volumes: Record - 'min-os-version': string - interfaces: Record - backup: BackupActions - migrations: Migrations | null - actions: Record -} - -export interface DependencyConfig { - check: ActionImpl - 'auto-configure': ActionImpl -} - -export interface ActionImpl { - type: 'docker' - image: string - system: boolean - entrypoint: string - args: string[] - mounts: { [id: string]: string } - 'io-format': DockerIoFormat | null - inject: boolean - 'shm-size': string - 'sigterm-timeout': string | null -} - -export enum DockerIoFormat { - Json = 'json', - Yaml = 'yaml', - Cbor = 'cbor', - Toml = 'toml', -} - -export interface ConfigActions { - get: ActionImpl | null - set: ActionImpl | null -} - -export type Volume = VolumeData - -export interface VolumeData { - type: VolumeType.Data - readonly: boolean -} - -export interface VolumeAssets { - type: VolumeType.Assets -} - -export interface VolumePointer { - type: VolumeType.Pointer - 'package-id': string - 'volume-id': string - path: string - readonly: boolean -} - -export interface VolumeCertificate { - type: VolumeType.Certificate - 'interface-id': string -} - -export interface VolumeBackup { - type: VolumeType.Backup - readonly: boolean -} - -export enum VolumeType { - Data = 'data', - Assets = 'assets', - Pointer = 'pointer', - Certificate = 'certificate', - Backup = 'backup', -} - -export interface InterfaceDef { +export interface AddressInfo { name: string description: string - 'tor-config': TorConfig | null - 'lan-config': LanConfig | null + addresses: Url[] ui: boolean - protocols: string[] -} - -export interface TorConfig { - 'port-mapping': { [port: number]: number } -} - -export type LanConfig = { - [port: number]: { ssl: boolean; mapping: number } -} - -export interface BackupActions { - create: ActionImpl - restore: ActionImpl -} - -export interface Migrations { - from: { [versionRange: string]: ActionImpl } - to: { [versionRange: string]: ActionImpl } } export interface Action { name: string description: string warning: string | null - implementation: ActionImpl - 'allowed-statuses': (PackageMainStatus.Stopped | PackageMainStatus.Running)[] + disabled: string | null 'input-spec': InputSpec | null + group: string | null } export interface Status { @@ -280,6 +202,7 @@ export type MainStatus = | MainStatusRunning | MainStatusBackingUp | MainStatusRestarting + | MainStatusConfiguring export interface MainStatusStopped { status: PackageMainStatus.Stopped @@ -301,13 +224,16 @@ export interface MainStatusRunning { export interface MainStatusBackingUp { status: PackageMainStatus.BackingUp - started: string | null // UTC date string } export interface MainStatusRestarting { status: PackageMainStatus.Restarting } +export interface MainStatusConfiguring { + status: PackageMainStatus.Configuring +} + export enum PackageMainStatus { Starting = 'starting', Running = 'running', @@ -315,6 +241,7 @@ export enum PackageMainStatus { Stopped = 'stopped', BackingUp = 'backing-up', Restarting = 'restarting', + Configuring = 'configuring', } export type HealthCheckResult = { name: string } & ( @@ -339,6 +266,7 @@ export interface HealthCheckResultStarting { export interface HealthCheckResultDisabled { result: HealthResult.Disabled + reason: string } export interface HealthCheckResultSuccess { @@ -370,7 +298,6 @@ export enum DependencyErrorType { IncorrectVersion = 'incorrect-version', ConfigUnsatisfied = 'config-unsatisfied', HealthChecksFailed = 'health-checks-failed', - InterfaceHealthChecksFailed = 'interface-health-checks-failed', Transitive = 'transitive', } diff --git a/frontend/projects/ui/src/app/services/pkg-status-rendering.service.ts b/frontend/projects/ui/src/app/services/pkg-status-rendering.service.ts index 361596b81..421323e9e 100644 --- a/frontend/projects/ui/src/app/services/pkg-status-rendering.service.ts +++ b/frontend/projects/ui/src/app/services/pkg-status-rendering.service.ts @@ -1,6 +1,6 @@ import { isEmptyObject } from '@start9labs/shared' import { - InstalledPackageDataEntry, + InstalledPackageInfo, PackageDataEntry, PackageMainStatus, PackageState, @@ -38,7 +38,7 @@ function getPrimaryStatus(status: Status): PrimaryStatus | PackageMainStatus { } function getDependencyStatus( - installed: InstalledPackageDataEntry, + installed: InstalledPackageInfo, ): DependencyStatus | null { if (isEmptyObject(installed['current-dependencies'])) return null diff --git a/frontend/projects/ui/src/app/services/ui-launcher.service.ts b/frontend/projects/ui/src/app/services/ui-launcher.service.ts index d6cb16cf3..d987beecc 100644 --- a/frontend/projects/ui/src/app/services/ui-launcher.service.ts +++ b/frontend/projects/ui/src/app/services/ui-launcher.service.ts @@ -1,22 +1,27 @@ import { Inject, Injectable } from '@angular/core' import { DOCUMENT } from '@angular/common' -import { PackageDataEntry } from 'src/app/services/patch-db/data-model' -import { ConfigService } from './config.service' +import { InstalledPackageInfo } from 'src/app/services/patch-db/data-model' @Injectable({ providedIn: 'root', }) export class UiLauncherService { - constructor( - @Inject(DOCUMENT) private readonly document: Document, - private readonly config: ConfigService, - ) {} + constructor(@Inject(DOCUMENT) private readonly document: Document) {} - launch(pkg: PackageDataEntry): void { - this.document.defaultView?.open( - this.config.launchableURL(pkg), - '_blank', - 'noreferrer', - ) + launch(addressInfo: InstalledPackageInfo['address-info']): void { + const UIs = Object.values(addressInfo) + .filter(info => info.ui) + .map(info => ({ + name: info.name, + addresses: info.addresses, + })) + + if (UIs.length === 1 && UIs[0].addresses.length === 1) { + this.document.defaultView?.open( + UIs[0].addresses[0], + '_blank', + 'noreferrer', + ) + } } }