Feat/combine uis (#2633)

* wip

* restructure backend for new ui structure

* new patchdb bootstrap, single websocket api, local storage migration, more

* update db websocket

* init apis

* update patch-db

* setup progress

* feat: implement state service, alert and routing

Signed-off-by: waterplea <alexander@inkin.ru>

* update setup wizard for new types

* feat: add init page

Signed-off-by: waterplea <alexander@inkin.ru>

* chore: refactor message, patch-db source stream and connection service

Signed-off-by: waterplea <alexander@inkin.ru>

* fix method not found on state

* fix backend bugs

* fix compat assets

* address comments

* remove unneeded styling

* cleaner progress

* bugfixes

* fix init logs

* fix progress reporting

* fix navigation by getting state after init

* remove patch dependency from live api

* fix caching

* re-add patchDB to live api

* fix metrics values

* send close frame

* add bootId and fix polling

---------

Signed-off-by: waterplea <alexander@inkin.ru>
Co-authored-by: Aiden McClelland <me@drbonez.dev>
Co-authored-by: waterplea <alexander@inkin.ru>
This commit is contained in:
Matt Hill
2024-06-19 13:51:44 -06:00
committed by GitHub
parent e92d4ff147
commit da3720c7a9
147 changed files with 3939 additions and 2637 deletions

View File

@@ -1,15 +1,14 @@
import { Injectable } from '@angular/core'
import { Log, pauseFor } from '@start9labs/shared'
import { Log, RPCErrorDetails, pauseFor } from '@start9labs/shared'
import { ApiService } from './embassy-api.service'
import {
Operation,
PatchOp,
pathFromArray,
RemoveOperation,
Update,
Revision,
} from 'patch-db-client'
import {
DataModel,
InstallingState,
PackageDataEntry,
StateInfo,
@@ -20,22 +19,17 @@ import { parsePropertiesPermissive } from 'src/app/util/properties.util'
import { Mock } from './api.fixures'
import markdown from 'raw-loader!../../../../../shared/assets/markdown/md-sample.md'
import {
EMPTY,
iif,
from,
interval,
map,
Observable,
shareReplay,
startWith,
Subject,
switchMap,
tap,
timer,
} from 'rxjs'
import { LocalStorageBootstrap } from '../patch-db/local-storage-bootstrap'
import { mockPatchData } from './mock-patch'
import { WebSocketSubjectConfig } from 'rxjs/webSocket'
import { AuthService } from '../auth.service'
import { ConnectionService } from '../connection.service'
import { StoreInfo } from '@start9labs/marketplace'
import { T } from '@start9labs/start-sdk'
@@ -71,32 +65,17 @@ const PROGRESS: T.FullProgress = {
@Injectable()
export class MockApiService extends ApiService {
readonly mockWsSource$ = new Subject<Update<DataModel>>()
readonly mockWsSource$ = new Subject<Revision>()
private readonly revertTime = 1800
sequence = 0
constructor(
private readonly bootstrapper: LocalStorageBootstrap,
private readonly connectionService: ConnectionService,
private readonly auth: AuthService,
) {
constructor(private readonly auth: AuthService) {
super()
this.auth.isVerified$
.pipe(
tap(() => {
this.sequence = 0
}),
switchMap(verified =>
iif(
() => verified,
timer(2000).pipe(
tap(() => {
this.connectionService.websocketConnected$.next(true)
}),
),
EMPTY,
),
),
)
.subscribe()
}
@@ -111,8 +90,57 @@ export class MockApiService extends ApiService {
return 'success'
}
// websocket
openWebsocket$<T>(
guid: string,
config: RR.WebsocketConfig<T>,
): Observable<T> {
if (guid === 'db-guid') {
return this.mockWsSource$.pipe<any>(
shareReplay({ bufferSize: 1, refCount: true }),
)
} else if (guid === 'logs-guid') {
return interval(50).pipe<any>(
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]
}),
)
} else if (guid === 'init-progress-guid') {
return from(this.initProgress()).pipe(
startWith(PROGRESS),
) as Observable<T>
} else {
throw new Error('invalid guid type')
}
}
// server state
private stateIndex = 0
async getState(): Promise<RR.ServerState> {
await pauseFor(1000)
this.stateIndex++
return this.stateIndex === 1 ? 'initializing' : 'running'
}
// db
async subscribeToPatchDB(
params: RR.SubscribePatchReq,
): Promise<RR.SubscribePatchRes> {
await pauseFor(2000)
return {
dump: { id: 1, value: mockPatchData },
guid: 'db-guid',
}
}
async setDbValue<T>(
pathArr: Array<string | number>,
value: T,
@@ -136,11 +164,6 @@ export class MockApiService extends ApiService {
async login(params: RR.LoginReq): Promise<RR.loginRes> {
await pauseFor(2000)
setTimeout(() => {
this.mockWsSource$.next({ id: 1, value: mockPatchData })
}, 2000)
return null
}
@@ -166,34 +189,63 @@ export class MockApiService extends ApiService {
return null
}
// server
// diagnostic
async echo(params: RR.EchoReq, url?: string): Promise<RR.EchoRes> {
if (url) {
const num = Math.floor(Math.random() * 10) + 1
if (num > 8) return params.message
throw new Error()
async getError(): Promise<RPCErrorDetails> {
await pauseFor(1000)
return {
code: 15,
message: 'Unknown server',
data: { details: 'Some details about the error here' },
}
}
async diagnosticGetError(): Promise<RR.DiagnosticErrorRes> {
await pauseFor(1000)
return {
code: 15,
message: 'Unknown server',
data: { details: 'Some details about the error here' },
}
}
async diagnosticRestart(): Promise<void> {
await pauseFor(1000)
}
async diagnosticForgetDrive(): Promise<void> {
await pauseFor(1000)
}
async diagnosticRepairDisk(): Promise<void> {
await pauseFor(1000)
}
async diagnosticGetLogs(
params: RR.GetServerLogsReq,
): Promise<RR.GetServerLogsRes> {
return this.getServerLogs(params)
}
// init
async initGetProgress(): Promise<RR.InitGetProgressRes> {
await pauseFor(250)
return {
progress: PROGRESS,
guid: 'init-progress-guid',
}
}
async initFollowLogs(): Promise<RR.FollowServerLogsRes> {
await pauseFor(2000)
return params.message
return {
startCursor: 'start-cursor',
guid: 'logs-guid',
}
}
openPatchWebsocket$(): Observable<Update<DataModel>> {
return this.mockWsSource$.pipe(
shareReplay({ bufferSize: 1, refCount: true }),
)
}
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]
}),
)
}
// server
async getSystemTime(
params: RR.GetSystemTimeReq,
@@ -248,7 +300,7 @@ export class MockApiService extends ApiService {
await pauseFor(2000)
return {
startCursor: 'start-cursor',
guid: '7251d5be-645f-4362-a51b-3a85be92b31e',
guid: 'logs-guid',
}
}
@@ -258,7 +310,7 @@ export class MockApiService extends ApiService {
await pauseFor(2000)
return {
startCursor: 'start-cursor',
guid: '7251d5be-645f-4362-a51b-3a85be92b31e',
guid: 'logs-guid',
}
}
@@ -268,11 +320,11 @@ export class MockApiService extends ApiService {
await pauseFor(2000)
return {
startCursor: 'start-cursor',
guid: '7251d5be-645f-4362-a51b-3a85be92b31e',
guid: 'logs-guid',
}
}
randomLogs(limit = 1): Log[] {
private randomLogs(limit = 1): Log[] {
const arrLength = Math.ceil(limit / Mock.ServerLogs.length)
const logs = new Array(arrLength)
.fill(Mock.ServerLogs)
@@ -374,12 +426,6 @@ export class MockApiService extends ApiService {
return null
}
async systemRebuild(
params: RR.SystemRebuildReq,
): Promise<RR.SystemRebuildRes> {
return this.restartServer(params)
}
async repairDisk(params: RR.RestartServerReq): Promise<RR.RestartServerRes> {
await pauseFor(2000)
return null
@@ -422,7 +468,7 @@ export class MockApiService extends ApiService {
}
}
async getEos(): Promise<RR.GetMarketplaceEosRes> {
async checkOSUpdate(qp: RR.CheckOSUpdateReq): Promise<RR.CheckOSUpdateRes> {
await pauseFor(2000)
return Mock.MarketplaceEos
}
@@ -641,13 +687,13 @@ export class MockApiService extends ApiService {
await pauseFor(2000)
let entries
if (Math.random() < 0.2) {
entries = Mock.PackageLogs
entries = Mock.ServerLogs
} else {
const arrLength = params.limit
? Math.ceil(params.limit / Mock.PackageLogs.length)
? Math.ceil(params.limit / Mock.ServerLogs.length)
: 10
entries = new Array(arrLength)
.fill(Mock.PackageLogs)
.fill(Mock.ServerLogs)
.reduce((acc, val) => acc.concat(val), [])
}
return {
@@ -663,7 +709,7 @@ export class MockApiService extends ApiService {
await pauseFor(2000)
return {
startCursor: 'start-cursor',
guid: '7251d5be-645f-4362-a51b-3a85be92b31e',
guid: 'logs-guid',
}
}
@@ -673,7 +719,7 @@ export class MockApiService extends ApiService {
await pauseFor(2000)
setTimeout(async () => {
this.updateProgress(params.id)
this.installProgress(params.id)
}, 1000)
const patch: Operation<
@@ -745,7 +791,7 @@ export class MockApiService extends ApiService {
await pauseFor(2000)
const patch: Operation<PackageDataEntry>[] = params.ids.map(id => {
setTimeout(async () => {
this.updateProgress(id)
this.installProgress(id)
}, 2000)
return {
@@ -1013,7 +1059,57 @@ export class MockApiService extends ApiService {
return '4120e092-05ab-4de2-9fbd-c3f1f4b1df9e' // no significance, randomly generated
}
private async updateProgress(id: string): Promise<void> {
private async initProgress(): Promise<T.FullProgress> {
const progress = JSON.parse(JSON.stringify(PROGRESS))
for (let [i, phase] of progress.phases.entries()) {
if (
!phase.progress ||
typeof phase.progress !== 'object' ||
!phase.progress.total
) {
await pauseFor(2000)
progress.phases[i].progress = true
if (
progress.overall &&
typeof progress.overall === 'object' &&
progress.overall.total
) {
const step = progress.overall.total / progress.phases.length
progress.overall.done += step
}
} else {
const step = phase.progress.total / 4
while (phase.progress.done < phase.progress.total) {
await pauseFor(200)
phase.progress.done += step
if (
progress.overall &&
typeof progress.overall === 'object' &&
progress.overall.total
) {
const step = progress.overall.total / progress.phases.length / 4
progress.overall.done += step
}
if (phase.progress.done === phase.progress.total) {
await pauseFor(250)
progress.phases[i].progress = true
}
}
}
}
return progress
}
private async installProgress(id: string): Promise<void> {
const progress = JSON.parse(JSON.stringify(PROGRESS))
for (let [i, phase] of progress.phases.entries()) {
@@ -1194,10 +1290,6 @@ export class MockApiService extends ApiService {
}
private async mockRevision<T>(patch: Operation<T>[]): Promise<void> {
if (!this.sequence) {
const { sequence } = this.bootstrapper.init()
this.sequence = sequence
}
const revision = {
id: ++this.sequence,
patch,