Merge branch 'integration/new-container-runtime' of github.com:Start9Labs/start-os into integration/new-container-runtime

This commit is contained in:
J H
2024-02-23 15:32:06 -07:00
25 changed files with 1096 additions and 571 deletions

View File

@@ -4,9 +4,7 @@ import {
HostInfo,
Hostname,
HostnameInfo,
ServiceInterface,
} from "../types"
import * as regexes from "./regexes"
import { ServiceInterfaceType } from "./utils"
export type UrlString = string

1
web/package-lock.json generated
View File

@@ -1972,6 +1972,7 @@
}
},
"../sdk/dist": {
"name": "@start9labs/start-sdk",
"version": "0.4.0-rev0.lib0.rc8.beta7",
"license": "MIT",
"dependencies": {

View File

@@ -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

View File

@@ -22,12 +22,6 @@
<ion-label class="label montserrat" routerLinkActive="label_selected">
{{ page.title }}
</ion-label>
<ion-icon
*ngIf="page.url === '/system' && (warning$ | async)"
color="warning"
size="small"
name="warning"
></ion-icon>
<ion-icon
*ngIf="page.url === '/system' && (showEOSUpdate$ | async)"
color="success"

View File

@@ -114,11 +114,6 @@ export class MenuComponent {
readonly theme$ = inject(THEME)
readonly warning$ = merge(
of(this.config.isTorHttp()),
this.patch.watch$('server-info', 'ntp-synced').pipe(map(synced => !synced)),
)
constructor(
private readonly patch: PatchDB<DataModel>,
private readonly eosService: EOSService,

View File

@@ -8,13 +8,8 @@
>
{{ (connected$ | async) ? rendering.display : 'Unknown' }}
<span
*ngIf="
rendering.display === PR[PS.Stopping].display &&
(sigtermTimeout | durationToSeconds) > 30
"
>
this may take a while
<span *ngIf="sigtermTimeout && (sigtermTimeout | durationToSeconds) > 30">
. This may take a while
</span>
<span *ngIf="installProgress">

View File

@@ -1,72 +1,44 @@
<ion-item *ngIf="interface">
<ion-item *ngIf="iFace">
<ion-icon
slot="start"
size="large"
[name]="interface.def.ui ? 'desktop-outline' : 'terminal-outline'"
[name]="
iFace.type === 'ui'
? 'desktop-outline'
: iFace.type === 'api'
? 'terminal-outline'
: 'people-outline'
"
></ion-icon>
<ion-label>
<h1>{{ interface.def.name }}</h1>
<h2>{{ interface.def.description }}</h2>
<h1>{{ iFace.name }}</h1>
<h2>{{ iFace.description }}</h2>
</ion-label>
</ion-item>
<div *ngIf="interface" style="padding-left: 64px">
<!-- has tor -->
<ion-item *ngIf="interface.addresses['tor-address'] as tor">
<div *ngIf="iFace" style="padding-left: 64px">
<ion-item *ngFor="let address of iFace.addresses">
<ion-label>
<h2>Tor Address</h2>
<p>{{ tor }}</p>
<h2>{{ address.name }}</h2>
<p>{{ address.url }}</p>
</ion-label>
<ion-buttons slot="end">
<ion-button *ngIf="interface.def.ui" fill="clear" (click)="launch(tor)">
<ion-button
*ngIf="iFace.type === 'ui'"
fill="clear"
(click)="launch(address.url)"
>
<ion-icon size="small" slot="icon-only" name="open-outline"></ion-icon>
</ion-button>
<ion-button fill="clear" (click)="showQR(tor)">
<ion-button fill="clear" (click)="showQR(address.url)">
<ion-icon
size="small"
slot="icon-only"
name="qr-code-outline"
></ion-icon>
</ion-button>
<ion-button fill="clear" (click)="copy(tor)">
<ion-button fill="clear" (click)="copy(address.url)">
<ion-icon size="small" slot="icon-only" name="copy-outline"></ion-icon>
</ion-button>
</ion-buttons>
</ion-item>
<!-- no tor -->
<ion-item *ngIf="!interface.addresses['tor-address']">
<ion-label>
<h2>Tor Address</h2>
<p>Service does not use a Tor Address</p>
</ion-label>
</ion-item>
<!-- lan -->
<ion-item *ngIf="interface.addresses['lan-address'] as lan">
<ion-label>
<h2>LAN Address</h2>
<p>{{ lan }}</p>
</ion-label>
<ion-buttons slot="end">
<ion-button *ngIf="interface.def.ui" fill="clear" (click)="launch(lan)">
<ion-icon size="small" slot="icon-only" name="open-outline"></ion-icon>
</ion-button>
<ion-button fill="clear" (click)="showQR(lan)">
<ion-icon
size="small"
slot="icon-only"
name="qr-code-outline"
></ion-icon>
</ion-button>
<ion-button fill="clear" (click)="copy(lan)">
<ion-icon size="small" slot="icon-only" name="copy-outline"></ion-icon>
</ion-button>
</ion-buttons>
</ion-item>
<!-- no lan -->
<ion-item *ngIf="!interface.addresses['lan-address']">
<ion-label>
<h2>LAN Address</h2>
<p>N/A</p>
</ion-label>
</ion-item>
</div>

View File

@@ -8,19 +8,29 @@
</ion-header>
<ion-content class="ion-padding-top with-widgets">
<ion-item-group>
<!-- iff ui -->
<ng-container *ngIf="ui">
<ion-item-divider>User Interface</ion-item-divider>
<app-interfaces-item [interface]="ui"></app-interfaces-item>
<ion-item-group *ngIf="serviceInterfaces$ | async as serviceInterfaces">
<ng-container *ngIf="serviceInterfaces.ui.length">
<ion-item-divider>User Interfaces (UI)</ion-item-divider>
<app-interfaces-item
*ngFor="let ui of serviceInterfaces.ui"
[iFace]="ui"
></app-interfaces-item>
</ng-container>
<!-- other interface -->
<ng-container *ngIf="other.length">
<ion-item-divider>Machine Interfaces</ion-item-divider>
<div *ngFor="let interface of other" style="margin-bottom: 30px">
<app-interfaces-item [interface]="interface"></app-interfaces-item>
</div>
<ng-container *ngIf="serviceInterfaces.api.length">
<ion-item-divider>Application Program Interfaces (API)</ion-item-divider>
<app-interfaces-item
*ngFor="let api of serviceInterfaces.api"
[iFace]="api"
></app-interfaces-item>
</ng-container>
<ng-container *ngIf="serviceInterfaces.p2p.length">
<ion-item-divider>Peer-To-Peer Interfaces (P2P)</ion-item-divider>
<app-interfaces-item
*ngFor="let p2p of serviceInterfaces.p2p"
[iFace]="p2p"
></app-interfaces-item>
</ng-container>
</ion-item-group>
</ion-content>

View File

@@ -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<DataModel>,
) {}
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()
}

View File

@@ -17,16 +17,18 @@
[installProgress]="pkg.entry['install-progress']"
weight="bold"
size="small"
[sigtermTimeout]="manifest.main['sigterm-timeout']"
[sigtermTimeout]="sigtermTimeout"
></status>
</ion-label>
<ion-button
*ngIf="manifest.interfaces | hasUi"
*ngIf="
pkg.entry.installed && (pkg.entry.installed['service-interfaces'] | hasUi)
"
slot="end"
fill="clear"
color="primary"
(click)="launchUi($event)"
[disabled]="!(pkg.entry.state | isLaunchable: status:manifest.interfaces)"
(click)="launchUi($event, pkg.entry.installed['service-interfaces'])"
[disabled]="!(pkg.entry.state | isLaunchable: pkgMainStatus.status)"
>
<ion-icon slot="icon-only" name="open-outline"></ion-icon>
</ion-button>

View File

@@ -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)
}
}

View File

@@ -6,7 +6,7 @@
weight="600"
[installProgress]="pkg['install-progress']"
[rendering]="PR[status.primary]"
[sigtermTimeout]="pkg.manifest.main['sigterm-timeout']"
[sigtermTimeout]="sigtermTimeout"
></status>
</ion-label>
</ion-item>
@@ -56,13 +56,11 @@
</ion-button>
<ion-button
*ngIf="pkgStatus && (interfaces | hasUi)"
*ngIf="pkgStatus && interfaces && (interfaces | hasUi)"
class="action-button"
color="primary"
[disabled]="
!(pkg.state | isLaunchable: pkgStatus.main.status:interfaces)
"
(click)="launchUi()"
[disabled]="!(pkg.state | isLaunchable: pkgStatus.main.status)"
(click)="launchUi(interfaces)"
>
<ion-icon slot="start" name="open-outline"></ion-icon>
Launch UI

View File

@@ -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<string, InterfaceDef> {
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<void> {

View File

@@ -6,30 +6,6 @@
<!-- not Local HTTP -->
<ng-template #notLanHttp>
<div *ngIf="config.isTorHttp()" class="banner">
<ion-item color="warning">
<ion-icon slot="start" name="warning-outline"></ion-icon>
<ion-label>
<h2 style="font-weight: bold">Http detected</h2>
<p style="font-weight: 600">
Tor is faster over https. Your Root CA must be trusted.
<a
href="https://docs.start9.com/0.3.5.x/user-manual/trust-ca"
target="_blank"
noreferrer
style="color: black"
>
View instructions
</a>
</p>
</ion-label>
<ion-button slot="end" color="light" (click)="launchHttps()">
Open Https
<ion-icon slot="end" name="open-outline"></ion-icon>
</ion-button>
</ion-item>
</div>
<ion-grid class="grid">
<ion-row class="row">
<ion-col>

View File

@@ -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 = ''

View File

@@ -40,27 +40,6 @@
</ion-button>
</ion-item>
<ion-item *ngIf="isTorHttp" color="warning" class="ion-margin-bottom">
<ion-icon slot="start" name="warning-outline"></ion-icon>
<ion-label>
<h2 style="font-weight: bold">Http detected</h2>
<p style="font-weight: 600">
Tor is faster over https.
<a
[routerLink]="['/system', 'root-ca']"
style="color: var(--ion-color-light)"
>
Download and trust your server's Root CA
</a>
, then switch to https.
</p>
</ion-label>
<ion-button slot="end" color="light" (click)="launchHttps()">
Open Https
<ion-icon slot="end" name="open-outline"></ion-icon>
</ion-button>
</ion-item>
<div *ngFor="let cat of settings | keyvalue : asIsOrder">
<ion-item-divider>
<ion-text color="dark" (click)="addClick(cat.key)">

View File

@@ -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':

View File

@@ -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<string, InterfaceDef>,
): boolean {
return this.configService.isLaunchable(state, status, interfaces)
transform(state: PackageState, status: PackageMainStatus): boolean {
return this.configService.isLaunchable(state, status)
}
}

View File

@@ -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<string, InterfaceDef>): boolean {
return hasUi(interfaces)
transform(
interfaces: InstalledPackageDataEntry['service-interfaces'],
): boolean {
return interfaces ? hasUi(interfaces) : false
}
}

View File

@@ -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': [],

View File

@@ -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',
},
},
]

View File

@@ -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': [],

View File

@@ -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<string, InterfaceDef>,
): 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<string, InterfaceDef>): boolean {
const int = getUiInterfaceValue(interfaces)
return !!int?.['tor-config']
}
export function hasLocalUi(interfaces: Record<string, InterfaceDef>): 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<string, InterfaceDef>): 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, InterfaceDef>,
): string {
return Object.keys(interfaces).find(key => interfaces[key].ui) || ''
}
export function getUiInterfaceValue(
interfaces: Record<string, InterfaceDef>,
): 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')
}

View File

@@ -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<string, ServiceInterfaceWithHostInfo>
'marketplace-url': string | null
'developer-key': string
}
@@ -160,7 +159,6 @@ export interface Manifest extends MarketplaceManifest<DependencyConfig | null> {
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<DependencyConfig | null> {
config: ConfigActions | null
volumes: Record<string, Volume>
'min-os-version': string
interfaces: Record<string, InterfaceDef>
backup: BackupActions
migrations: Migrations | null
actions: Record<string, Action>
@@ -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 {

View File

@@ -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',
)
}
}