diff --git a/sdk/lib/util/getServiceInterface.ts b/sdk/lib/util/getServiceInterface.ts index 6f03e75e5..dd2712849 100644 --- a/sdk/lib/util/getServiceInterface.ts +++ b/sdk/lib/util/getServiceInterface.ts @@ -4,9 +4,7 @@ import { HostInfo, Hostname, HostnameInfo, - ServiceInterface, } from "../types" -import * as regexes from "./regexes" import { ServiceInterfaceType } from "./utils" export type UrlString = string diff --git a/web/package-lock.json b/web/package-lock.json index bcd2967f1..f1aab5e5b 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -1972,6 +1972,7 @@ } }, "../sdk/dist": { + "name": "@start9labs/start-sdk", "version": "0.4.0-rev0.lib0.rc8.beta7", "license": "MIT", "dependencies": { diff --git a/web/projects/shared/src/types/workspace-config.ts b/web/projects/shared/src/types/workspace-config.ts index 101af40fd..57d5e2a4c 100644 --- a/web/projects/shared/src/types/workspace-config.ts +++ b/web/projects/shared/src/types/workspace-config.ts @@ -13,7 +13,7 @@ export type WorkspaceConfig = { community: 'https://community-registry.start9.com/' } mocks: { - maskAs: 'tor' | 'local' | 'localhost' + maskAs: 'tor' | 'local' | 'ip' | 'localhost' // enables local development in secure mode maskAsHttps: boolean skipStartupAlerts: boolean diff --git a/web/projects/ui/src/app/app/menu/menu.component.html b/web/projects/ui/src/app/app/menu/menu.component.html index a54033b67..6db19dea6 100644 --- a/web/projects/ui/src/app/app/menu/menu.component.html +++ b/web/projects/ui/src/app/app/menu/menu.component.html @@ -22,12 +22,6 @@ {{ page.title }} - !synced)), - ) - constructor( private readonly patch: PatchDB, private readonly eosService: EOSService, diff --git a/web/projects/ui/src/app/components/status/status.component.html b/web/projects/ui/src/app/components/status/status.component.html index fd265fd96..e1829f4ca 100644 --- a/web/projects/ui/src/app/components/status/status.component.html +++ b/web/projects/ui/src/app/components/status/status.component.html @@ -8,13 +8,8 @@ > {{ (connected$ | async) ? rendering.display : 'Unknown' }} - - this may take a while + + . This may take a while diff --git a/web/projects/ui/src/app/pages/apps-routes/app-interfaces/app-interfaces-item.component.html b/web/projects/ui/src/app/pages/apps-routes/app-interfaces/app-interfaces-item.component.html index 932c1fd0a..420cbcf1b 100644 --- a/web/projects/ui/src/app/pages/apps-routes/app-interfaces/app-interfaces-item.component.html +++ b/web/projects/ui/src/app/pages/apps-routes/app-interfaces/app-interfaces-item.component.html @@ -1,72 +1,44 @@ - + -

{{ interface.def.name }}

-

{{ interface.def.description }}

+

{{ iFace.name }}

+

{{ iFace.description }}

-
- - +
+ -

Tor Address

-

{{ tor }}

+

{{ address.name }}

+

{{ address.url }}

- + - + - +
- - - -

Tor Address

-

Service does not use a Tor Address

-
-
- - - - -

LAN Address

-

{{ lan }}

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

LAN Address

-

N/A

-
-
diff --git a/web/projects/ui/src/app/pages/apps-routes/app-interfaces/app-interfaces.page.html b/web/projects/ui/src/app/pages/apps-routes/app-interfaces/app-interfaces.page.html index 16c6a5bd6..ea83aedb1 100644 --- a/web/projects/ui/src/app/pages/apps-routes/app-interfaces/app-interfaces.page.html +++ b/web/projects/ui/src/app/pages/apps-routes/app-interfaces/app-interfaces.page.html @@ -8,19 +8,29 @@ - - - - User Interface - + + + User Interfaces (UI) + - - - Machine Interfaces -
- -
+ + Application Program Interfaces (API) + + + + + Peer-To-Peer Interfaces (P2P) +
diff --git a/web/projects/ui/src/app/pages/apps-routes/app-interfaces/app-interfaces.page.ts b/web/projects/ui/src/app/pages/apps-routes/app-interfaces/app-interfaces.page.ts index 825d6536d..3678474e6 100644 --- a/web/projects/ui/src/app/pages/apps-routes/app-interfaces/app-interfaces.page.ts +++ b/web/projects/ui/src/app/pages/apps-routes/app-interfaces/app-interfaces.page.ts @@ -3,19 +3,21 @@ import { WINDOW } from '@ng-web-apis/common' import { ActivatedRoute } from '@angular/router' import { ModalController, ToastController } from '@ionic/angular' import { copyToClipboard, getPkgId } from '@start9labs/shared' -import { getUiInterfaceKey } from 'src/app/services/config.service' -import { - DataModel, - InstalledPackageDataEntry, - InterfaceDef, -} from 'src/app/services/patch-db/data-model' +import { 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' +import { map } from 'rxjs' +import { + ServiceInterface, + ServiceInterfaceWithHostInfo, +} from '@start9labs/start-sdk/mjs/lib/types' -interface LocalInterface { - def: InterfaceDef - addresses: InstalledPackageDataEntry['interface-addresses'][string] +type MappedInterface = ServiceInterface & { + addresses: MappedAddress[] +} +type MappedAddress = { + name: string + url: string } @Component({ @@ -24,60 +26,33 @@ interface LocalInterface { styleUrls: ['./app-interfaces.page.scss'], }) export class AppInterfacesPage { - ui?: LocalInterface - other: LocalInterface[] = [] readonly pkgId = getPkgId(this.route) + readonly serviceInterfaces$ = this.patch + .watch$('package-data', this.pkgId, 'installed', 'service-interfaces') + .pipe( + map(interfaces => { + const sorted = Object.values(interfaces) + .sort(iface => + iface.name.toLowerCase() > iface.name.toLowerCase() ? -1 : 1, + ) + .map(iface => ({ + ...iface, + addresses: getAddresses(iface), + })) + + return { + ui: sorted.filter(val => val.type === 'ui'), + api: sorted.filter(val => val.type === 'api'), + p2p: sorted.filter(val => val.type === 'p2p'), + } + }), + ) + 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'] - : '', - // leave http for services - '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'] - ? // leave http for services - 'http://' + addresses['tor-address'] - : '', - }, - } - }) - } } @Component({ @@ -86,8 +61,7 @@ export class AppInterfacesPage { styleUrls: ['./app-interfaces.page.scss'], }) export class AppInterfacesItemComponent { - @Input() - interface!: LocalInterface + @Input() iFace!: MappedInterface constructor( private readonly toastCtrl: ToastController, @@ -126,3 +100,65 @@ export class AppInterfacesItemComponent { await toast.present() } } + +function getAddresses( + serviceInterface: ServiceInterfaceWithHostInfo, +): MappedAddress[] { + const host = serviceInterface.hostInfo + const addressInfo = serviceInterface.addressInfo + const username = addressInfo.username ? addressInfo.username + '@' : '' + const suffix = addressInfo.suffix || '' + + const hostnames = + host.kind === 'multi' + ? host.hostnames + : host.hostname + ? [host.hostname] + : [] + + return hostnames + .map(h => { + const addresses: MappedAddress[] = [] + + let name = '' + let hostname = '' + + if (h.kind === 'onion') { + name = 'Tor' + hostname = h.hostname.value + } else { + name = h.hostname.kind + hostname = + h.hostname.kind === 'domain' + ? `${h.hostname.subdomain}.${h.hostname.domain}` + : h.hostname.value + } + + if (h.hostname.sslPort) { + const port = h.hostname.sslPort === 443 ? '' : `:${h.hostname.sslPort}` + const scheme = addressInfo.bindOptions.addSsl?.scheme + ? `${addressInfo.bindOptions.addSsl.scheme}://` + : '' + + addresses.push({ + name, + url: `${scheme}${username}${hostname}${port}${suffix}`, + }) + } + + if (h.hostname.port) { + const port = h.hostname.port === 80 ? '' : `:${h.hostname.port}` + const scheme = addressInfo.bindOptions.scheme + ? `${addressInfo.bindOptions.scheme}://` + : '' + + addresses.push({ + name, + url: `${scheme}${username}${hostname}${port}${suffix}`, + }) + } + + return addresses + }) + .flat() +} diff --git a/web/projects/ui/src/app/pages/apps-routes/app-list/app-list-pkg/app-list-pkg.component.html b/web/projects/ui/src/app/pages/apps-routes/app-list/app-list-pkg/app-list-pkg.component.html index 037f7cc07..71988ba5b 100644 --- a/web/projects/ui/src/app/pages/apps-routes/app-list/app-list-pkg/app-list-pkg.component.html +++ b/web/projects/ui/src/app/pages/apps-routes/app-list/app-list-pkg/app-list-pkg.component.html @@ -17,16 +17,18 @@ [installProgress]="pkg.entry['install-progress']" weight="bold" size="small" - [sigtermTimeout]="manifest.main['sigterm-timeout']" + [sigtermTimeout]="sigtermTimeout" > diff --git a/web/projects/ui/src/app/pages/apps-routes/app-list/app-list-pkg/app-list-pkg.component.ts b/web/projects/ui/src/app/pages/apps-routes/app-list/app-list-pkg/app-list-pkg.component.ts index 3302e182e..76559a8b1 100644 --- a/web/projects/ui/src/app/pages/apps-routes/app-list/app-list-pkg/app-list-pkg.component.ts +++ b/web/projects/ui/src/app/pages/apps-routes/app-list/app-list-pkg/app-list-pkg.component.ts @@ -1,5 +1,9 @@ import { ChangeDetectionStrategy, Component, Input } from '@angular/core' -import { PackageMainStatus } from 'src/app/services/patch-db/data-model' +import { + InstalledPackageDataEntry, + MainStatus, + 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' @@ -14,15 +18,26 @@ export class AppListPkgComponent { constructor(private readonly launcherService: UiLauncherService) {} - get status(): PackageMainStatus { + get pkgMainStatus(): MainStatus { return ( - this.pkg.entry.installed?.status.main.status || PackageMainStatus.Stopped + this.pkg.entry.installed?.status.main || { + status: PackageMainStatus.Stopped, + } ) } - launchUi(e: Event): void { + get sigtermTimeout(): string | null { + return this.pkgMainStatus.status === PackageMainStatus.Stopping + ? this.pkgMainStatus.timeout + : null + } + + launchUi( + e: Event, + interfaces: InstalledPackageDataEntry['service-interfaces'], + ): void { e.stopPropagation() e.preventDefault() - this.launcherService.launch(this.pkg.entry) + this.launcherService.launch(interfaces) } } diff --git a/web/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-status/app-show-status.component.html b/web/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-status/app-show-status.component.html index 6a8676ebc..e2379713b 100644 --- a/web/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-status/app-show-status.component.html +++ b/web/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-status/app-show-status.component.html @@ -6,7 +6,7 @@ weight="600" [installProgress]="pkg['install-progress']" [rendering]="PR[status.primary]" - [sigtermTimeout]="pkg.manifest.main['sigterm-timeout']" + [sigtermTimeout]="sigtermTimeout" >
@@ -56,13 +56,11 @@ Launch UI diff --git a/web/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-status/app-show-status.component.ts b/web/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-status/app-show-status.component.ts index f67a2b2fa..dd8832c55 100644 --- a/web/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-status/app-show-status.component.ts +++ b/web/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-status/app-show-status.component.ts @@ -6,8 +6,9 @@ import { PrimaryStatus, } from 'src/app/services/pkg-status-rendering.service' import { - InterfaceDef, + InstalledPackageDataEntry, PackageDataEntry, + PackageMainStatus, PackageState, Status, } from 'src/app/services/patch-db/data-model' @@ -45,8 +46,10 @@ export class AppShowStatusComponent { private readonly connectionService: ConnectionService, ) {} - get interfaces(): Record { - return this.pkg.manifest.interfaces || {} + get interfaces(): + | InstalledPackageDataEntry['service-interfaces'] + | undefined { + return this.pkg.installed?.['service-interfaces'] } get pkgStatus(): Status | null { @@ -73,8 +76,14 @@ export class AppShowStatusComponent { return this.status.primary === PrimaryStatus.Stopped } - launchUi(): void { - this.launcherService.launch(this.pkg) + get sigtermTimeout(): string | null { + return this.pkgStatus?.main.status === PackageMainStatus.Stopping + ? this.pkgStatus.main.timeout + : null + } + + launchUi(interfaces: InstalledPackageDataEntry['service-interfaces']): void { + this.launcherService.launch(interfaces) } async presentModalConfig(): Promise { diff --git a/web/projects/ui/src/app/pages/login/login.page.html b/web/projects/ui/src/app/pages/login/login.page.html index 99f6abbe8..7e27a16c3 100644 --- a/web/projects/ui/src/app/pages/login/login.page.html +++ b/web/projects/ui/src/app/pages/login/login.page.html @@ -6,30 +6,6 @@ - - diff --git a/web/projects/ui/src/app/pages/login/login.page.ts b/web/projects/ui/src/app/pages/login/login.page.ts index 15f7d588e..db0d1c2c4 100644 --- a/web/projects/ui/src/app/pages/login/login.page.ts +++ b/web/projects/ui/src/app/pages/login/login.page.ts @@ -5,7 +5,6 @@ import { AuthService } from 'src/app/services/auth.service' import { Router } from '@angular/router' import { ConfigService } from 'src/app/services/config.service' import { DOCUMENT } from '@angular/common' -import { WINDOW } from '@ng-web-apis/common' @Component({ selector: 'login', @@ -24,14 +23,8 @@ export class LoginPage { private readonly api: ApiService, public readonly config: ConfigService, @Inject(DOCUMENT) public readonly document: Document, - @Inject(WINDOW) private readonly windowRef: Window, ) {} - launchHttps() { - const host = this.config.getHost() - this.windowRef.open(`https://${host}`, '_self') - } - async submit() { this.error = '' diff --git a/web/projects/ui/src/app/pages/server-routes/server-show/server-show.page.html b/web/projects/ui/src/app/pages/server-routes/server-show/server-show.page.html index 760a013e6..f16066874 100644 --- a/web/projects/ui/src/app/pages/server-routes/server-show/server-show.page.html +++ b/web/projects/ui/src/app/pages/server-routes/server-show/server-show.page.html @@ -40,27 +40,6 @@ - - - -

Http detected

-

- Tor is faster over https. - - Download and trust your server's Root CA - - , then switch to https. -

-
- - Open Https - - -
-
diff --git a/web/projects/ui/src/app/pages/server-routes/server-show/server-show.page.ts b/web/projects/ui/src/app/pages/server-routes/server-show/server-show.page.ts index 2b64fcf1c..38087b354 100644 --- a/web/projects/ui/src/app/pages/server-routes/server-show/server-show.page.ts +++ b/web/projects/ui/src/app/pages/server-routes/server-show/server-show.page.ts @@ -41,8 +41,6 @@ export class ServerShowPage { readonly showUpdate$ = this.eosService.showUpdate$ readonly showDiskRepair$ = this.ClientStorageService.showDiskRepair$ - readonly isTorHttp = this.config.isTorHttp() - constructor( private readonly alertCtrl: AlertController, private readonly modalCtrl: ModalController, @@ -56,7 +54,6 @@ export class ServerShowPage { private readonly ClientStorageService: ClientStorageService, private readonly authService: AuthService, private readonly toastCtrl: ToastController, - private readonly config: ConfigService, @Inject(WINDOW) private readonly windowRef: Window, ) {} @@ -305,11 +302,6 @@ export class ServerShowPage { await alert.present() } - async launchHttps() { - const { 'tor-address': torAddress } = await getServerInfo(this.patch) - this.windowRef.open(torAddress, '_self') - } - addClick(title: string) { switch (title) { case 'Manage': diff --git a/web/projects/ui/src/app/pipes/launchable/launchable.pipe.ts b/web/projects/ui/src/app/pipes/launchable/launchable.pipe.ts index be1d5218d..668328820 100644 --- a/web/projects/ui/src/app/pipes/launchable/launchable.pipe.ts +++ b/web/projects/ui/src/app/pipes/launchable/launchable.pipe.ts @@ -1,6 +1,5 @@ import { Pipe, PipeTransform } from '@angular/core' import { - InterfaceDef, PackageMainStatus, PackageState, } from 'src/app/services/patch-db/data-model' @@ -12,11 +11,7 @@ import { ConfigService } from '../../services/config.service' export class LaunchablePipe implements PipeTransform { constructor(private configService: ConfigService) {} - transform( - state: PackageState, - status: PackageMainStatus, - interfaces: Record, - ): boolean { - return this.configService.isLaunchable(state, status, interfaces) + transform(state: PackageState, status: PackageMainStatus): boolean { + return this.configService.isLaunchable(state, status) } } diff --git a/web/projects/ui/src/app/pipes/ui/ui.pipe.ts b/web/projects/ui/src/app/pipes/ui/ui.pipe.ts index 9d46bfd86..03ec11df0 100644 --- a/web/projects/ui/src/app/pipes/ui/ui.pipe.ts +++ b/web/projects/ui/src/app/pipes/ui/ui.pipe.ts @@ -1,12 +1,14 @@ import { Pipe, PipeTransform } from '@angular/core' -import { InterfaceDef } from '../../services/patch-db/data-model' +import { InstalledPackageDataEntry } from '../../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( + interfaces: InstalledPackageDataEntry['service-interfaces'], + ): boolean { + return interfaces ? hasUi(interfaces) : false } } diff --git a/web/projects/ui/src/app/services/api/api.fixures.ts b/web/projects/ui/src/app/services/api/api.fixures.ts index 17460609a..ea83a4f0a 100644 --- a/web/projects/ui/src/app/services/api/api.fixures.ts +++ b/web/projects/ui/src/app/services/api/api.fixures.ts @@ -78,18 +78,6 @@ 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', - }, 'health-checks': {}, config: { get: null, @@ -97,40 +85,6 @@ export module Mock { }, 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', @@ -382,18 +336,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', - }, 'health-checks': {}, config: { get: null, @@ -401,38 +343,6 @@ export module Mock { }, 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', @@ -535,39 +445,10 @@ 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', - }, 'health-checks': {}, 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', @@ -1887,18 +1768,219 @@ export module Mock { }, 'dependency-config-errors': {}, }, - 'interface-addresses': { + 'service-interfaces': { ui: { - 'tor-address': 'bitcoind-ui-address.onion', - 'lan-address': 'bitcoind-ui-address.local', + id: 'ui', + hasPrimary: false, + disabled: false, + masked: false, + name: 'Web UI', + description: + 'A launchable web app for you to interact with your Bitcoin node', + type: 'ui', + addressInfo: { + username: null, + hostId: 'abcdefg', + bindOptions: { + scheme: 'http', + preferredExternalPort: 80, + addSsl: { + preferredExternalPort: 443, + scheme: 'https', + }, + secure: false, + ssl: false, + }, + suffix: '', + }, + hostInfo: { + id: 'abcdefg', + kind: 'multi', + hostnames: [ + { + kind: 'onion', + hostname: { + value: 'bitcoin-ui-address.onion', + port: 80, + sslPort: 443, + }, + }, + { + kind: 'ip', + networkInterfaceId: 'elan0', + public: false, + hostname: { + kind: 'local', + value: 'adjective-noun.local', + port: null, + sslPort: 1234, + }, + }, + { + kind: 'ip', + networkInterfaceId: 'elan0', + public: false, + hostname: { + kind: 'ipv4', + value: '192.168.1.5', + port: null, + sslPort: 1234, + }, + }, + { + kind: 'ip', + networkInterfaceId: 'elan0', + public: false, + hostname: { + kind: 'ipv6', + value: '[2001:db8:85a3:8d3:1319:8a2e:370:7348]', + port: null, + sslPort: 1234, + }, + }, + ], + }, }, rpc: { - 'tor-address': 'bitcoind-rpc-address.onion', - 'lan-address': 'bitcoind-rpc-address.local', + id: 'rpc', + hasPrimary: false, + disabled: false, + masked: false, + name: 'RPC', + description: + 'Used by dependent services and client wallets for connecting to your node', + type: 'api', + addressInfo: { + username: null, + hostId: 'bcdefgh', + bindOptions: { + scheme: 'http', + preferredExternalPort: 80, + addSsl: { + preferredExternalPort: 443, + scheme: 'https', + }, + secure: false, + ssl: false, + }, + suffix: '', + }, + hostInfo: { + id: 'bcdefgh', + kind: 'multi', + hostnames: [ + { + kind: 'onion', + hostname: { + value: 'bitcoin-rpc-address.onion', + port: 80, + sslPort: 443, + }, + }, + { + kind: 'ip', + networkInterfaceId: 'elan0', + public: false, + hostname: { + kind: 'local', + value: 'adjective-noun.local', + port: null, + sslPort: 2345, + }, + }, + { + kind: 'ip', + networkInterfaceId: 'elan0', + public: false, + hostname: { + kind: 'ipv4', + value: '192.168.1.5', + port: null, + sslPort: 2345, + }, + }, + { + kind: 'ip', + networkInterfaceId: 'elan0', + public: false, + hostname: { + kind: 'ipv6', + value: '[2001:db8:85a3:8d3:1319:8a2e:370:7348]', + port: null, + sslPort: 2345, + }, + }, + ], + }, }, p2p: { - 'tor-address': 'bitcoind-p2p-address.onion', - 'lan-address': 'bitcoind-p2p-address.local', + id: 'p2p', + hasPrimary: true, + disabled: false, + masked: false, + name: 'P2P', + description: + 'Used for connecting to other nodes on the Bitcoin network', + type: 'p2p', + addressInfo: { + username: null, + hostId: 'cdefghi', + bindOptions: { + scheme: 'bitcoin', + preferredExternalPort: 8333, + addSsl: null, + secure: true, + ssl: false, + }, + suffix: '', + }, + hostInfo: { + id: 'cdefghi', + kind: 'multi', + hostnames: [ + { + kind: 'onion', + hostname: { + value: 'bitcoin-p2p-address.onion', + port: 8333, + sslPort: null, + }, + }, + { + kind: 'ip', + networkInterfaceId: 'elan0', + public: false, + hostname: { + kind: 'local', + value: 'adjective-noun.local', + port: 3456, + sslPort: null, + }, + }, + { + kind: 'ip', + networkInterfaceId: 'elan0', + public: false, + hostname: { + kind: 'ipv4', + value: '192.168.1.5', + port: 3456, + sslPort: null, + }, + }, + { + kind: 'ip', + networkInterfaceId: 'elan0', + public: false, + hostname: { + kind: 'ipv6', + value: '[2001:db8:85a3:8d3:1319:8a2e:370:7348]', + port: 3456, + sslPort: null, + }, + }, + ], + }, }, }, 'system-pointers': [], @@ -1934,10 +2016,110 @@ export module Mock { 'dependency-config-errors': {}, }, manifest: MockManifestBitcoinProxy, - 'interface-addresses': { - rpc: { - 'tor-address': 'bitcoinproxy-rpc-address.onion', - 'lan-address': 'bitcoinproxy-rpc-address.local', + 'service-interfaces': { + ui: { + id: 'ui', + hasPrimary: false, + disabled: false, + masked: false, + name: 'Web UI', + description: 'A launchable web app for Bitcoin Proxy', + type: 'ui', + addressInfo: { + username: null, + hostId: 'hijklmnop', + bindOptions: { + scheme: 'http', + preferredExternalPort: 80, + addSsl: { + preferredExternalPort: 443, + scheme: 'https', + }, + secure: true, + ssl: true, + }, + suffix: '', + }, + hostInfo: { + id: 'hijklmnop', + kind: 'multi', + hostnames: [ + { + kind: 'onion', + hostname: { + value: 'proxy-ui-address.onion', + port: 80, + sslPort: 443, + }, + }, + { + kind: 'ip', + networkInterfaceId: 'elan0', + public: false, + hostname: { + kind: 'local', + value: 'adjective-noun.local', + port: null, + sslPort: 4567, + }, + }, + { + kind: 'ip', + networkInterfaceId: 'elan0', + public: false, + hostname: { + kind: 'ipv4', + value: '192.168.1.5', + port: null, + sslPort: 4567, + }, + }, + { + kind: 'ip', + networkInterfaceId: 'elan0', + public: false, + hostname: { + kind: 'ipv6', + value: '[2001:db8:85a3:8d3:1319:8a2e:370:7348]', + port: null, + sslPort: 4567, + }, + }, + { + kind: 'ip', + networkInterfaceId: 'wlan0', + public: false, + hostname: { + kind: 'local', + value: 'adjective-noun.local', + port: null, + sslPort: 4567, + }, + }, + { + kind: 'ip', + networkInterfaceId: 'wlan0', + public: false, + hostname: { + kind: 'ipv4', + value: '192.168.1.7', + port: null, + sslPort: 4567, + }, + }, + { + kind: 'ip', + networkInterfaceId: 'wlan0', + public: false, + hostname: { + kind: 'ipv6', + value: '[2001:db8:85a3:8d3:1319:8a2e:370:7348]', + port: null, + sslPort: 4567, + }, + }, + ], + }, }, }, 'system-pointers': [], @@ -1985,14 +2167,213 @@ export module Mock { }, }, manifest: MockManifestLnd, - 'interface-addresses': { - rpc: { - 'tor-address': 'lnd-rpc-address.onion', - 'lan-address': 'lnd-rpc-address.local', - }, + 'service-interfaces': { grpc: { - 'tor-address': 'lnd-grpc-address.onion', - 'lan-address': 'lnd-grpc-address.local', + id: 'grpc', + hasPrimary: false, + disabled: false, + masked: false, + name: 'GRPC', + description: + 'Used by dependent services and client wallets for connecting to your node', + type: 'api', + addressInfo: { + username: null, + hostId: 'qrstuv', + bindOptions: { + scheme: 'grpc', + preferredExternalPort: 10009, + addSsl: null, + secure: true, + ssl: true, + }, + suffix: '', + }, + hostInfo: { + id: 'qrstuv', + kind: 'multi', + hostnames: [ + { + kind: 'onion', + hostname: { + value: 'lnd-grpc-address.onion', + port: 10009, + sslPort: null, + }, + }, + { + kind: 'ip', + networkInterfaceId: 'elan0', + public: false, + hostname: { + kind: 'local', + value: 'adjective-noun.local', + port: 5678, + sslPort: null, + }, + }, + { + kind: 'ip', + networkInterfaceId: 'elan0', + public: false, + hostname: { + kind: 'ipv4', + value: '192.168.1.5', + port: 5678, + sslPort: null, + }, + }, + { + kind: 'ip', + networkInterfaceId: 'elan0', + public: false, + hostname: { + kind: 'ipv6', + value: '[2001:db8:85a3:8d3:1319:8a2e:370:7348]', + port: 5678, + sslPort: null, + }, + }, + ], + }, + }, + lndconnect: { + id: 'lndconnect', + hasPrimary: false, + disabled: false, + masked: true, + name: 'LND Connect', + description: + 'Used by client wallets adhering to LND Connect protocol to connect to your node', + type: 'api', + addressInfo: { + username: null, + hostId: 'qrstuv', + bindOptions: { + scheme: 'lndconnect', + preferredExternalPort: 10009, + addSsl: null, + secure: true, + ssl: true, + }, + suffix: 'cert=askjdfbjadnaskjnd&macaroon=ksjbdfnhjasbndjksand', + }, + hostInfo: { + id: 'qrstuv', + kind: 'multi', + hostnames: [ + { + kind: 'onion', + hostname: { + value: 'lnd-grpc-address.onion', + port: 10009, + sslPort: null, + }, + }, + { + kind: 'ip', + networkInterfaceId: 'elan0', + public: false, + hostname: { + kind: 'local', + value: 'adjective-noun.local', + port: 5678, + sslPort: null, + }, + }, + { + kind: 'ip', + networkInterfaceId: 'elan0', + public: false, + hostname: { + kind: 'ipv4', + value: '192.168.1.5', + port: 5678, + sslPort: null, + }, + }, + { + kind: 'ip', + networkInterfaceId: 'elan0', + public: false, + hostname: { + kind: 'ipv6', + value: '[2001:db8:85a3:8d3:1319:8a2e:370:7348]', + port: 5678, + sslPort: null, + }, + }, + ], + }, + }, + p2p: { + id: 'p2p', + hasPrimary: true, + disabled: false, + masked: false, + name: 'P2P', + description: + 'Used for connecting to other nodes on the Bitcoin network', + type: 'p2p', + addressInfo: { + username: null, + hostId: 'rstuvw', + bindOptions: { + scheme: null, + preferredExternalPort: 9735, + addSsl: null, + secure: true, + ssl: true, + }, + suffix: '', + }, + hostInfo: { + id: 'rstuvw', + kind: 'multi', + hostnames: [ + { + kind: 'onion', + hostname: { + value: 'lnd-p2p-address.onion', + port: 9735, + sslPort: null, + }, + }, + { + kind: 'ip', + networkInterfaceId: 'elan0', + public: false, + hostname: { + kind: 'local', + value: 'adjective-noun.local', + port: 6789, + sslPort: null, + }, + }, + { + kind: 'ip', + networkInterfaceId: 'elan0', + public: false, + hostname: { + kind: 'ipv4', + value: '192.168.1.5', + port: 6789, + sslPort: null, + }, + }, + { + kind: 'ip', + networkInterfaceId: 'elan0', + public: false, + hostname: { + kind: 'ipv6', + value: '[2001:db8:85a3:8d3:1319:8a2e:370:7348]', + port: 6789, + sslPort: null, + }, + }, + ], + }, }, }, 'system-pointers': [], diff --git a/web/projects/ui/src/app/services/api/embassy-mock-api.service.ts b/web/projects/ui/src/app/services/api/embassy-mock-api.service.ts index c98a4bd87..a2e5324a1 100644 --- a/web/projects/ui/src/app/services/api/embassy-mock-api.service.ts +++ b/web/projects/ui/src/app/services/api/embassy-mock-api.service.ts @@ -919,8 +919,10 @@ export class MockApiService extends ApiService { const patch2 = [ { op: PatchOp.REPLACE, - path: path + '/status', - value: PackageMainStatus.Stopped, + path: path, + value: { + status: PackageMainStatus.Stopped, + }, }, ] this.mockRevision(patch2) @@ -929,13 +931,11 @@ export class MockApiService extends ApiService { const patch = [ { op: PatchOp.REPLACE, - path: path + '/status', - value: PackageMainStatus.Stopping, - }, - { - op: PatchOp.REPLACE, - path: path + '/health', - value: {}, + path: path, + value: { + status: PackageMainStatus.Stopping, + timeout: '35s', + }, }, ] diff --git a/web/projects/ui/src/app/services/api/mock-patch.ts b/web/projects/ui/src/app/services/api/mock-patch.ts index 1dc7abd66..35942e92f 100644 --- a/web/projects/ui/src/app/services/api/mock-patch.ts +++ b/web/projects/ui/src/app/services/api/mock-patch.ts @@ -116,18 +116,6 @@ 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', - }, 'health-checks': { 'chain-state': { name: 'Chain State', @@ -152,41 +140,6 @@ export const mockPatchData: DataModel = { } 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', @@ -441,18 +394,110 @@ export const mockPatchData: DataModel = { }, 'dependency-config-errors': {}, }, - 'interface-addresses': { + 'service-interfaces': { ui: { - 'tor-address': 'bitcoind-ui-address.onion', - 'lan-address': 'bitcoind-ui-address.local', - }, - rpc: { - 'tor-address': 'bitcoind-rpc-address.onion', - 'lan-address': 'bitcoind-rpc-address.local', - }, - p2p: { - 'tor-address': 'bitcoind-p2p-address.onion', - 'lan-address': 'bitcoind-p2p-address.local', + id: 'ui', + hasPrimary: false, + disabled: false, + masked: false, + name: 'Web UI', + description: 'A launchable web app for Bitcoin Proxy', + type: 'ui', + addressInfo: { + username: null, + hostId: 'hijklmnop', + bindOptions: { + scheme: 'http', + preferredExternalPort: 80, + addSsl: { + preferredExternalPort: 443, + scheme: 'https', + }, + secure: true, + ssl: true, + }, + suffix: '', + }, + hostInfo: { + id: 'hijklmnop', + kind: 'multi', + hostnames: [ + { + kind: 'onion', + hostname: { + value: 'proxy-ui-address.onion', + port: 80, + sslPort: 443, + }, + }, + { + kind: 'ip', + networkInterfaceId: 'elan0', + public: false, + hostname: { + kind: 'local', + value: 'adjective-noun.local', + port: null, + sslPort: 4567, + }, + }, + { + kind: 'ip', + networkInterfaceId: 'elan0', + public: false, + hostname: { + kind: 'ipv4', + value: '192.168.1.5', + port: null, + sslPort: 4567, + }, + }, + { + kind: 'ip', + networkInterfaceId: 'elan0', + public: false, + hostname: { + kind: 'ipv6', + value: '[2001:db8:85a3:8d3:1319:8a2e:370:7348]', + port: null, + sslPort: 4567, + }, + }, + { + kind: 'ip', + networkInterfaceId: 'wlan0', + public: false, + hostname: { + kind: 'local', + value: 'adjective-noun.local', + port: null, + sslPort: 4567, + }, + }, + { + kind: 'ip', + networkInterfaceId: 'wlan0', + public: false, + hostname: { + kind: 'ipv4', + value: '192.168.1.7', + port: null, + sslPort: 4567, + }, + }, + { + kind: 'ip', + networkInterfaceId: 'wlan0', + public: false, + hostname: { + kind: 'ipv6', + value: '[2001:db8:85a3:8d3:1319:8a2e:370:7348]', + port: null, + sslPort: 4567, + }, + }, + ], + }, }, }, 'system-pointers': [], @@ -506,18 +551,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', - }, 'health-checks': {}, config: { get: null, @@ -525,38 +558,6 @@ export const mockPatchData: DataModel = { }, 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', @@ -642,14 +643,213 @@ export const mockPatchData: DataModel = { 'btc-rpc-proxy': 'This is a config unsatisfied error', }, }, - 'interface-addresses': { - rpc: { - 'tor-address': 'lnd-rpc-address.onion', - 'lan-address': 'lnd-rpc-address.local', - }, + 'service-interfaces': { grpc: { - 'tor-address': 'lnd-grpc-address.onion', - 'lan-address': 'lnd-grpc-address.local', + id: 'grpc', + hasPrimary: false, + disabled: false, + masked: false, + name: 'GRPC', + description: + 'Used by dependent services and client wallets for connecting to your node', + type: 'api', + addressInfo: { + username: null, + hostId: 'qrstuv', + bindOptions: { + scheme: 'grpc', + preferredExternalPort: 10009, + addSsl: null, + secure: true, + ssl: true, + }, + suffix: '', + }, + hostInfo: { + id: 'qrstuv', + kind: 'multi', + hostnames: [ + { + kind: 'onion', + hostname: { + value: 'lnd-grpc-address.onion', + port: 10009, + sslPort: null, + }, + }, + { + kind: 'ip', + networkInterfaceId: 'elan0', + public: false, + hostname: { + kind: 'local', + value: 'adjective-noun.local', + port: 5678, + sslPort: null, + }, + }, + { + kind: 'ip', + networkInterfaceId: 'elan0', + public: false, + hostname: { + kind: 'ipv4', + value: '192.168.1.5', + port: 5678, + sslPort: null, + }, + }, + { + kind: 'ip', + networkInterfaceId: 'elan0', + public: false, + hostname: { + kind: 'ipv6', + value: '[2001:db8:85a3:8d3:1319:8a2e:370:7348]', + port: 5678, + sslPort: null, + }, + }, + ], + }, + }, + lndconnect: { + id: 'lndconnect', + hasPrimary: false, + disabled: false, + masked: true, + name: 'LND Connect', + description: + 'Used by client wallets adhering to LND Connect protocol to connect to your node', + type: 'api', + addressInfo: { + username: null, + hostId: 'qrstuv', + bindOptions: { + scheme: 'lndconnect', + preferredExternalPort: 10009, + addSsl: null, + secure: true, + ssl: true, + }, + suffix: 'cert=askjdfbjadnaskjnd&macaroon=ksjbdfnhjasbndjksand', + }, + hostInfo: { + id: 'qrstuv', + kind: 'multi', + hostnames: [ + { + kind: 'onion', + hostname: { + value: 'lnd-grpc-address.onion', + port: 10009, + sslPort: null, + }, + }, + { + kind: 'ip', + networkInterfaceId: 'elan0', + public: false, + hostname: { + kind: 'local', + value: 'adjective-noun.local', + port: 5678, + sslPort: null, + }, + }, + { + kind: 'ip', + networkInterfaceId: 'elan0', + public: false, + hostname: { + kind: 'ipv4', + value: '192.168.1.5', + port: 5678, + sslPort: null, + }, + }, + { + kind: 'ip', + networkInterfaceId: 'elan0', + public: false, + hostname: { + kind: 'ipv6', + value: '[2001:db8:85a3:8d3:1319:8a2e:370:7348]', + port: 5678, + sslPort: null, + }, + }, + ], + }, + }, + p2p: { + id: 'p2p', + hasPrimary: true, + disabled: false, + masked: false, + name: 'P2P', + description: + 'Used for connecting to other nodes on the Bitcoin network', + type: 'p2p', + addressInfo: { + username: null, + hostId: 'rstuvw', + bindOptions: { + scheme: null, + preferredExternalPort: 9735, + addSsl: null, + secure: true, + ssl: true, + }, + suffix: '', + }, + hostInfo: { + id: 'rstuvw', + kind: 'multi', + hostnames: [ + { + kind: 'onion', + hostname: { + value: 'lnd-p2p-address.onion', + port: 9735, + sslPort: null, + }, + }, + { + kind: 'ip', + networkInterfaceId: 'elan0', + public: false, + hostname: { + kind: 'local', + value: 'adjective-noun.local', + port: 6789, + sslPort: null, + }, + }, + { + kind: 'ip', + networkInterfaceId: 'elan0', + public: false, + hostname: { + kind: 'ipv4', + value: '192.168.1.5', + port: 6789, + sslPort: null, + }, + }, + { + kind: 'ip', + networkInterfaceId: 'elan0', + public: false, + hostname: { + kind: 'ipv6', + value: '[2001:db8:85a3:8d3:1319:8a2e:370:7348]', + port: 6789, + sslPort: null, + }, + }, + ], + }, }, }, 'system-pointers': [], diff --git a/web/projects/ui/src/app/services/config.service.ts b/web/projects/ui/src/app/services/config.service.ts index f8fef3d60..50f228e95 100644 --- a/web/projects/ui/src/app/services/config.service.ts +++ b/web/projects/ui/src/app/services/config.service.ts @@ -2,8 +2,11 @@ import { DOCUMENT } from '@angular/common' import { Inject, Injectable } from '@angular/core' import { WorkspaceConfig } from '@start9labs/shared' import { - InterfaceDef, - PackageDataEntry, + HostnameInfoIp, + HostnameInfoOnion, +} from '@start9labs/start-sdk/mjs/lib/types' +import { + InstalledPackageDataEntry, PackageMainStatus, PackageState, } from 'src/app/services/patch-db/data-model' @@ -45,10 +48,6 @@ export class ConfigService { : this.hostname.endsWith('.local') } - isTorHttp(): boolean { - return this.isTor() && !this.isHttps() - } - isLanHttp(): boolean { return !this.isTor() && !this.isLocalhost() && !this.isHttps() } @@ -57,24 +56,60 @@ export class ConfigService { return window.isSecureContext || this.isTor() } - isLaunchable( - state: PackageState, - status: PackageMainStatus, - interfaces: Record, - ): boolean { + isLaunchable(state: PackageState, status: PackageMainStatus): boolean { return ( - state === PackageState.Installed && - status === PackageMainStatus.Running && - hasUi(interfaces) + state === PackageState.Installed && status === PackageMainStatus.Running ) } - launchableURL(pkg: PackageDataEntry): string { - if (!this.isTor() && hasLocalUi(pkg.manifest.interfaces)) { - return `https://${lanUiAddress(pkg)}` + /** ${scheme}://${username}@${host}:${externalPort}${suffix} */ + launchableAddress( + interfaces: InstalledPackageDataEntry['service-interfaces'], + ): string { + const ui = Object.values(interfaces).find(i => i.type === 'ui') + + if (!ui) return '' + + const host = ui.hostInfo + const addressInfo = ui.addressInfo + const scheme = this.isHttps() ? 'https' : 'http' + const username = addressInfo.username ? addressInfo.username + '@' : '' + const suffix = addressInfo.suffix || '' + const url = new URL(`${scheme}://${username}placeholder${suffix}`) + + if (host.kind === 'multi') { + const onionHostname = host.hostnames.find( + h => h.kind === 'onion', + ) as HostnameInfoOnion + + if (this.isTor() && onionHostname) { + url.hostname = onionHostname.hostname.value + } else { + const ipHostname = host.hostnames.find( + h => h.kind === 'ip', + ) as HostnameInfoIp + + if (!ipHostname) return '' + + url.hostname = this.hostname + url.port = String( + ipHostname.hostname.sslPort || ipHostname.hostname.port, + ) + } } else { - return `http://${torUiAddress(pkg)}` + const hostname = host.hostname + + if (!hostname) return '' + + if (this.isTor() && hostname.kind === 'onion') { + url.hostname = hostname.hostname.value + } else { + url.hostname = this.hostname + url.port = String(hostname.hostname.sslPort || hostname.hostname.port) + } } + + return url.href } getHost(): string { @@ -92,54 +127,8 @@ export class ConfigService { } } -export function hasTorUi(interfaces: Record): boolean { - const int = getUiInterfaceValue(interfaces) - return !!int?.['tor-config'] -} - -export function hasLocalUi(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) || hasLocalUi(interfaces) -} - -export function removeProtocol(str: string): string { - if (str.startsWith('http://')) return str.slice(7) - if (str.startsWith('https://')) return str.slice(8) - return str -} - -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 +export function hasUi( + interfaces: InstalledPackageDataEntry['service-interfaces'], +): boolean { + return Object.values(interfaces).some(iface => iface.type === 'ui') } diff --git a/web/projects/ui/src/app/services/patch-db/data-model.ts b/web/projects/ui/src/app/services/patch-db/data-model.ts index e4ea729d9..e00350528 100644 --- a/web/projects/ui/src/app/services/patch-db/data-model.ts +++ b/web/projects/ui/src/app/services/patch-db/data-model.ts @@ -2,6 +2,7 @@ import { ConfigSpec } from 'src/app/pkg-config/config-types' import { Url } from '@start9labs/shared' import { MarketplaceManifest } from '@start9labs/marketplace' import { BasicInfo } from 'src/app/pages/developer-routes/developer-menu/form-info' +import { ServiceInterfaceWithHostInfo } from '@start9labs/start-sdk/mjs/lib/types' export interface DataModel { 'server-info': ServerInfo @@ -139,9 +140,7 @@ export interface InstalledPackageDataEntry { icon: Url } } - 'interface-addresses': { - [id: string]: { 'tor-address': string; 'lan-address': string } - } + 'service-interfaces': Record 'marketplace-url': string | null 'developer-key': string } @@ -160,7 +159,6 @@ export interface Manifest extends MarketplaceManifest { assets: string // path to assets folder scripts: string // path to scripts folder } - main: ActionImpl 'health-checks': Record< string, ActionImpl & { name: string; 'success-message': string | null } @@ -168,7 +166,6 @@ export interface Manifest extends MarketplaceManifest { config: ConfigActions | null volumes: Record 'min-os-version': string - interfaces: Record backup: BackupActions migrations: Migrations | null actions: Record @@ -241,15 +238,6 @@ export enum VolumeType { Backup = 'backup', } -export interface InterfaceDef { - name: string - description: string - 'tor-config': TorConfig | null - 'lan-config': LanConfig | null - ui: boolean - protocols: string[] -} - export interface TorConfig { 'port-mapping': { [port: number]: number } } @@ -297,6 +285,7 @@ export interface MainStatusStopped { export interface MainStatusStopping { status: PackageMainStatus.Stopping + timeout: string } export interface MainStatusStarting { diff --git a/web/projects/ui/src/app/services/ui-launcher.service.ts b/web/projects/ui/src/app/services/ui-launcher.service.ts index 55559bcd3..70666e264 100644 --- a/web/projects/ui/src/app/services/ui-launcher.service.ts +++ b/web/projects/ui/src/app/services/ui-launcher.service.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@angular/core' import { WINDOW } from '@ng-web-apis/common' -import { PackageDataEntry } from 'src/app/services/patch-db/data-model' +import { InstalledPackageDataEntry } from 'src/app/services/patch-db/data-model' import { ConfigService } from './config.service' @Injectable({ @@ -12,7 +12,11 @@ export class UiLauncherService { private readonly config: ConfigService, ) {} - launch(pkg: PackageDataEntry): void { - this.windowRef.open(this.config.launchableURL(pkg), '_blank', 'noreferrer') + launch(interfaces: InstalledPackageDataEntry['service-interfaces']): void { + this.windowRef.open( + this.config.launchableAddress(interfaces), + '_blank', + 'noreferrer', + ) } }