address comments and more

This commit is contained in:
Matt Hill
2024-08-15 08:05:37 -06:00
parent a730543c76
commit 015131f198
24 changed files with 316 additions and 396 deletions

17
web/package-lock.json generated
View File

@@ -5254,23 +5254,6 @@
"rxjs": ">=6.0.0" "rxjs": ">=6.0.0"
} }
}, },
"node_modules/@taiga-ui/experimental": {
"version": "4.0.0-rc.7",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.6.3"
},
"peerDependencies": {
"@angular/common": ">=16.0.0",
"@angular/core": ">=16.0.0",
"@taiga-ui/addon-commerce": "^4.0.0-rc.7",
"@taiga-ui/cdk": "^4.0.0-rc.7",
"@taiga-ui/core": "^4.0.0-rc.7",
"@taiga-ui/kit": "^4.0.0-rc.7",
"@taiga-ui/polymorpheus": "^4.6.4",
"rxjs": ">=7.0.0"
}
},
"node_modules/@taiga-ui/i18n": { "node_modules/@taiga-ui/i18n": {
"version": "4.0.0-rc.7", "version": "4.0.0-rc.7",
"license": "Apache-2.0", "license": "Apache-2.0",

View File

@@ -14,7 +14,7 @@ import { PolymorpheusContent } from '@taiga-ui/polymorpheus'
@Component({ @Component({
selector: 'marketplace-package-screenshots', selector: 'marketplace-package-screenshots',
template: ` template: `
<!--@TODO Matt or Lucy?--> <!--@TODO future release-->
<div <div
*ngIf="$any(pkg).screenshots as screenshots" *ngIf="$any(pkg).screenshots as screenshots"
tuiCarouselButtons tuiCarouselButtons

View File

@@ -40,7 +40,7 @@ export default class LoadingPage {
filter(Boolean), filter(Boolean),
take(1), take(1),
switchMap(({ guid, progress }) => switchMap(({ guid, progress }) =>
this.api.openProgressWebsocket$(guid).pipe( this.api.openWebsocket$<T.FullProgress>(guid).pipe(
startWith(progress), startWith(progress),
catchError((_, watch$) => catchError((_, watch$) =>
interval(2000).pipe( interval(2000).pipe(

View File

@@ -2,9 +2,7 @@ import * as jose from 'node-jose'
import { import {
DiskInfo, DiskInfo,
DiskListResponse, DiskListResponse,
FollowLogsReq,
FollowLogsRes, FollowLogsRes,
Log,
PartitionInfo, PartitionInfo,
StartOSDiskInfo, StartOSDiskInfo,
} from '@start9labs/shared' } from '@start9labs/shared'
@@ -25,11 +23,8 @@ export abstract class ApiService {
abstract execute(setupInfo: T.SetupExecuteParams): Promise<T.SetupProgress> // setup.execute abstract execute(setupInfo: T.SetupExecuteParams): Promise<T.SetupProgress> // setup.execute
abstract complete(): Promise<T.SetupResult> // setup.complete abstract complete(): Promise<T.SetupResult> // setup.complete
abstract exit(): Promise<void> // setup.exit abstract exit(): Promise<void> // setup.exit
abstract followServerLogs(params: FollowLogsReq): Promise<FollowLogsRes> // setup.logs.follow abstract followServerLogs(): Promise<FollowLogsRes> // setup.logs.follow
abstract openLogsWebsocket$( abstract openWebsocket$<T>(guid: string): Observable<T>
config: WebSocketSubjectConfig<Log>,
): Observable<Log>
abstract openProgressWebsocket$(guid: string): Observable<T.FullProgress>
async encrypt(toEncrypt: string): Promise<T.EncryptedWire> { async encrypt(toEncrypt: string): Promise<T.EncryptedWire> {
if (!this.pubkey) throw new Error('No pubkey found!') if (!this.pubkey) throw new Error('No pubkey found!')

View File

@@ -3,11 +3,9 @@ import { Inject, Injectable } from '@angular/core'
import { import {
DiskListResponse, DiskListResponse,
encodeBase64, encodeBase64,
FollowLogsReq,
FollowLogsRes, FollowLogsRes,
HttpService, HttpService,
isRpcError, isRpcError,
Log,
RpcError, RpcError,
RPCOptions, RPCOptions,
StartOSDiskInfo, StartOSDiskInfo,
@@ -15,7 +13,7 @@ import {
import { T } from '@start9labs/start-sdk' import { T } from '@start9labs/start-sdk'
import * as jose from 'node-jose' import * as jose from 'node-jose'
import { Observable } from 'rxjs' import { Observable } from 'rxjs'
import { webSocket, WebSocketSubjectConfig } from 'rxjs/webSocket' import { webSocket } from 'rxjs/webSocket'
import { ApiService } from './api.service' import { ApiService } from './api.service'
@Injectable({ @Injectable({
@@ -29,12 +27,13 @@ export class LiveApiService extends ApiService {
super() super()
} }
openProgressWebsocket$(guid: string): Observable<T.FullProgress> { openWebsocket$<T>(guid: string): Observable<T> {
const { location } = this.document.defaultView! const { location } = this.document.defaultView!
const protocol = location.protocol === 'http:' ? 'ws' : 'wss'
const host = location.host const host = location.host
return webSocket({ return webSocket({
url: `ws://${host}/ws/rpc/${guid}`, url: `${protocol}://${host}/ws/rpc/${guid}`,
}) })
} }
@@ -99,12 +98,8 @@ export class LiveApiService extends ApiService {
}) })
} }
async followServerLogs(params: FollowLogsReq): Promise<FollowLogsRes> { async followServerLogs(): Promise<FollowLogsRes> {
return this.rpcRequest({ method: 'setup.logs.follow', params }) return this.rpcRequest({ method: 'setup.logs.follow', params: {} })
}
openLogsWebsocket$({ url }: WebSocketSubjectConfig<Log>): Observable<Log> {
return webSocket(`http://start.local/ws/${url}`)
} }
async complete(): Promise<T.SetupResult> { async complete(): Promise<T.SetupResult> {

View File

@@ -2,16 +2,13 @@ import { Injectable } from '@angular/core'
import { import {
DiskListResponse, DiskListResponse,
encodeBase64, encodeBase64,
FollowLogsReq,
FollowLogsRes, FollowLogsRes,
Log,
pauseFor, pauseFor,
StartOSDiskInfo, StartOSDiskInfo,
} from '@start9labs/shared' } from '@start9labs/shared'
import { T } from '@start9labs/start-sdk' import { T } from '@start9labs/start-sdk'
import * as jose from 'node-jose' import * as jose from 'node-jose'
import { interval, map, Observable, of } from 'rxjs' import { interval, map, Observable } from 'rxjs'
import { WebSocketSubjectConfig } from 'rxjs/webSocket'
import { ApiService } from './api.service' import { ApiService } from './api.service'
@Injectable({ @Injectable({
@@ -47,68 +44,102 @@ export class MockApiService extends ApiService {
// websocket // websocket
openProgressWebsocket$(guid: string): Observable<T.FullProgress> { // oldMockProgress$(): Promise<T.FullProgress> {
return of(PROGRESS) // const numPhases = PROGRESS.phases.length
// const numPhases = PROGRESS.phases.length
// return of(PROGRESS).pipe( // return of(PROGRESS).pipe(
// switchMap(full => // switchMap(full =>
// from(PROGRESS.phases).pipe( // from(PROGRESS.phases).pipe(
// mergeScan((full, phase, i) => { // mergeScan((full, phase, i) => {
// if ( // if (
// !phase.progress || // !phase.progress ||
// typeof phase.progress !== 'object' || // typeof phase.progress !== 'object' ||
// !phase.progress.total // !phase.progress.total
// ) { // ) {
// full.phases[i].progress = true // full.phases[i].progress = true
// if ( // if (
// full.overall && // full.overall &&
// typeof full.overall === 'object' && // typeof full.overall === 'object' &&
// full.overall.total // full.overall.total
// ) { // ) {
// const step = full.overall.total / numPhases // const step = full.overall.total / numPhases
// full.overall.done += step // full.overall.done += step
// } // }
// return of(full).pipe(delay(2000)) // return of(full).pipe(delay(2000))
// } else { // } else {
// const total = phase.progress.total // const total = phase.progress.total
// const step = total / 4 // const step = total / 4
// let done = phase.progress.done // let done = phase.progress.done
// return interval(1000).pipe( // return interval(1000).pipe(
// takeWhile(() => done < total), // takeWhile(() => done < total),
// map(() => { // map(() => {
// done += step // done += step
// console.error(done) // console.error(done)
// if ( // if (
// full.overall && // full.overall &&
// typeof full.overall === 'object' && // typeof full.overall === 'object' &&
// full.overall.total // full.overall.total
// ) { // ) {
// const step = full.overall.total / numPhases / 4 // const step = full.overall.total / numPhases / 4
// full.overall.done += step // full.overall.done += step
// } // }
// if (done === total) { // if (done === total) {
// full.phases[i].progress = true // full.phases[i].progress = true
// if (i === numPhases - 1) { // if (i === numPhases - 1) {
// full.overall = true // full.overall = true
// } // }
// } // }
// return full // return full
// }), // }),
// ) // )
// } // }
// }, full), // }, full),
// ), // ),
// ), // ),
// ) // )
// }
openWebsocket$<T>(guid: string): Observable<T> {
if (guid === 'logs-guid') {
return interval(500).pipe(
map(() => ({
timestamp: new Date().toISOString(),
message: 'fake log entry',
bootId: 'boot-id',
})),
) as Observable<T>
} else if (guid === 'progress-guid') {
// @TODO mock progress
return interval(1000).pipe(
map(() => ({
overall: true,
phases: [
{
name: 'Preparing Data',
progress: true,
},
{
name: 'Transferring Data',
progress: true,
},
{
name: 'Finalizing Setup',
progress: true,
},
],
})),
) as Observable<T>
} else {
throw new Error('invalid guid type')
}
} }
private statusIndex = 0 private statusIndex = 0
@@ -270,24 +301,14 @@ export class MockApiService extends ApiService {
} }
} }
async followServerLogs(params: FollowLogsReq): Promise<FollowLogsRes> { async followServerLogs(): Promise<FollowLogsRes> {
await pauseFor(1000) await pauseFor(1000)
return { return {
startCursor: 'fakestartcursor', startCursor: 'fakestartcursor',
guid: 'fake-guid', guid: 'logs-guid',
} }
} }
openLogsWebsocket$(config: WebSocketSubjectConfig<Log>): Observable<Log> {
return interval(500).pipe(
map(() => ({
timestamp: new Date().toISOString(),
message: 'fake log entry',
bootId: 'boot-id',
})),
)
}
async complete(): Promise<T.SetupResult> { async complete(): Promise<T.SetupResult> {
await pauseFor(1000) await pauseFor(1000)
return { return {

View File

@@ -1,13 +1,12 @@
import { StaticClassProvider } from '@angular/core' import { StaticClassProvider } from '@angular/core'
import { bufferTime, defer, map, Observable, scan, switchMap } from 'rxjs' import { bufferTime, defer, map, Observable, scan, switchMap } from 'rxjs'
import { WebSocketSubjectConfig } from 'rxjs/webSocket'
import { FollowLogsReq, FollowLogsRes, Log } from '../types/api' import { FollowLogsReq, FollowLogsRes, Log } from '../types/api'
import { Constructor } from '../types/constructor' import { Constructor } from '../types/constructor'
import { convertAnsi } from '../util/convert-ansi' import { convertAnsi } from '../util/convert-ansi'
interface Api { interface Api {
followServerLogs: (params: FollowLogsReq) => Promise<FollowLogsRes> followServerLogs: (params: FollowLogsReq) => Promise<FollowLogsRes>
openLogsWebsocket$: (config: WebSocketSubjectConfig<Log>) => Observable<Log> openWebsocket$: (guid: string) => Observable<Log>
} }
export function provideSetupLogsService( export function provideSetupLogsService(
@@ -22,9 +21,7 @@ export function provideSetupLogsService(
export class SetupLogsService extends Observable<readonly string[]> { export class SetupLogsService extends Observable<readonly string[]> {
private readonly log$ = defer(() => this.api.followServerLogs({})).pipe( private readonly log$ = defer(() => this.api.followServerLogs({})).pipe(
switchMap(({ guid }) => switchMap(({ guid }) => this.api.openWebsocket$(guid)),
this.api.openLogsWebsocket$({ url: `/rpc/${guid}` }),
),
bufferTime(1000), bufferTime(1000),
map(convertAnsi), map(convertAnsi),
scan((logs: readonly string[], log) => [...logs, log], []), scan((logs: readonly string[], log) => [...logs, log], []),

View File

@@ -1,4 +1,4 @@
// @TODO Matt this is T.FullProgress but shared does not depend on sdk // @TODO get types from sdk
type Progress = null | boolean | { done: number; total: number | null } type Progress = null | boolean | { done: number; total: number | null }
type NamedProgress = { name: string; progress: Progress } type NamedProgress = { name: string; progress: Progress }
type FullProgress = { overall: Progress; phases: Array<NamedProgress> } type FullProgress = { overall: Progress; phases: Array<NamedProgress> }

View File

@@ -33,7 +33,7 @@ export default class InitializingPage {
private readonly state = inject(StateService) private readonly state = inject(StateService)
readonly progress = toSignal( readonly progress = toSignal(
defer(() => from(this.api.initGetProgress())).pipe( defer(() => from(this.api.initFollowProgress())).pipe(
switchMap(({ guid, progress }) => switchMap(({ guid, progress }) =>
this.api this.api
.openWebsocket$<T.FullProgress>(guid, {}) .openWebsocket$<T.FullProgress>(guid, {})

View File

@@ -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 { TuiDialogOptions } from '@taiga-ui/core'
import { TuiConfirmData } from '@taiga-ui/kit' import { TuiConfirmData } from '@taiga-ui/kit'
import { NetworkInfo } from 'src/app/services/patch-db/data-model' import { NetworkInfo } from 'src/app/services/patch-db/data-model'
@@ -53,75 +53,44 @@ export type AddressDetails = {
url: string url: string
} }
// @TODO Matt these types have change significantly export function getMultihostAddresses(
export function getAddresses(serviceInterface: any): { serviceInterface: T.ServiceInterface,
// T.ServiceInterface): { host: T.Host,
): {
clearnet: AddressDetails[] clearnet: AddressDetails[]
local: AddressDetails[] local: AddressDetails[]
tor: AddressDetails[] tor: AddressDetails[]
} { } {
const host = serviceInterface.hostInfo
const addressInfo = serviceInterface.addressInfo const addressInfo = serviceInterface.addressInfo
const username = addressInfo.username ? addressInfo.username + '@' : '' const hostnamesInfo = host.hostnameInfo[addressInfo.internalPort]
const suffix = addressInfo.suffix || ''
const hostnames =
host.kind === 'multi'
? host.hostnames
: host.hostname
? [host.hostname]
: []
const clearnet: AddressDetails[] = [] const clearnet: AddressDetails[] = []
const local: AddressDetails[] = [] const local: AddressDetails[] = []
const tor: AddressDetails[] = [] const tor: AddressDetails[] = []
hostnames.forEach((h: any) => { hostnamesInfo.forEach(hostnameInfo => {
let scheme = '' utils.addressHostToUrl(addressInfo, hostnameInfo).forEach(url => {
let port = '' // Onion
if (hostnameInfo.kind === 'onion') {
if (h.hostname.sslPort) { tor.push({ url })
port = h.hostname.sslPort === 443 ? '' : `:${h.hostname.sslPort}` // IP
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,
),
})
} else { } else {
local.push({ // Domain
label: if (hostnameInfo.hostname.kind === 'domain') {
hostnameKind === 'local' clearnet.push({ url })
? 'Local' // Local
: `${h.networkInterfaceId} (${hostnameKind})`, } else {
url: toHref(scheme, username, h.hostname.value, port, suffix), const hostnameKind = hostnameInfo.hostname.kind
}) local.push({
label:
hostnameKind === 'local'
? 'Local'
: `${hostnameInfo.networkInterfaceId} (${hostnameKind})`,
url,
})
}
} }
} })
}) })
return { return {
@@ -130,13 +99,3 @@ export function getAddresses(serviceInterface: any): {
tor, tor,
} }
} }
function toHref(
scheme: string,
username: string,
hostname: string,
port: string,
suffix: string,
): string {
return `${scheme}${username}${hostname}${port}${suffix}`
}

View File

@@ -1,5 +1,5 @@
import { inject, Pipe, PipeTransform } from '@angular/core' import { inject, Pipe, PipeTransform } from '@angular/core'
import { convertAnsi, toLocalIsoString } from '@start9labs/shared' import { convertAnsi, Log, toLocalIsoString } from '@start9labs/shared'
import { import {
bufferTime, bufferTime,
catchError, catchError,
@@ -44,7 +44,13 @@ export class LogsPipe implements PipeTransform {
), ),
defer(() => followLogs(this.options)).pipe( defer(() => followLogs(this.options)).pipe(
tap(r => this.logs.setCursor(r.startCursor)), 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), bufferTime(1000),
filter(logs => !!logs.length), filter(logs => !!logs.length),
map(convertAnsi), map(convertAnsi),
@@ -67,15 +73,6 @@ export class LogsPipe implements PipeTransform {
private get options() { private get options() {
return this.logs.status$.value === 'connected' ? { limit: 400 } : {} 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 { function getMessage(success: boolean): string {

View File

@@ -3,17 +3,17 @@ import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
import { ActivatedRoute } from '@angular/router' import { ActivatedRoute } from '@angular/router'
import { getPkgId } from '@start9labs/shared' import { getPkgId } from '@start9labs/shared'
import { PatchDB } from 'patch-db-client' 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 { InterfaceComponent } from 'src/app/routes/portal/components/interfaces/interface.component'
import { DataModel } from 'src/app/services/patch-db/data-model' 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({ @Component({
template: ` template: `
<app-interface <app-interface
*ngIf="interfaceInfo$ | async as interfaceInfo" *ngIf="interfacesWithAddresses$ | async as serviceInterface"
[packageContext]="context" [packageContext]="context"
[serviceInterface]="interfaceInfo" [serviceInterface]="serviceInterface"
/> />
`, `,
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
@@ -22,23 +22,25 @@ import { getAddresses } from '../../../components/interfaces/interface.utils'
}) })
export class ServiceInterfaceRoute { export class ServiceInterfaceRoute {
private readonly route = inject(ActivatedRoute) private readonly route = inject(ActivatedRoute)
private readonly patch = inject<PatchDB<DataModel>>(PatchDB)
readonly context = { readonly context = {
packageId: getPkgId(this.route), packageId: getPkgId(this.route),
interfaceId: this.route.snapshot.paramMap.get('interfaceId') || '', interfaceId: this.route.snapshot.paramMap.get('interfaceId') || '',
} }
readonly interfaceInfo$ = inject<PatchDB<DataModel>>(PatchDB) readonly interfacesWithAddresses$ = combineLatest([
.watch$( this.patch.watch$(
'packageData', 'packageData',
this.context.packageId, this.context.packageId,
'serviceInterfaces', 'serviceInterfaces',
this.context.interfaceId, this.context.interfaceId,
) ),
.pipe( this.patch.watch$('packageData', this.context.packageId, 'hosts'),
map(info => ({ ]).pipe(
...info, map(([iFace, hosts]) => ({
addresses: getAddresses(info), ...iFace,
})), addresses: getMultihostAddresses(iFace, hosts[iFace.addressInfo.hostId]),
) })),
)
} }

View File

@@ -23,6 +23,7 @@ import { BackupType } from '../types/backup-type'
export class BackupsStatusComponent { export class BackupsStatusComponent {
private readonly exver = inject(Exver) private readonly exver = inject(Exver)
@Input({ required: true }) serverId!: string
@Input({ required: true }) type!: BackupType @Input({ required: true }) type!: BackupType
@Input({ required: true }) target!: BackupTarget @Input({ required: true }) target!: BackupTarget
@@ -61,8 +62,12 @@ export class BackupsStatusComponent {
} }
private get hasBackup(): boolean { private get hasBackup(): boolean {
return !!this.target.startOs return (
// @TODO Matt types changed this.target.startOs[this.serverId] &&
// && this.exver.compareExver(this.target.startOs.version, '0.3.0') !== -1 this.exver.compareOsVersion(
this.target.startOs[this.serverId].version,
'0.3.6',
) !== 'less'
)
} }
} }

View File

@@ -23,6 +23,9 @@ import { BackupsStatusComponent } from '../components/status.component'
import { GetDisplayInfoPipe } from '../pipes/get-display-info.pipe' import { GetDisplayInfoPipe } from '../pipes/get-display-info.pipe'
import { BackupType } from '../types/backup-type' import { BackupType } from '../types/backup-type'
import { TARGETS } from './targets.component' 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({ @Component({
template: ` template: `
@@ -40,7 +43,12 @@ import { TARGETS } from './targets.component'
<tui-icon [icon]="displayInfo.icon" /> <tui-icon [icon]="displayInfo.icon" />
<div> <div>
<strong>{{ displayInfo.name }}</strong> <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'"> <div [style.color]="'var(--tui-text-secondary'">
{{ displayInfo.description }} {{ displayInfo.description }}
<br /> <br />
@@ -69,6 +77,7 @@ export class BackupsTargetModal {
private readonly dialogs = inject(TuiDialogService) private readonly dialogs = inject(TuiDialogService)
private readonly errorService = inject(ErrorService) private readonly errorService = inject(ErrorService)
private readonly api = inject(ApiService) private readonly api = inject(ApiService)
private readonly patch = inject(PatchDB<DataModel>)
readonly context = readonly context =
inject<TuiDialogContext<BackupTarget, { type: BackupType }>>( inject<TuiDialogContext<BackupTarget, { type: BackupType }>>(
@@ -81,10 +90,12 @@ export class BackupsTargetModal {
? 'Loading Backup Targets' ? 'Loading Backup Targets'
: 'Loading Backup Sources' : 'Loading Backup Sources'
serverId = ''
targets: BackupTarget[] = [] targets: BackupTarget[] = []
async ngOnInit() { async ngOnInit() {
try { try {
this.serverId = (await getServerInfo(this.patch)).id
this.targets = (await this.api.getBackupTargets({})).saved this.targets = (await this.api.getBackupTargets({})).saved
} catch (e: any) { } catch (e: any) {
this.errorService.handleError(e) this.errorService.handleError(e)

View File

@@ -1,18 +1,32 @@
import { inject, Injectable } from '@angular/core' import { inject, Injectable } from '@angular/core'
import { Observable, retry, shareReplay } from 'rxjs' import {
import { Metrics } from 'src/app/services/api/api.types' 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' import { ApiService } from 'src/app/services/api/embassy-api.service'
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
}) })
export class MetricsService extends Observable<Metrics> { export class MetricsService extends Observable<ServerMetrics> {
// @TODO get real url, 'rpc/{guid}' or something like that private readonly api = inject(ApiService)
private readonly metrics$ = inject(ApiService)
.openMetricsWebsocket$({ // @TODO Alex do we need to use defer? I am unsure when this is necessary.
url: '', private readonly metrics$ = defer(() =>
}) this.api.followServerMetrics({}),
.pipe(retry(), shareReplay(1)) ).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() { constructor() {
super(subscriber => this.metrics$.subscribe(subscriber)) super(subscriber => this.metrics$.subscribe(subscriber))

View File

@@ -7,7 +7,7 @@ import {
InterfaceComponent, InterfaceComponent,
ServiceInterfaceWithAddresses, ServiceInterfaceWithAddresses,
} from 'src/app/routes/portal/components/interfaces/interface.component' } 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' import { DataModel } from 'src/app/services/patch-db/data-model'
@Component({ @Component({
@@ -29,7 +29,6 @@ export class StartOsUiComponent {
.watch$('serverInfo', 'ui') .watch$('serverInfo', 'ui')
.pipe( .pipe(
map(hosts => { map(hosts => {
// @TODO Matt fix types
const serviceInterface: T.ServiceInterface = { const serviceInterface: T.ServiceInterface = {
id: 'startos-ui', id: 'startos-ui',
name: 'StartOS UI', name: 'StartOS UI',
@@ -41,31 +40,26 @@ export class StartOsUiComponent {
addressInfo: { addressInfo: {
hostId: '', hostId: '',
username: null, username: null,
internalPort: 80,
scheme: 'http',
sslScheme: 'https',
suffix: '', 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', // @TODO Aiden confirm this is correct
hostnames: hosts, const host: T.Host = {
kind: 'multi',
bindings: {},
hostnameInfo: {
80: hosts,
}, },
} as any addresses: [],
}
return { return {
...serviceInterface, ...serviceInterface,
addresses: getAddresses(serviceInterface), addresses: getMultihostAddresses(serviceInterface, host),
} }
}), }),
) )

View File

@@ -3,12 +3,7 @@ import {
PackageDataEntry, PackageDataEntry,
ServerStatusInfo, ServerStatusInfo,
} from 'src/app/services/patch-db/data-model' } from 'src/app/services/patch-db/data-model'
import { import { RR, ServerMetrics, ServerNotifications } from './api.types'
Metrics,
NotificationLevel,
RR,
ServerNotifications,
} from './api.types'
import { BTC_ICON, LND_ICON, PROXY_ICON, REGISTRY_ICON } from './api-icons' import { BTC_ICON, LND_ICON, PROXY_ICON, REGISTRY_ICON } from './api-icons'
import { Log } from '@start9labs/shared' import { Log } from '@start9labs/shared'
import { configBuilderToSpec } from 'src/app/utils/configBuilderToSpec' import { configBuilderToSpec } from 'src/app/utils/configBuilderToSpec'
@@ -748,7 +743,7 @@ export module Mock {
packageId: null, packageId: null,
createdAt: '2019-12-26T14:20:30.872Z', createdAt: '2019-12-26T14:20:30.872Z',
code: 1, code: 1,
level: NotificationLevel.Success, level: 'success',
title: 'Backup Complete', title: 'Backup Complete',
message: 'StartOS and services have been successfully backed up.', message: 'StartOS and services have been successfully backed up.',
data: { data: {
@@ -769,7 +764,7 @@ export module Mock {
packageId: null, packageId: null,
createdAt: '2019-12-26T14:20:30.872Z', createdAt: '2019-12-26T14:20:30.872Z',
code: 2, code: 2,
level: NotificationLevel.Warning, level: 'warning',
title: 'SSH Key Added', title: 'SSH Key Added',
message: 'A new SSH key was added. If you did not do this, shit is bad.', message: 'A new SSH key was added. If you did not do this, shit is bad.',
data: null, data: null,
@@ -780,7 +775,7 @@ export module Mock {
packageId: null, packageId: null,
createdAt: '2019-12-26T14:20:30.872Z', createdAt: '2019-12-26T14:20:30.872Z',
code: 3, code: 3,
level: NotificationLevel.Info, level: 'info',
title: 'SSH Key Removed', title: 'SSH Key Removed',
message: 'A SSH key was removed.', message: 'A SSH key was removed.',
data: null, data: null,
@@ -791,7 +786,7 @@ export module Mock {
packageId: 'bitcoind', packageId: 'bitcoind',
createdAt: '2019-12-26T14:20:30.872Z', createdAt: '2019-12-26T14:20:30.872Z',
code: 4, code: 4,
level: NotificationLevel.Error, level: 'error',
title: 'Service Crashed', title: 'Service Crashed',
message: new Array(3) message: new Array(3)
.fill( .fill(
@@ -806,7 +801,7 @@ export module Mock {
}, },
] ]
export function getMetrics(): Metrics { export function getMetrics(): ServerMetrics {
return { return {
general: { general: {
temperature: { temperature: {
@@ -1020,8 +1015,16 @@ export module Mock {
path: '/Desktop/embassy-backups', path: '/Desktop/embassy-backups',
username: 'TestUser', username: 'TestUser',
mountable: false, mountable: false,
// @TODO Matt Provide mock for startOs startOs: {
startOs: {}, abcdefgh: {
hostname: 'adjective-noun.local',
version: '0.3.6',
timestamp: new Date().toISOString(),
passwordHash:
'$argon2d$v=19$m=1024,t=1,p=1$YXNkZmFzZGZhc2RmYXNkZg$Ceev1I901G6UwU+hY0sHrFZ56D+o+LNJ',
wrappedKey: '',
},
},
}, },
{ {
id: 'ftcvewdnkemfksdm', id: 'ftcvewdnkemfksdm',
@@ -1054,8 +1057,16 @@ export module Mock {
vendor: 'SSK', vendor: 'SSK',
mountable: true, mountable: true,
path: '/HomeFolder/Documents', path: '/HomeFolder/Documents',
// @TODO Matt Provide mock for startOs startOs: {
startOs: {}, 'different-server': {
hostname: 'different-server.local',
version: '0.3.6',
timestamp: new Date().toISOString(),
passwordHash:
'$argon2d$v=19$m=1024,t=1,p=1$YXNkZmFzZGZhc2RmYXNkZg$Ceev1I901G6UwU+hY0sHrFZ56D+o+LNJ',
wrappedKey: '',
},
},
}, },
], ],
} }

View File

@@ -61,7 +61,7 @@ export module RR {
// init // init
export type InitGetProgressRes = { export type InitFollowProgressRes = {
progress: T.FullProgress progress: T.FullProgress
guid: string guid: string
} }
@@ -88,10 +88,10 @@ export module RR {
guid: string guid: string
} }
export type GetServerMetricsReq = {} // server.metrics export type FollowServerMetricsReq = {} // server.metrics.follow
export type GetServerMetricsRes = { export type FollowServerMetricsRes = {
guid: string guid: string
metrics: Metrics metrics: ServerMetrics
} }
export type UpdateServerReq = { registry: string } // server.update export type UpdateServerReq = { registry: string } // server.update
@@ -339,9 +339,11 @@ export module RR {
export type FollowPackageLogsReq = FollowServerLogsReq & { id: string } // package.logs.follow export type FollowPackageLogsReq = FollowServerLogsReq & { id: string } // package.logs.follow
export type FollowPackageLogsRes = FollowServerLogsRes export type FollowPackageLogsRes = FollowServerLogsRes
export type GetPackageMetricsReq = { id: string } // package.metrics export type FollowPackageMetricsReq = { id: string } // package.metrics.follow
// @TODO Matt create package metrics type export type FollowPackageMetricsRes = {
export type GetPackageMetricsRes = any guid: string
metrics: AppMetrics
}
export type InstallPackageReq = T.InstallParams export type InstallPackageReq = T.InstallParams
export type InstallPackageRes = null export type InstallPackageRes = null
@@ -415,25 +417,6 @@ export module RR {
} // package.proxy.set-outbound } // package.proxy.set-outbound
export type SetServiceOutboundProxyRes = null export type SetServiceOutboundProxyRes = null
// marketplace
export type GetMarketplaceInfoReq = { serverId: string }
// @TODO Matt fix type
export type GetMarketplaceInfoRes = any
export type GetMarketplaceEosReq = { serverId: string }
// @TODO Matt fix type
export type GetMarketplaceEosRes = any
export type GetMarketplacePackagesReq = {
ids?: { id: string; version: string }[]
// iff !ids
category?: string
query?: string
page?: number
perPage?: number
}
// registry // registry
/** these are returned in ASCENDING order. the newest available version will be the LAST in the object */ /** these are returned in ASCENDING order. the newest available version will be the LAST in the object */
@@ -443,34 +426,34 @@ export module RR {
export type CheckOSUpdateRes = OSUpdate export type CheckOSUpdateRes = OSUpdate
} }
export interface OSUpdate { export type OSUpdate = {
version: string version: string
headline: string headline: string
releaseNotes: { [version: string]: string } releaseNotes: { [version: string]: string }
} }
export interface Breakages { export type Breakages = {
[id: string]: TaggedDependencyError [id: string]: TaggedDependencyError
} }
export interface TaggedDependencyError { export type TaggedDependencyError = {
dependency: string dependency: string
error: DependencyError error: DependencyError
} }
export interface ActionResponse { export type ActionResponse = {
message: string message: string
value: string | null value: string | null
copyable: boolean copyable: boolean
qr: boolean qr: boolean
} }
interface MetricData { type MetricData = {
value: string value: string
unit: string unit: string
} }
export interface Metrics { export type ServerMetrics = {
general: { general: {
temperature: MetricData | null temperature: MetricData | null
} }
@@ -498,14 +481,28 @@ export interface Metrics {
} }
} }
export interface Session { export type AppMetrics = {
memory: {
percentageUsed: MetricData
used: MetricData
}
cpu: {
percentageUsed: MetricData
}
disk: {
percentageUsed: MetricData
used: MetricData
}
}
export type Session = {
loggedIn: string loggedIn: string
lastActive: string lastActive: string
userAgent: string userAgent: string
metadata: SessionMetadata metadata: SessionMetadata
} }
export interface SessionMetadata { export type SessionMetadata = {
platforms: PlatformType[] platforms: PlatformType[]
} }
@@ -565,7 +562,7 @@ export interface CloudBackupTarget extends BaseBackupTarget {
provider: 'dropbox' | 'google-drive' provider: 'dropbox' | 'google-drive'
} }
export interface BackupRun { export type BackupRun = {
id: string id: string
startedAt: string startedAt: string
completedAt: string completedAt: string
@@ -574,7 +571,7 @@ export interface BackupRun {
report: BackupReport report: BackupReport
} }
export interface BackupJob { export type BackupJob = {
id: string id: string
name: string name: string
target: BackupTarget target: BackupTarget
@@ -582,7 +579,7 @@ export interface BackupJob {
packageIds: string[] packageIds: string[]
} }
export interface BackupInfo { export type BackupInfo = {
version: string version: string
timestamp: string timestamp: string
packageBackups: { packageBackups: {
@@ -590,18 +587,18 @@ export interface BackupInfo {
} }
} }
export interface PackageBackupInfo { export type PackageBackupInfo = {
title: string title: string
version: string version: string
osVersion: string osVersion: string
timestamp: string timestamp: string
} }
export interface ServerSpecs { export type ServerSpecs = {
[key: string]: string | number [key: string]: string | number
} }
export interface SSHKey { export type SSHKey = {
createdAt: string createdAt: string
alg: string alg: string
hostname: string hostname: string
@@ -610,32 +607,25 @@ export interface SSHKey {
export type ServerNotifications = ServerNotification<number>[] export type ServerNotifications = ServerNotification<number>[]
export interface ServerNotification<T extends number> { export type ServerNotification<T extends number> = {
id: number id: number
packageId: string | null packageId: string | null
createdAt: string createdAt: string
code: T code: T
level: NotificationLevel level: 'success' | 'info' | 'warning' | 'error'
title: string title: string
message: string message: string
data: NotificationData<T> data: NotificationData<T>
read: boolean read: boolean
} }
export enum NotificationLevel {
Success = 'success',
Info = 'info',
Warning = 'warning',
Error = 'error',
}
export type NotificationData<T> = T extends 0 export type NotificationData<T> = T extends 0
? null ? null
: T extends 1 : T extends 1
? BackupReport ? BackupReport
: any : any
export interface BackupReport { export type BackupReport = {
server: { server: {
attempted: boolean attempted: boolean
error: string | null error: string | null
@@ -647,7 +637,7 @@ export interface BackupReport {
} }
} }
export interface AvailableWifi { export type AvailableWifi = {
ssid: string ssid: string
strength: number strength: number
security: string[] security: string[]
@@ -682,29 +672,29 @@ export type DependencyError =
| DependencyErrorHealthChecksFailed | DependencyErrorHealthChecksFailed
| DependencyErrorTransitive | DependencyErrorTransitive
export interface DependencyErrorNotInstalled { export type DependencyErrorNotInstalled = {
type: 'notInstalled' type: 'notInstalled'
} }
export interface DependencyErrorNotRunning { export type DependencyErrorNotRunning = {
type: 'notRunning' type: 'notRunning'
} }
export interface DependencyErrorIncorrectVersion { export type DependencyErrorIncorrectVersion = {
type: 'incorrectVersion' type: 'incorrectVersion'
expected: string // version range expected: string // version range
received: string // version received: string // version
} }
export interface DependencyErrorConfigUnsatisfied { export type DependencyErrorConfigUnsatisfied = {
type: 'configUnsatisfied' type: 'configUnsatisfied'
} }
export interface DependencyErrorHealthChecksFailed { export type DependencyErrorHealthChecksFailed = {
type: 'healthChecksFailed' type: 'healthChecksFailed'
check: T.HealthCheckResult check: T.HealthCheckResult
} }
export interface DependencyErrorTransitive { export type DependencyErrorTransitive = {
type: 'transitive' type: 'transitive'
} }

View File

@@ -3,11 +3,10 @@ import {
GetPackagesRes, GetPackagesRes,
MarketplacePkg, MarketplacePkg,
} from '@start9labs/marketplace' } from '@start9labs/marketplace'
import { Log, RPCOptions } from '@start9labs/shared' import { RPCOptions } from '@start9labs/shared'
import { T } from '@start9labs/start-sdk' import { T } from '@start9labs/start-sdk'
import { Observable } from 'rxjs' import { Observable } from 'rxjs'
import { WebSocketSubjectConfig } from 'rxjs/webSocket' import { BackupTargetType, RR } from './api.types'
import { BackupTargetType, Metrics, RR } from './api.types'
export abstract class ApiService { export abstract class ApiService {
// http // http
@@ -32,7 +31,7 @@ export abstract class ApiService {
abstract openWebsocket$<T>( abstract openWebsocket$<T>(
guid: string, guid: string,
config: RR.WebsocketConfig<T>, config?: RR.WebsocketConfig<T>,
): Observable<T> ): Observable<T>
// state // state
@@ -78,20 +77,13 @@ export abstract class ApiService {
// init // init
abstract initGetProgress(): Promise<RR.InitGetProgressRes> abstract initFollowProgress(): Promise<RR.InitFollowProgressRes>
abstract initFollowLogs( abstract initFollowLogs(
params: RR.FollowServerLogsReq, params: RR.FollowServerLogsReq,
): Promise<RR.FollowServerLogsRes> ): Promise<RR.FollowServerLogsRes>
// server // server
abstract openLogsWebsocket$(
config: WebSocketSubjectConfig<Log>,
): Observable<Log>
abstract openMetricsWebsocket$(
config: WebSocketSubjectConfig<Metrics>,
): Observable<Metrics>
abstract getSystemTime( abstract getSystemTime(
params: RR.GetSystemTimeReq, params: RR.GetSystemTimeReq,
@@ -119,9 +111,9 @@ export abstract class ApiService {
params: RR.FollowServerLogsReq, params: RR.FollowServerLogsReq,
): Promise<RR.FollowServerLogsRes> ): Promise<RR.FollowServerLogsRes>
abstract getServerMetrics( abstract followServerMetrics(
params: RR.GetServerMetricsReq, params: RR.FollowServerMetricsReq,
): Promise<RR.GetServerMetricsRes> ): Promise<RR.FollowServerMetricsRes>
abstract updateServer(url?: string): Promise<RR.UpdateServerRes> abstract updateServer(url?: string): Promise<RR.UpdateServerRes>

View File

@@ -3,16 +3,15 @@ import {
HttpOptions, HttpOptions,
HttpService, HttpService,
isRpcError, isRpcError,
Log,
Method, Method,
RpcError, RpcError,
RPCOptions, RPCOptions,
} from '@start9labs/shared' } from '@start9labs/shared'
import { PATCH_CACHE } from 'src/app/services/patch-db/patch-db-source' import { PATCH_CACHE } from 'src/app/services/patch-db/patch-db-source'
import { ApiService } from './embassy-api.service' import { ApiService } from './embassy-api.service'
import { BackupTargetType, Metrics, RR } from './api.types' import { BackupTargetType, RR } from './api.types'
import { ConfigService } from '../config.service' import { ConfigService } from '../config.service'
import { webSocket, WebSocketSubjectConfig } from 'rxjs/webSocket' import { webSocket } from 'rxjs/webSocket'
import { Observable, filter, firstValueFrom } from 'rxjs' import { Observable, filter, firstValueFrom } from 'rxjs'
import { AuthService } from '../auth.service' import { AuthService } from '../auth.service'
import { DOCUMENT } from '@angular/common' import { DOCUMENT } from '@angular/common'
@@ -93,20 +92,10 @@ export class LiveApiService extends ApiService {
} }
// websocket // websocket
// @TODO Matt which of these 2 APIs should we use?
private openWebsocket<T>(config: WebSocketSubjectConfig<T>): Observable<T> {
const { location } = this.document.defaultView!
const protocol = location.protocol === 'http:' ? 'ws' : 'wss'
const host = location.host
config.url = `${protocol}://${host}/ws${config.url}`
return webSocket(config)
}
openWebsocket$<T>( openWebsocket$<T>(
guid: string, guid: string,
config: RR.WebsocketConfig<T>, config: RR.WebsocketConfig<T> = {},
): Observable<T> { ): Observable<T> {
const { location } = this.document.defaultView! const { location } = this.document.defaultView!
const protocol = location.protocol === 'http:' ? 'ws' : 'wss' const protocol = location.protocol === 'http:' ? 'ws' : 'wss'
@@ -210,7 +199,7 @@ export class LiveApiService extends ApiService {
// init // init
async initGetProgress(): Promise<RR.InitGetProgressRes> { async initFollowProgress(): Promise<RR.InitFollowProgressRes> {
return this.rpcRequest({ method: 'init.subscribe', params: {} }) return this.rpcRequest({ method: 'init.subscribe', params: {} })
} }
@@ -221,15 +210,6 @@ export class LiveApiService extends ApiService {
} }
// server // server
openLogsWebsocket$(config: WebSocketSubjectConfig<Log>): Observable<Log> {
return this.openWebsocket(config)
}
openMetricsWebsocket$(
config: WebSocketSubjectConfig<Metrics>,
): Observable<Metrics> {
return this.openWebsocket(config)
}
async getSystemTime( async getSystemTime(
params: RR.GetSystemTimeReq, params: RR.GetSystemTimeReq,
@@ -271,9 +251,9 @@ export class LiveApiService extends ApiService {
return this.rpcRequest({ method: 'net.tor.logs.follow', params }) return this.rpcRequest({ method: 'net.tor.logs.follow', params })
} }
async getServerMetrics( async followServerMetrics(
params: RR.GetServerMetricsReq, params: RR.FollowServerMetricsReq,
): Promise<RR.GetServerMetricsRes> { ): Promise<RR.FollowServerMetricsRes> {
return this.rpcRequest({ method: 'server.metrics', params }) return this.rpcRequest({ method: 'server.metrics', params })
} }

View File

@@ -15,7 +15,7 @@ import {
StateInfo, StateInfo,
UpdatingState, UpdatingState,
} from 'src/app/services/patch-db/data-model' } from 'src/app/services/patch-db/data-model'
import { BackupTargetType, Metrics, RR } from './api.types' import { BackupTargetType, RR } from './api.types'
import { Mock } from './api.fixures' import { Mock } from './api.fixures'
import { import {
from, from,
@@ -35,7 +35,6 @@ import {
GetPackagesRes, GetPackagesRes,
MarketplacePkg, MarketplacePkg,
} from '@start9labs/marketplace' } from '@start9labs/marketplace'
import { WebSocketSubjectConfig } from 'rxjs/webSocket'
import markdown from 'raw-loader!../../../../../shared/assets/markdown/md-sample.md' import markdown from 'raw-loader!../../../../../shared/assets/markdown/md-sample.md'
const PROGRESS: T.FullProgress = { const PROGRESS: T.FullProgress = {
@@ -84,8 +83,8 @@ export class MockApiService extends ApiService {
async uploadPackage(guid: string, body: Blob): Promise<string> { async uploadPackage(guid: string, body: Blob): Promise<string> {
await pauseFor(2000) await pauseFor(2000)
// @TODO Matt what should this return? // @TODO Aiden confirm this is correct
return '' return 'success'
} }
async uploadFile(body: Blob): Promise<string> { async uploadFile(body: Blob): Promise<string> {
@@ -258,7 +257,7 @@ export class MockApiService extends ApiService {
// init // init
async initGetProgress(): Promise<RR.InitGetProgressRes> { async initFollowProgress(): Promise<RR.InitFollowProgressRes> {
await pauseFor(250) await pauseFor(250)
return { return {
progress: PROGRESS, progress: PROGRESS,
@@ -276,30 +275,6 @@ export class MockApiService extends ApiService {
// server // server
openLogsWebsocket$(config: WebSocketSubjectConfig<Log>): Observable<Log> {
return interval(50).pipe(
map((_, index) => {
// mock fire open observer
if (index === 0) config.openObserver?.next(new Event(''))
if (index === 100) throw new Error('HAAHHA')
return Mock.ServerLogs[0]
}),
)
}
openMetricsWebsocket$(
config: WebSocketSubjectConfig<Metrics>,
): Observable<Metrics> {
return interval(2000).pipe(
map((_, index) => {
// mock fire open observer
if (index === 0) config.openObserver?.next(new Event(''))
if (index === 4) throw new Error('HAHAHA')
return Mock.getMetrics()
}),
)
}
async getSystemTime( async getSystemTime(
params: RR.GetSystemTimeReq, params: RR.GetSystemTimeReq,
): Promise<RR.GetSystemTimeRes> { ): Promise<RR.GetSystemTimeRes> {
@@ -386,9 +361,9 @@ export class MockApiService extends ApiService {
return logs return logs
} }
async getServerMetrics( async followServerMetrics(
params: RR.GetServerMetricsReq, params: RR.FollowServerMetricsReq,
): Promise<RR.GetServerMetricsRes> { ): Promise<RR.FollowServerMetricsRes> {
await pauseFor(2000) await pauseFor(2000)
return { return {
guid: 'iqudh37um-i38u3-34-a51b-jkhd783ein', guid: 'iqudh37um-i38u3-34-a51b-jkhd783ein',

View File

@@ -130,8 +130,7 @@ export const mockPatchData: DataModel = {
password: '', password: '',
}, },
platform: 'x86_64-nonfree', platform: 'x86_64-nonfree',
// @TODO Matt zram needs to be added? zram: true,
// zram: true,
governor: 'performance', governor: 'performance',
passwordHash: passwordHash:
'$argon2d$v=19$m=1024,t=1,p=1$YXNkZmFzZGZhc2RmYXNkZg$Ceev1I901G6UwU+hY0sHrFZ56D+o+LNJ', '$argon2d$v=19$m=1024,t=1,p=1$YXNkZmFzZGZhc2RmYXNkZg$Ceev1I901G6UwU+hY0sHrFZ56D+o+LNJ',

View File

@@ -2,7 +2,6 @@ import { inject, Injectable } from '@angular/core'
import { ErrorService } from '@start9labs/shared' import { ErrorService } from '@start9labs/shared'
import { TuiDialogService } from '@taiga-ui/core' import { TuiDialogService } from '@taiga-ui/core'
import { import {
NotificationLevel,
ServerNotification, ServerNotification,
ServerNotifications, ServerNotifications,
} from 'src/app/services/api/api.types' } from 'src/app/services/api/api.types'
@@ -63,13 +62,13 @@ export class NotificationService {
getColor(notification: ServerNotification<number>): string { getColor(notification: ServerNotification<number>): string {
switch (notification.level) { switch (notification.level) {
case NotificationLevel.Info: case 'info':
return 'var(--tui-status-info)' return 'var(--tui-status-info)'
case NotificationLevel.Success: case 'success':
return 'var(--tui-status-positive)' return 'var(--tui-status-positive)'
case NotificationLevel.Warning: case 'warning':
return 'var(--tui-status-warning)' return 'var(--tui-status-warning)'
case NotificationLevel.Error: case 'error':
return 'var(--tui-status-negative)' return 'var(--tui-status-negative)'
default: default:
return '' return ''
@@ -78,12 +77,12 @@ export class NotificationService {
getIcon(notification: ServerNotification<number>): string { getIcon(notification: ServerNotification<number>): string {
switch (notification.level) { switch (notification.level) {
case NotificationLevel.Info: case 'info':
return '@tui.info' return '@tui.info'
case NotificationLevel.Success: case 'success':
return '@tui.circle-check' return '@tui.circle-check'
case NotificationLevel.Warning: case 'warning':
case NotificationLevel.Error: case 'error':
return '@tui.circle-alert' return '@tui.circle-alert'
default: default:
return '' return ''

View File

@@ -56,6 +56,7 @@ export type ServerInfo = {
platform: string platform: string
arch: string arch: string
governor: string | null governor: string | null
zram: boolean
} }
export type NetworkInfo = { export type NetworkInfo = {