From 8313dfaeb952560da1bd40086d1cc918bc0c24c6 Mon Sep 17 00:00:00 2001 From: Aiden McClelland <3732071+dr-bonez@users.noreply.github.com> Date: Tue, 9 May 2023 15:05:42 -0600 Subject: [PATCH] statically type server metrics and use websocket (#2124) Co-authored-by: Matt Hill --- .../app-metrics/app-metrics.module.ts | 26 --- .../app-metrics/app-metrics.page.html | 25 --- .../app-metrics/app-metrics.page.scss | 3 - .../app-metrics/app-metrics.page.ts | 59 ------- .../pages/apps-routes/apps-routing.module.ts | 7 - .../server-metrics/server-metrics.page.html | 159 ++++++++++++------ .../server-metrics/server-metrics.page.scss | 4 +- .../server-metrics/server-metrics.page.ts | 95 +++++------ .../ui/src/app/services/api/api.fixures.ts | 128 ++++---------- .../ui/src/app/services/api/api.types.ts | 42 +++-- .../app/services/api/embassy-api.service.ts | 10 +- .../services/api/embassy-live-api.service.ts | 14 +- .../services/api/embassy-mock-api.service.ts | 27 ++- .../ui/src/app/services/time-service.ts | 109 ++++++------ 14 files changed, 303 insertions(+), 405 deletions(-) delete mode 100644 frontend/projects/ui/src/app/pages/apps-routes/app-metrics/app-metrics.module.ts delete mode 100644 frontend/projects/ui/src/app/pages/apps-routes/app-metrics/app-metrics.page.html delete mode 100644 frontend/projects/ui/src/app/pages/apps-routes/app-metrics/app-metrics.page.scss delete mode 100644 frontend/projects/ui/src/app/pages/apps-routes/app-metrics/app-metrics.page.ts diff --git a/frontend/projects/ui/src/app/pages/apps-routes/app-metrics/app-metrics.module.ts b/frontend/projects/ui/src/app/pages/apps-routes/app-metrics/app-metrics.module.ts deleted file mode 100644 index 2c53d0fea..000000000 --- a/frontend/projects/ui/src/app/pages/apps-routes/app-metrics/app-metrics.module.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { NgModule } from '@angular/core' -import { CommonModule } from '@angular/common' -import { Routes, RouterModule } from '@angular/router' -import { IonicModule } from '@ionic/angular' -import { AppMetricsPage } from './app-metrics.page' -import { SharedPipesModule } from '@start9labs/shared' -import { SkeletonListComponentModule } from 'src/app/components/skeleton-list/skeleton-list.component.module' - -const routes: Routes = [ - { - path: '', - component: AppMetricsPage, - }, -] - -@NgModule({ - imports: [ - CommonModule, - IonicModule, - RouterModule.forChild(routes), - SharedPipesModule, - SkeletonListComponentModule, - ], - declarations: [AppMetricsPage], -}) -export class AppMetricsPageModule {} diff --git a/frontend/projects/ui/src/app/pages/apps-routes/app-metrics/app-metrics.page.html b/frontend/projects/ui/src/app/pages/apps-routes/app-metrics/app-metrics.page.html deleted file mode 100644 index 18650321c..000000000 --- a/frontend/projects/ui/src/app/pages/apps-routes/app-metrics/app-metrics.page.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - Monitor - - - - - - - - - {{ metric.key }} - - {{ metric.value.value }} {{ metric.value.unit }} - - - - diff --git a/frontend/projects/ui/src/app/pages/apps-routes/app-metrics/app-metrics.page.scss b/frontend/projects/ui/src/app/pages/apps-routes/app-metrics/app-metrics.page.scss deleted file mode 100644 index eea898305..000000000 --- a/frontend/projects/ui/src/app/pages/apps-routes/app-metrics/app-metrics.page.scss +++ /dev/null @@ -1,3 +0,0 @@ -.metric-note { - font-size: 16px; -} \ No newline at end of file diff --git a/frontend/projects/ui/src/app/pages/apps-routes/app-metrics/app-metrics.page.ts b/frontend/projects/ui/src/app/pages/apps-routes/app-metrics/app-metrics.page.ts deleted file mode 100644 index 8cf576bd7..000000000 --- a/frontend/projects/ui/src/app/pages/apps-routes/app-metrics/app-metrics.page.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { Component } from '@angular/core' -import { ActivatedRoute } from '@angular/router' -import { Metric } from 'src/app/services/api/api.types' -import { ApiService } from 'src/app/services/api/embassy-api.service' -import { pauseFor, ErrorToastService, getPkgId } from '@start9labs/shared' - -@Component({ - selector: 'app-metrics', - templateUrl: './app-metrics.page.html', - styleUrls: ['./app-metrics.page.scss'], -}) -export class AppMetricsPage { - loading = true - readonly pkgId = getPkgId(this.route) - going = false - metrics?: Metric - - constructor( - private readonly route: ActivatedRoute, - private readonly errToast: ErrorToastService, - private readonly embassyApi: ApiService, - ) {} - - ngOnInit() { - this.startDaemon() - } - - ngOnDestroy() { - this.stopDaemon() - } - - async startDaemon(): Promise { - this.going = true - while (this.going) { - const startTime = Date.now() - await this.getMetrics() - await pauseFor(Math.max(4000 - (Date.now() - startTime), 0)) - } - } - - stopDaemon() { - this.going = false - } - - async getMetrics(): Promise { - try { - this.metrics = await this.embassyApi.getPkgMetrics({ id: this.pkgId }) - } catch (e: any) { - this.errToast.present(e) - this.stopDaemon() - } finally { - this.loading = false - } - } - - asIsOrder(a: any, b: any) { - return 0 - } -} diff --git a/frontend/projects/ui/src/app/pages/apps-routes/apps-routing.module.ts b/frontend/projects/ui/src/app/pages/apps-routes/apps-routing.module.ts index 9dfbddcad..21d1f9dea 100644 --- a/frontend/projects/ui/src/app/pages/apps-routes/apps-routing.module.ts +++ b/frontend/projects/ui/src/app/pages/apps-routes/apps-routing.module.ts @@ -36,13 +36,6 @@ const routes: Routes = [ loadChildren: () => import('./app-logs/app-logs.module').then(m => m.AppLogsPageModule), }, - { - path: ':pkgId/metrics', - loadChildren: () => - import('./app-metrics/app-metrics.module').then( - m => m.AppMetricsPageModule, - ), - }, { path: ':pkgId/properties', loadChildren: () => diff --git a/frontend/projects/ui/src/app/pages/server-routes/server-metrics/server-metrics.page.html b/frontend/projects/ui/src/app/pages/server-routes/server-metrics/server-metrics.page.html index c93d633c0..beb137f26 100644 --- a/frontend/projects/ui/src/app/pages/server-routes/server-metrics/server-metrics.page.html +++ b/frontend/projects/ui/src/app/pages/server-routes/server-metrics/server-metrics.page.html @@ -4,58 +4,121 @@ Monitor - - + +

+ Websocket Failed. Reconnecting +

-
- - - Time - - System Time - - {{ systemTime$ | async | date:'MMMM d, y, h:mm a z':'UTC' - }} - - - - System Uptime - - - {{ uptime.days }} Days, {{ uptime.hours }} Hours, - {{ uptime.minutes }} Minutes - - - - - - - {{ metricGroup.key }} - - {{ metric.key }} - - {{ metric.value.value }} {{ metric.value.unit }} - - - + + System Time + + Current Time (UTC) +
+ {{ timeInfo.systemCurrentTime | date : 'MMMM d, y, h:mm:ss a' : 'UTC' + }} +
+
+ + Start Time (UTC) +
+ {{ timeInfo.systemStartTime | date : 'MMMM d, y, h:mm:ss a' : 'UTC' }} +
+
+ + Uptime +
+ {{ uptime.days }} Days, {{ uptime.hours }} Hours, + {{ uptime.minutes }} Minutes, + {{ uptime.seconds }} Seconds +
+
-
+ + + + General + + Temperature +
{{ metrics.general.temperature }} °C
+
+ + Memory + + Percentage Used +
{{ metrics.memory['percentage-used'] }} %
+
+ + Total +
{{ metrics.memory.total }} MiB
+
+ + Available +
{{ metrics.memory.available }} MiB
+
+ + Used +
{{ metrics.memory.used }} MiB
+
+ + Swap Total +
{{ metrics.memory['swap-total'] }} MiB
+
+ + Swap Free +
{{ metrics.memory['swap-free'] }} MiB
+
+ + Swap Used +
{{ metrics.memory['swap-used'] }} MiB
+
+ + CPU + + User Space +
{{ metrics.cpu['user-space'] }} %
+
+ + Kernel Space +
{{ metrics.cpu['kernel-space'] }} %
+
+ + I/O Wait +
{{ metrics.cpu['io-wait'] }} %
+
+ + Idle +
{{ metrics.cpu.idle }} %
+
+ + Usage +
{{ metrics.cpu.usage }} %
+
+ + Disk + + Size +
{{ metrics.disk.size }} GB
+
+ + Used +
{{ metrics.disk.used }} GB
+
+ + Percentage Used +
{{ metrics.disk['percentage-used'] }} %
+
+ + Available +
{{ metrics.disk.available }} GB
+
+
+
+ + + +
diff --git a/frontend/projects/ui/src/app/pages/server-routes/server-metrics/server-metrics.page.scss b/frontend/projects/ui/src/app/pages/server-routes/server-metrics/server-metrics.page.scss index eea898305..17a33cb9c 100644 --- a/frontend/projects/ui/src/app/pages/server-routes/server-metrics/server-metrics.page.scss +++ b/frontend/projects/ui/src/app/pages/server-routes/server-metrics/server-metrics.page.scss @@ -1,3 +1,3 @@ -.metric-note { - font-size: 16px; +ion-note { + font-size: medium; } \ No newline at end of file diff --git a/frontend/projects/ui/src/app/pages/server-routes/server-metrics/server-metrics.page.ts b/frontend/projects/ui/src/app/pages/server-routes/server-metrics/server-metrics.page.ts index 83f576919..08b8ae5f4 100644 --- a/frontend/projects/ui/src/app/pages/server-routes/server-metrics/server-metrics.page.ts +++ b/frontend/projects/ui/src/app/pages/server-routes/server-metrics/server-metrics.page.ts @@ -1,8 +1,17 @@ import { Component } from '@angular/core' import { Metrics } from 'src/app/services/api/api.types' import { ApiService } from 'src/app/services/api/embassy-api.service' -import { TimeService } from 'src/app/services/time-service' -import { pauseFor, ErrorToastService } from '@start9labs/shared' +import { TimeInfo, TimeService } from 'src/app/services/time-service' +import { + catchError, + combineLatest, + filter, + from, + Observable, + startWith, + switchMap, +} from 'rxjs' +import { ConnectionService } from 'src/app/services/connection.service' @Component({ selector: 'server-metrics', @@ -10,63 +19,43 @@ import { pauseFor, ErrorToastService } from '@start9labs/shared' styleUrls: ['./server-metrics.page.scss'], }) export class ServerMetricsPage { - loading = true - going = false - metrics: Metrics = {} + websocketFail = false - readonly systemTime$ = this.timeService.systemTime$ - readonly systemUptime$ = this.timeService.systemUptime$ + readonly serverData$ = this.getServerData$() constructor( - private readonly errToast: ErrorToastService, - private readonly embassyApi: ApiService, - private readonly timeService: TimeService, + private readonly api: ApiService, + readonly timeService: TimeService, + private readonly connectionService: ConnectionService, ) {} - async ngOnInit() { - await this.getMetrics() - let headersCount = 0 - let rowsCount = 0 - Object.values(this.metrics).forEach(groupVal => { - headersCount++ - Object.keys(groupVal).forEach(_ => { - rowsCount++ - }) - }) - const height = headersCount * 54 + rowsCount * 50 + 24 // extra 24 for room at the bottom - const elem = document.getElementById('metricSection') - if (elem) elem.style.height = `${height}px` - this.startDaemon() - this.loading = false + private getServerData$(): Observable<[TimeInfo, Metrics]> { + return combineLatest([ + this.timeService.getTimeInfo$(), + this.getMetrics$(), + ]).pipe( + catchError(() => { + this.websocketFail = true + return this.connectionService.connected$.pipe( + filter(Boolean), + switchMap(() => this.getServerData$()), + ) + }), + ) } - ngOnDestroy() { - this.stopDaemon() - } - - private async startDaemon(): Promise { - this.going = true - while (this.going) { - const startTime = Date.now() - await this.getMetrics() - await pauseFor(4000 - Math.max(Date.now() - startTime, 0)) - } - } - - private stopDaemon() { - this.going = false - } - - private async getMetrics(): Promise { - try { - this.metrics = await this.embassyApi.getServerMetrics({}) - } catch (e: any) { - this.errToast.present(e) - this.stopDaemon() - } - } - - asIsOrder(a: any, b: any) { - return 0 + private getMetrics$(): Observable { + return from(this.api.getServerMetrics({})).pipe( + switchMap(({ metrics, guid }) => + this.api + .openMetricsWebsocket$({ + url: `/rpc/${guid}`, + openObserver: { + next: () => (this.websocketFail = false), + }, + }) + .pipe(startWith(metrics)), + ), + ) } } diff --git a/frontend/projects/ui/src/app/services/api/api.fixures.ts b/frontend/projects/ui/src/app/services/api/api.fixures.ts index a887061c5..8c38291f4 100644 --- a/frontend/projects/ui/src/app/services/api/api.fixures.ts +++ b/frontend/projects/ui/src/app/services/api/api.fixures.ts @@ -5,7 +5,13 @@ import { PackageState, ServerStatusInfo, } from 'src/app/services/patch-db/data-model' -import { Metric, RR, NotificationLevel, ServerNotifications } from './api.types' +import { + RR, + NotificationLevel, + ServerNotifications, + Metrics, +} from './api.types' + import { BTC_ICON, LND_ICON, PROXY_ICON } from './api-icons' import { DependencyMetadata, @@ -365,112 +371,36 @@ export module Mock { }, ] - export function getServerMetrics() { + export function getMetrics(): Metrics { return { - Group1: { - Metric1: { - value: Math.random(), - unit: 'mi/b', - }, - Metric2: { - value: Math.random(), - unit: '%', - }, - Metric3: { - value: 10.1, - unit: '%', - }, + general: { + temperature: (Math.random() * 100).toFixed(1), }, - Group2: { - Hmmmm1: { - value: 22.2, - unit: 'mi/b', - }, - Hmmmm2: { - value: 50, - unit: '%', - }, - Hmmmm3: { - value: 10.1, - unit: '%', - }, + memory: { + 'percentage-used': '20', + total: (Math.random() * 100).toFixed(2), + available: '18000', + used: '4000', + 'swap-total': '1000', + 'swap-free': Math.random().toFixed(2), + 'swap-used': '0', }, - Group3: { - Hmmmm1: { - value: Math.random(), - unit: 'mi/b', - }, - Hmmmm2: { - value: 50, - unit: '%', - }, - Hmmmm3: { - value: 10.1, - unit: '%', - }, + cpu: { + 'user-space': '100', + 'kernel-space': '50', + 'io-wait': String(Math.random() * 50), + idle: '80', + usage: '30', }, - Group4: { - Hmmmm1: { - value: Math.random(), - unit: 'mi/b', - }, - Hmmmm2: { - value: 50, - unit: '%', - }, - Hmmmm3: { - value: 10.1, - unit: '%', - }, - }, - Group5: { - Hmmmm1: { - value: Math.random(), - unit: 'mi/b', - }, - Hmmmm2: { - value: 50, - unit: '%', - }, - Hmmmm3: { - value: 10.1, - unit: '%', - }, - Hmmmm4: { - value: Math.random(), - unit: 'mi/b', - }, - Hmmmm5: { - value: 50, - unit: '%', - }, - Hmmmm6: { - value: 10.1, - unit: '%', - }, + disk: { + size: '1000', + used: '900', + available: '100', + 'percentage-used': '90', }, } } - export function getAppMetrics() { - const metr: Metric = { - Metric1: { - value: Math.random(), - unit: 'mi/b', - }, - Metric2: { - value: Math.random(), - unit: '%', - }, - Metric3: { - value: 10.1, - unit: '%', - }, - } - - return metr - } - export const ServerLogs: Log[] = [ { timestamp: '2022-07-28T03:52:54.808769Z', diff --git a/frontend/projects/ui/src/app/services/api/api.types.ts b/frontend/projects/ui/src/app/services/api/api.types.ts index a34484690..c1f38fae4 100644 --- a/frontend/projects/ui/src/app/services/api/api.types.ts +++ b/frontend/projects/ui/src/app/services/api/api.types.ts @@ -47,7 +47,10 @@ export module RR { } export type GetServerMetricsReq = {} // server.metrics - export type GetServerMetricsRes = Metrics + export type GetServerMetricsRes = { + guid: string + metrics: Metrics + } export type UpdateServerReq = { 'marketplace-url': string } // server.update export type UpdateServerRes = 'updating' | 'no-updates' @@ -233,9 +236,6 @@ export module RR { export type FollowPackageLogsReq = FollowServerLogsReq & { id: string } // package.logs.follow export type FollowPackageLogsRes = FollowServerLogsRes - export type GetPackageMetricsReq = { id: string } // package.metrics - export type GetPackageMetricsRes = Metric - export type InstallPackageReq = { id: string 'version-spec'?: string @@ -350,18 +350,30 @@ export interface ActionResponse { } export interface Metrics { - [key: string]: { - [key: string]: { - value: string | number | null - unit?: string - } + general: { + temperature: string } -} - -export interface Metric { - [key: string]: { - value: string | number | null - unit?: string + memory: { + 'percentage-used': string + total: string + available: string + used: string + 'swap-total': string + 'swap-free': string + 'swap-used': string + } + cpu: { + 'user-space': string + 'kernel-space': string + 'io-wait': string + idle: string + usage: string + } + disk: { + size: string + used: string + available: string + 'percentage-used': string } } diff --git a/frontend/projects/ui/src/app/services/api/embassy-api.service.ts b/frontend/projects/ui/src/app/services/api/embassy-api.service.ts index 609f8d786..5703d2939 100644 --- a/frontend/projects/ui/src/app/services/api/embassy-api.service.ts +++ b/frontend/projects/ui/src/app/services/api/embassy-api.service.ts @@ -1,6 +1,6 @@ import { BehaviorSubject, Observable } from 'rxjs' import { Update } from 'patch-db-client' -import { RR, Encrypted, BackupTargetType } from './api.types' +import { RR, Encrypted, BackupTargetType, Metrics } from './api.types' import { DataModel } from 'src/app/services/patch-db/data-model' import { Log } from '@start9labs/shared' import { WebSocketSubjectConfig } from 'rxjs/webSocket' @@ -63,6 +63,10 @@ export abstract class ApiService { config: WebSocketSubjectConfig, ): Observable + abstract openMetricsWebsocket$( + config: WebSocketSubjectConfig, + ): Observable + abstract getSystemTime( params: RR.GetSystemTimeReq, ): Promise @@ -93,10 +97,6 @@ export abstract class ApiService { params: RR.GetServerMetricsReq, ): Promise - abstract getPkgMetrics( - params: RR.GetPackageMetricsReq, - ): Promise - abstract updateServer(url?: string): Promise abstract restartServer( diff --git a/frontend/projects/ui/src/app/services/api/embassy-live-api.service.ts b/frontend/projects/ui/src/app/services/api/embassy-live-api.service.ts index fe02111a4..b96ad3368 100644 --- a/frontend/projects/ui/src/app/services/api/embassy-live-api.service.ts +++ b/frontend/projects/ui/src/app/services/api/embassy-live-api.service.ts @@ -10,7 +10,7 @@ import { RPCOptions, } from '@start9labs/shared' import { ApiService } from './embassy-api.service' -import { BackupTargetType, RR } from './api.types' +import { BackupTargetType, Metrics, RR } from './api.types' import { parsePropertiesPermissive } from 'src/app/util/properties.util' import { ConfigService } from '../config.service' import { webSocket, WebSocketSubjectConfig } from 'rxjs/webSocket' @@ -126,6 +126,12 @@ export class LiveApiService extends ApiService { return this.openWebsocket(config) } + openMetricsWebsocket$( + config: WebSocketSubjectConfig, + ): Observable { + return this.openWebsocket(config) + } + async getSystemTime( params: RR.GetSystemTimeReq, ): Promise { @@ -400,12 +406,6 @@ export class LiveApiService extends ApiService { return this.rpcRequest({ method: 'package.logs.follow', params }) } - async getPkgMetrics( - params: RR.GetPackageMetricsReq, - ): Promise { - return this.rpcRequest({ method: 'package.metrics', params }) - } - async installPackage( params: RR.InstallPackageReq, ): Promise { diff --git a/frontend/projects/ui/src/app/services/api/embassy-mock-api.service.ts b/frontend/projects/ui/src/app/services/api/embassy-mock-api.service.ts index 73696bdab..0c1ed4f96 100644 --- a/frontend/projects/ui/src/app/services/api/embassy-mock-api.service.ts +++ b/frontend/projects/ui/src/app/services/api/embassy-mock-api.service.ts @@ -16,7 +16,7 @@ import { PackageMainStatus, PackageState, } from 'src/app/services/patch-db/data-model' -import { BackupTargetType, CifsBackupTarget, RR } from './api.types' +import { BackupTargetType, Metrics, RR } from './api.types' import { parsePropertiesPermissive } from 'src/app/util/properties.util' import { Mock } from './api.fixures' import markdown from 'raw-loader!../../../../../shared/assets/markdown/md-sample.md' @@ -181,6 +181,19 @@ export class MockApiService extends ApiService { ) } + openMetricsWebsocket$( + config: WebSocketSubjectConfig, + ): Observable { + 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( params: RR.GetSystemTimeReq, ): Promise { @@ -268,14 +281,10 @@ export class MockApiService extends ApiService { params: RR.GetServerMetricsReq, ): Promise { await pauseFor(2000) - return Mock.getServerMetrics() - } - - async getPkgMetrics( - params: RR.GetServerMetricsReq, - ): Promise { - await pauseFor(2000) - return Mock.getAppMetrics() + return { + guid: 'iqudh37um-i38u3-34-a51b-jkhd783ein', + metrics: Mock.getMetrics(), + } } async updateServer(url?: string): Promise { diff --git a/frontend/projects/ui/src/app/services/time-service.ts b/frontend/projects/ui/src/app/services/time-service.ts index 2f0193da4..7439dea0f 100644 --- a/frontend/projects/ui/src/app/services/time-service.ts +++ b/frontend/projects/ui/src/app/services/time-service.ts @@ -1,63 +1,78 @@ import { Injectable } from '@angular/core' -import { - map, - shareReplay, - startWith, - switchMap, - take, - tap, -} from 'rxjs/operators' +import { map, startWith, switchMap } from 'rxjs/operators' import { PatchDB } from 'patch-db-client' import { DataModel } from './patch-db/data-model' import { ApiService } from './api/embassy-api.service' -import { combineLatest, from, timer } from 'rxjs' +import { combineLatest, from, Observable, timer } from 'rxjs' + +export interface TimeInfo { + systemStartTime: number + systemCurrentTime: number + systemUptime: { + days: number + hours: number + minutes: number + seconds: number + } +} @Injectable({ providedIn: 'root', }) export class TimeService { - private readonly startTimeMs$ = this.patch + private readonly systemStartTime$ = this.patch .watch$('server-info', 'system-start-time') - .pipe( - take(1), - map(startTime => new Date(startTime).valueOf()), - shareReplay(), - ) - - readonly systemTime$ = from(this.apiService.getSystemTime({})).pipe( - switchMap(utcStr => { - const dateObj = new Date(utcStr) - const msRemaining = (60 - dateObj.getSeconds()) * 1000 - dateObj.setSeconds(0) - const current = dateObj.valueOf() - return timer(msRemaining, 60000).pipe( - map(index => { - const incremented = index + 1 - const msToAdd = 60000 * incremented - return current + msToAdd - }), - startWith(current), - ) - }), - ) - - readonly systemUptime$ = combineLatest([ - this.startTimeMs$, - this.systemTime$, - ]).pipe( - map(([startTime, currentTime]) => { - const ms = currentTime - startTime - const days = Math.floor(ms / (24 * 60 * 60 * 1000)) - const daysms = ms % (24 * 60 * 60 * 1000) - const hours = Math.floor(daysms / (60 * 60 * 1000)) - const hoursms = ms % (60 * 60 * 1000) - const minutes = Math.floor(hoursms / (60 * 1000)) - return { days, hours, minutes } - }), - ) + .pipe(map(startTime => new Date(startTime).valueOf())) constructor( private readonly patch: PatchDB, private readonly apiService: ApiService, ) {} + + getTimeInfo$(): Observable { + return combineLatest([ + this.systemStartTime$.pipe(), + this.getSystemCurrentTime$(), + ]).pipe( + map(([systemStartTime, systemCurrentTime]) => ({ + systemStartTime, + systemCurrentTime, + systemUptime: this.getSystemUptime(systemStartTime, systemCurrentTime), + })), + ) + } + + private getSystemCurrentTime$() { + return from(this.apiService.getSystemTime({})).pipe( + switchMap(utcStr => { + const dateObj = new Date(utcStr) + const current = dateObj.valueOf() + return timer(0, 1000).pipe( + map(index => { + const incremented = index + 1 + const msToAdd = 1000 * incremented + return current + msToAdd + }), + startWith(current), + ) + }), + ) + } + + private getSystemUptime(systemStartTime: number, systemCurrentTime: number) { + const ms = systemCurrentTime - systemStartTime + + const days = Math.floor(ms / (24 * 60 * 60 * 1000)) + const daysms = ms % (24 * 60 * 60 * 1000) + + const hours = Math.floor(daysms / (60 * 60 * 1000)) + const hoursms = ms % (60 * 60 * 1000) + + const minutes = Math.floor(hoursms / (60 * 1000)) + const minutesms = ms % (60 * 1000) + + const seconds = Math.floor(minutesms / 1000) + + return { days, hours, minutes, seconds } + } }