mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-04-04 14:29:45 +00:00
address comments and more
This commit is contained in:
@@ -33,7 +33,7 @@ export default class InitializingPage {
|
||||
private readonly state = inject(StateService)
|
||||
|
||||
readonly progress = toSignal(
|
||||
defer(() => from(this.api.initGetProgress())).pipe(
|
||||
defer(() => from(this.api.initFollowProgress())).pipe(
|
||||
switchMap(({ guid, progress }) =>
|
||||
this.api
|
||||
.openWebsocket$<T.FullProgress>(guid, {})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { CB, CT, T } from '@start9labs/start-sdk'
|
||||
import { CB, CT, T, utils } from '@start9labs/start-sdk'
|
||||
import { TuiDialogOptions } from '@taiga-ui/core'
|
||||
import { TuiConfirmData } from '@taiga-ui/kit'
|
||||
import { NetworkInfo } from 'src/app/services/patch-db/data-model'
|
||||
@@ -53,75 +53,44 @@ export type AddressDetails = {
|
||||
url: string
|
||||
}
|
||||
|
||||
// @TODO Matt these types have change significantly
|
||||
export function getAddresses(serviceInterface: any): {
|
||||
// T.ServiceInterface): {
|
||||
export function getMultihostAddresses(
|
||||
serviceInterface: T.ServiceInterface,
|
||||
host: T.Host,
|
||||
): {
|
||||
clearnet: AddressDetails[]
|
||||
local: AddressDetails[]
|
||||
tor: AddressDetails[]
|
||||
} {
|
||||
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]
|
||||
: []
|
||||
const hostnamesInfo = host.hostnameInfo[addressInfo.internalPort]
|
||||
|
||||
const clearnet: AddressDetails[] = []
|
||||
const local: AddressDetails[] = []
|
||||
const tor: AddressDetails[] = []
|
||||
|
||||
hostnames.forEach((h: any) => {
|
||||
let scheme = ''
|
||||
let port = ''
|
||||
|
||||
if (h.hostname.sslPort) {
|
||||
port = h.hostname.sslPort === 443 ? '' : `:${h.hostname.sslPort}`
|
||||
scheme = addressInfo.bindOptions.addSsl?.scheme
|
||||
? `${addressInfo.bindOptions.addSsl.scheme}://`
|
||||
: ''
|
||||
}
|
||||
|
||||
if (h.hostname.port) {
|
||||
port = h.hostname.port === 80 ? '' : `:${h.hostname.port}`
|
||||
scheme = addressInfo.bindOptions.scheme
|
||||
? `${addressInfo.bindOptions.scheme}://`
|
||||
: ''
|
||||
}
|
||||
|
||||
if (h.kind === 'onion') {
|
||||
tor.push({
|
||||
label: h.hostname.sslPort ? 'HTTPS' : 'HTTP',
|
||||
url: toHref(scheme, username, h.hostname.value, port, suffix),
|
||||
})
|
||||
} else {
|
||||
const hostnameKind = h.hostname.kind
|
||||
|
||||
if (hostnameKind === 'domain') {
|
||||
tor.push({
|
||||
url: toHref(
|
||||
scheme,
|
||||
username,
|
||||
`${h.hostname.subdomain}.${h.hostname.domain}`,
|
||||
port,
|
||||
suffix,
|
||||
),
|
||||
})
|
||||
hostnamesInfo.forEach(hostnameInfo => {
|
||||
utils.addressHostToUrl(addressInfo, hostnameInfo).forEach(url => {
|
||||
// Onion
|
||||
if (hostnameInfo.kind === 'onion') {
|
||||
tor.push({ url })
|
||||
// IP
|
||||
} else {
|
||||
local.push({
|
||||
label:
|
||||
hostnameKind === 'local'
|
||||
? 'Local'
|
||||
: `${h.networkInterfaceId} (${hostnameKind})`,
|
||||
url: toHref(scheme, username, h.hostname.value, port, suffix),
|
||||
})
|
||||
// Domain
|
||||
if (hostnameInfo.hostname.kind === 'domain') {
|
||||
clearnet.push({ url })
|
||||
// Local
|
||||
} else {
|
||||
const hostnameKind = hostnameInfo.hostname.kind
|
||||
local.push({
|
||||
label:
|
||||
hostnameKind === 'local'
|
||||
? 'Local'
|
||||
: `${hostnameInfo.networkInterfaceId} (${hostnameKind})`,
|
||||
url,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
return {
|
||||
@@ -130,13 +99,3 @@ export function getAddresses(serviceInterface: any): {
|
||||
tor,
|
||||
}
|
||||
}
|
||||
|
||||
function toHref(
|
||||
scheme: string,
|
||||
username: string,
|
||||
hostname: string,
|
||||
port: string,
|
||||
suffix: string,
|
||||
): string {
|
||||
return `${scheme}${username}${hostname}${port}${suffix}`
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { inject, Pipe, PipeTransform } from '@angular/core'
|
||||
import { convertAnsi, toLocalIsoString } from '@start9labs/shared'
|
||||
import { convertAnsi, Log, toLocalIsoString } from '@start9labs/shared'
|
||||
import {
|
||||
bufferTime,
|
||||
catchError,
|
||||
@@ -44,7 +44,13 @@ export class LogsPipe implements PipeTransform {
|
||||
),
|
||||
defer(() => followLogs(this.options)).pipe(
|
||||
tap(r => this.logs.setCursor(r.startCursor)),
|
||||
switchMap(r => this.api.openLogsWebsocket$(this.toConfig(r.guid))),
|
||||
switchMap(r =>
|
||||
this.api.openWebsocket$<Log>(r.guid, {
|
||||
openObserver: {
|
||||
next: () => this.logs.status$.next('connected'),
|
||||
},
|
||||
}),
|
||||
),
|
||||
bufferTime(1000),
|
||||
filter(logs => !!logs.length),
|
||||
map(convertAnsi),
|
||||
@@ -67,15 +73,6 @@ export class LogsPipe implements PipeTransform {
|
||||
private get options() {
|
||||
return this.logs.status$.value === 'connected' ? { limit: 400 } : {}
|
||||
}
|
||||
|
||||
private toConfig(guid: string) {
|
||||
return {
|
||||
url: `/rpc/${guid}`,
|
||||
openObserver: {
|
||||
next: () => this.logs.status$.next('connected'),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getMessage(success: boolean): string {
|
||||
|
||||
@@ -3,17 +3,17 @@ import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
||||
import { ActivatedRoute } from '@angular/router'
|
||||
import { getPkgId } from '@start9labs/shared'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { map } from 'rxjs'
|
||||
import { combineLatest, map } from 'rxjs'
|
||||
import { InterfaceComponent } from 'src/app/routes/portal/components/interfaces/interface.component'
|
||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
import { getAddresses } from '../../../components/interfaces/interface.utils'
|
||||
import { getMultihostAddresses } from '../../../components/interfaces/interface.utils'
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<app-interface
|
||||
*ngIf="interfaceInfo$ | async as interfaceInfo"
|
||||
*ngIf="interfacesWithAddresses$ | async as serviceInterface"
|
||||
[packageContext]="context"
|
||||
[serviceInterface]="interfaceInfo"
|
||||
[serviceInterface]="serviceInterface"
|
||||
/>
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
@@ -22,23 +22,25 @@ import { getAddresses } from '../../../components/interfaces/interface.utils'
|
||||
})
|
||||
export class ServiceInterfaceRoute {
|
||||
private readonly route = inject(ActivatedRoute)
|
||||
private readonly patch = inject<PatchDB<DataModel>>(PatchDB)
|
||||
|
||||
readonly context = {
|
||||
packageId: getPkgId(this.route),
|
||||
interfaceId: this.route.snapshot.paramMap.get('interfaceId') || '',
|
||||
}
|
||||
|
||||
readonly interfaceInfo$ = inject<PatchDB<DataModel>>(PatchDB)
|
||||
.watch$(
|
||||
readonly interfacesWithAddresses$ = combineLatest([
|
||||
this.patch.watch$(
|
||||
'packageData',
|
||||
this.context.packageId,
|
||||
'serviceInterfaces',
|
||||
this.context.interfaceId,
|
||||
)
|
||||
.pipe(
|
||||
map(info => ({
|
||||
...info,
|
||||
addresses: getAddresses(info),
|
||||
})),
|
||||
)
|
||||
),
|
||||
this.patch.watch$('packageData', this.context.packageId, 'hosts'),
|
||||
]).pipe(
|
||||
map(([iFace, hosts]) => ({
|
||||
...iFace,
|
||||
addresses: getMultihostAddresses(iFace, hosts[iFace.addressInfo.hostId]),
|
||||
})),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import { BackupType } from '../types/backup-type'
|
||||
export class BackupsStatusComponent {
|
||||
private readonly exver = inject(Exver)
|
||||
|
||||
@Input({ required: true }) serverId!: string
|
||||
@Input({ required: true }) type!: BackupType
|
||||
@Input({ required: true }) target!: BackupTarget
|
||||
|
||||
@@ -61,8 +62,12 @@ export class BackupsStatusComponent {
|
||||
}
|
||||
|
||||
private get hasBackup(): boolean {
|
||||
return !!this.target.startOs
|
||||
// @TODO Matt types changed
|
||||
// && this.exver.compareExver(this.target.startOs.version, '0.3.0') !== -1
|
||||
return (
|
||||
this.target.startOs[this.serverId] &&
|
||||
this.exver.compareOsVersion(
|
||||
this.target.startOs[this.serverId].version,
|
||||
'0.3.6',
|
||||
) !== 'less'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,9 @@ import { BackupsStatusComponent } from '../components/status.component'
|
||||
import { GetDisplayInfoPipe } from '../pipes/get-display-info.pipe'
|
||||
import { BackupType } from '../types/backup-type'
|
||||
import { TARGETS } from './targets.component'
|
||||
import { getServerInfo } from 'src/app/utils/get-server-info'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
@@ -40,7 +43,12 @@ import { TARGETS } from './targets.component'
|
||||
<tui-icon [icon]="displayInfo.icon" />
|
||||
<div>
|
||||
<strong>{{ displayInfo.name }}</strong>
|
||||
<backups-status [type]="context.data.type" [target]="target" />
|
||||
<backups-status
|
||||
[type]="context.data.type"
|
||||
[target]="target"
|
||||
[serverId]="serverId"
|
||||
]
|
||||
/>
|
||||
<div [style.color]="'var(--tui-text-secondary'">
|
||||
{{ displayInfo.description }}
|
||||
<br />
|
||||
@@ -69,6 +77,7 @@ export class BackupsTargetModal {
|
||||
private readonly dialogs = inject(TuiDialogService)
|
||||
private readonly errorService = inject(ErrorService)
|
||||
private readonly api = inject(ApiService)
|
||||
private readonly patch = inject(PatchDB<DataModel>)
|
||||
|
||||
readonly context =
|
||||
inject<TuiDialogContext<BackupTarget, { type: BackupType }>>(
|
||||
@@ -81,10 +90,12 @@ export class BackupsTargetModal {
|
||||
? 'Loading Backup Targets'
|
||||
: 'Loading Backup Sources'
|
||||
|
||||
serverId = ''
|
||||
targets: BackupTarget[] = []
|
||||
|
||||
async ngOnInit() {
|
||||
try {
|
||||
this.serverId = (await getServerInfo(this.patch)).id
|
||||
this.targets = (await this.api.getBackupTargets({})).saved
|
||||
} catch (e: any) {
|
||||
this.errorService.handleError(e)
|
||||
|
||||
@@ -1,18 +1,32 @@
|
||||
import { inject, Injectable } from '@angular/core'
|
||||
import { Observable, retry, shareReplay } from 'rxjs'
|
||||
import { Metrics } from 'src/app/services/api/api.types'
|
||||
import {
|
||||
defer,
|
||||
Observable,
|
||||
retry,
|
||||
shareReplay,
|
||||
startWith,
|
||||
switchMap,
|
||||
} from 'rxjs'
|
||||
import { ServerMetrics } from 'src/app/services/api/api.types'
|
||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class MetricsService extends Observable<Metrics> {
|
||||
// @TODO get real url, 'rpc/{guid}' or something like that
|
||||
private readonly metrics$ = inject(ApiService)
|
||||
.openMetricsWebsocket$({
|
||||
url: '',
|
||||
})
|
||||
.pipe(retry(), shareReplay(1))
|
||||
export class MetricsService extends Observable<ServerMetrics> {
|
||||
private readonly api = inject(ApiService)
|
||||
|
||||
// @TODO Alex do we need to use defer? I am unsure when this is necessary.
|
||||
private readonly metrics$ = defer(() =>
|
||||
this.api.followServerMetrics({}),
|
||||
).pipe(
|
||||
switchMap(({ guid, metrics }) =>
|
||||
this.api.openWebsocket$<ServerMetrics>(guid).pipe(startWith(metrics)),
|
||||
),
|
||||
// @TODO Alex how to handle failure and reconnection here? Simple retry() will not work. Seems like we need a general solution for reconnecting websockets: patchDB, logs, metrics, progress, and any future. Reconnection should depend on server state, then we need to get a new guid, then reconnect. Similar to how patchDB websocket currently behaves on disconnect/reconnect.
|
||||
retry(),
|
||||
shareReplay(),
|
||||
)
|
||||
|
||||
constructor() {
|
||||
super(subscriber => this.metrics$.subscribe(subscriber))
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
InterfaceComponent,
|
||||
ServiceInterfaceWithAddresses,
|
||||
} from 'src/app/routes/portal/components/interfaces/interface.component'
|
||||
import { getAddresses } from 'src/app/routes/portal/components/interfaces/interface.utils'
|
||||
import { getMultihostAddresses } from 'src/app/routes/portal/components/interfaces/interface.utils'
|
||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
|
||||
@Component({
|
||||
@@ -29,7 +29,6 @@ export class StartOsUiComponent {
|
||||
.watch$('serverInfo', 'ui')
|
||||
.pipe(
|
||||
map(hosts => {
|
||||
// @TODO Matt fix types
|
||||
const serviceInterface: T.ServiceInterface = {
|
||||
id: 'startos-ui',
|
||||
name: 'StartOS UI',
|
||||
@@ -41,31 +40,26 @@ export class StartOsUiComponent {
|
||||
addressInfo: {
|
||||
hostId: '',
|
||||
username: null,
|
||||
internalPort: 80,
|
||||
scheme: 'http',
|
||||
sslScheme: 'https',
|
||||
suffix: '',
|
||||
bindOptions: {
|
||||
scheme: 'http',
|
||||
preferredExternalPort: 80,
|
||||
addSsl: {
|
||||
scheme: 'https',
|
||||
preferredExternalPort: 443,
|
||||
// @TODO is this alpn correct?
|
||||
alpn: { specified: ['http/1.1', 'h2'] },
|
||||
},
|
||||
secure: {
|
||||
ssl: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
hostInfo: {
|
||||
id: 'start-os-ui-host',
|
||||
kind: 'multi',
|
||||
hostnames: hosts,
|
||||
}
|
||||
|
||||
// @TODO Aiden confirm this is correct
|
||||
const host: T.Host = {
|
||||
kind: 'multi',
|
||||
bindings: {},
|
||||
hostnameInfo: {
|
||||
80: hosts,
|
||||
},
|
||||
} as any
|
||||
addresses: [],
|
||||
}
|
||||
|
||||
return {
|
||||
...serviceInterface,
|
||||
addresses: getAddresses(serviceInterface),
|
||||
addresses: getMultihostAddresses(serviceInterface, host),
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user