Frontend changes for 036 (#2554)

* new interfaces and remove tor http warnings

* move sigtermTimeout to stopping main status

* lightning, masked, schemeOverride, invert host-iface relationship

* update for new sdk

* update for latest SDK changes

* Update app-interfaces.page.ts

* Update config.service.ts
This commit is contained in:
Matt Hill
2024-02-23 10:38:50 -07:00
committed by GitHub
parent 3bd7596873
commit 87d6684ca7
25 changed files with 1096 additions and 571 deletions

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