mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 20:14:49 +00:00
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:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 = ''
|
||||
|
||||
|
||||
@@ -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)">
|
||||
|
||||
@@ -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':
|
||||
|
||||
Reference in New Issue
Block a user