diff --git a/ui/src/app/pages/apps-routes/app-metrics/app-metrics.page.html b/ui/src/app/pages/apps-routes/app-metrics/app-metrics.page.html index 322d76f74..7fc6a0176 100644 --- a/ui/src/app/pages/apps-routes/app-metrics/app-metrics.page.html +++ b/ui/src/app/pages/apps-routes/app-metrics/app-metrics.page.html @@ -37,4 +37,23 @@ + + + + + Service Metrics + + + + + + {{ metric.key }} + + + {{ metric.value.value }} {{ metric.value.unit }} + + + + + diff --git a/ui/src/app/pages/apps-routes/app-metrics/app-metrics.page.ts b/ui/src/app/pages/apps-routes/app-metrics/app-metrics.page.ts index 953788227..0255f9650 100644 --- a/ui/src/app/pages/apps-routes/app-metrics/app-metrics.page.ts +++ b/ui/src/app/pages/apps-routes/app-metrics/app-metrics.page.ts @@ -1,8 +1,11 @@ import { Component, ViewChild } from '@angular/core' import { ActivatedRoute } from '@angular/router' import { IonContent } from '@ionic/angular' +import { Metric } from 'src/app/services/api/api-types' +import { ApiService } from 'src/app/services/api/api.service' import { PackageDataEntry } from 'src/app/services/patch-db/data-model' import { PatchDbModel } from 'src/app/services/patch-db/patch-db.service' +import { pauseFor } from 'src/app/util/misc.util' @Component({ selector: 'app-metrics', @@ -10,18 +13,50 @@ import { PatchDbModel } from 'src/app/services/patch-db/patch-db.service' styleUrls: ['./app-metrics.page.scss'], }) export class AppMetricsPage { + pkgId: string pkg: PackageDataEntry + going = false + metrics: Metric @ViewChild(IonContent) content: IonContent constructor ( private readonly route: ActivatedRoute, private readonly patch: PatchDbModel, + private readonly apiService: ApiService, ) { } ngOnInit () { - const pkgId = this.route.snapshot.paramMap.get('pkgId') - this.pkg = this.patch.data['package-data'][pkgId] + this.pkgId = this.route.snapshot.paramMap.get('pkgId') + this.pkg = this.patch.data['package-data'][this.pkgId] + + this.startDaemon() + } + + ngOnDestroy () { + this.stopDaemon() + } + + async startDaemon (): Promise { + this.going = true + while (this.going) { + await this.getMetrics() + await pauseFor(250) + } + } + + stopDaemon () { + this.going = false + } + + async getMetrics (): Promise { + try { + this.metrics = await this.apiService.getPkgMetrics({ id: this.pkgId}) + } catch (e) { + this.stopDaemon() + await pauseFor(1000) + this.startDaemon() + } } ngAfterViewInit () { diff --git a/ui/src/app/pages/server-routes/server-metrics/server-metrics.page.ts b/ui/src/app/pages/server-routes/server-metrics/server-metrics.page.ts index e2c91f316..d2e1e680a 100644 --- a/ui/src/app/pages/server-routes/server-metrics/server-metrics.page.ts +++ b/ui/src/app/pages/server-routes/server-metrics/server-metrics.page.ts @@ -19,10 +19,8 @@ export class ServerMetricsPage { ) { } ngOnInit () { - this.getMetrics().then(() => { - this.loading = false - this.startDaemon() - }) + this.loading = false + this.startDaemon() } ngOnDestroy () { @@ -32,8 +30,8 @@ export class ServerMetricsPage { async startDaemon (): Promise { this.going = true while (this.going) { - await pauseFor(250) await this.getMetrics() + await pauseFor(250) } } @@ -44,18 +42,6 @@ export class ServerMetricsPage { async getMetrics (): Promise { try { this.metrics = await this.apiService.getServerMetrics({ }) - - // @TODO keeping code in case naive approach (above) has issues - // Object.keys(metrics).forEach(outerKey => { - // if (!this.metrics[outerKey]) { - // console.log('outer keys') - // this.metrics[outerKey] = metrics[outerKey] - // } else { - // Object.entries(metrics[outerKey]).forEach(([key, value]) => { - // this.metrics[outerKey][key] = value - // }) - // } - // }) } catch (e) { console.error(e) this.error = e.message diff --git a/ui/src/app/services/api/api-types.ts b/ui/src/app/services/api/api-types.ts index 948e1ffa9..34eb17f90 100644 --- a/ui/src/app/services/api/api-types.ts +++ b/ui/src/app/services/api/api-types.ts @@ -109,6 +109,9 @@ export module RR { export type GetPackageLogsReq = { id: string, before?: string } // package.logs export type GetPackageLogsRes = Log[] + export type GetPackageMetricsReq = { id: string } // package.metrics + export type GetPackageMetricsRes = Metric + export type InstallPackageReq = WithExpire<{ id: string, version: string }> // package.install export type InstallPackageRes = WithRevision @@ -231,6 +234,13 @@ export interface Metrics { } } +export interface Metric { + [key: string]: { + value: string | number | null + unit?: string + } +} + export interface DiskInfo { [id: string]: DiskInfoEntry } diff --git a/ui/src/app/services/api/api.service.ts b/ui/src/app/services/api/api.service.ts index c0b258fce..c9ebc311c 100644 --- a/ui/src/app/services/api/api.service.ts +++ b/ui/src/app/services/api/api.service.ts @@ -38,6 +38,8 @@ export abstract class ApiService implements Source, Http { abstract getServerMetrics (params: RR.GetServerMetricsReq): Promise + abstract getPkgMetrics (params: RR.GetPackageMetricsReq): Promise + protected abstract updateServerRaw (params: RR.UpdateServerReq): Promise updateServer = (params: RR.UpdateServerReq) => this.syncResponse( () => this.updateServerRaw(params), diff --git a/ui/src/app/services/api/live-api.service.ts b/ui/src/app/services/api/live-api.service.ts index 24a538285..ed5576591 100644 --- a/ui/src/app/services/api/live-api.service.ts +++ b/ui/src/app/services/api/live-api.service.ts @@ -141,6 +141,10 @@ export class LiveApiService extends ApiService { return this.http.rpcRequest( { method: 'package.logs', params }) } + async getPkgMetrics (params: RR.GetPackageMetricsReq): Promise { + return this.http.rpcRequest({ method: 'package.metrics', params }) + } + async installPackageRaw (params: RR.InstallPackageReq): Promise { return this.http.rpcRequest({ method: 'package.install', params }) } diff --git a/ui/src/app/services/api/mock-api.service.ts b/ui/src/app/services/api/mock-api.service.ts index afe2297c2..5efe3b4a4 100644 --- a/ui/src/app/services/api/mock-api.service.ts +++ b/ui/src/app/services/api/mock-api.service.ts @@ -1,23 +1,35 @@ import { Injectable } from '@angular/core' import { pauseFor } from '../../util/misc.util' import { ApiService } from './api.service' -import { PatchOp } from 'patch-db-client' -import { PackageDataEntry, PackageMainStatus, PackageState, ServerStatus } from 'src/app/services/patch-db/data-model' -import { RR, WithRevision } from './api-types' +import { Observable } from 'rxjs' +import { PatchOp, Store, Update } from 'patch-db-client' +import { DataModel, PackageDataEntry, PackageMainStatus, PackageState, ServerStatus } from 'src/app/services/patch-db/data-model' +import { RR } from './api-types' import { parsePropertiesPermissive } from 'src/app/util/properties.util' import { Mock } from './mock-app-fixures' import { HttpService } from '../http.service' import markdown from 'raw-loader!src/assets/markdown/md-sample.md' -import { revision } from '@start9labs/emver' @Injectable() export class MockApiService extends ApiService { + sequence = 0 welcomeAck = false constructor ( private readonly http: HttpService, ) { super() } + // every time a patch is returned from the mock, we override its sequence to be 1 more than the last sequence in the patch-db as provided by `o`. + watch$ (store: Store): Observable> { + store.sequence$.subscribe(seq => { + console.log('INCOMING: ', seq) + if (this.sequence < seq) { + this.sequence = seq + } + }) + return super.watch$() + } + async getStatic (url: string): Promise { await pauseFor(2000) return markdown @@ -86,16 +98,27 @@ export class MockApiService extends ApiService { return Mock.getServerMetrics() } + async getPkgMetrics (params: RR.GetServerMetricsReq): Promise { + await pauseFor(2000) + return Mock.getAppMetrics() + } + async updateServerRaw (params: RR.UpdateServerReq): Promise { await pauseFor(2000) - const patch = [ - { - op: PatchOp.REPLACE, - path: '/server-info/status', - value: ServerStatus.Updating, + return { + response: null, + revision: { + id: this.nextSequence(), + patch: [ + { + op: PatchOp.REPLACE, + path: '/server-info/status', + value: ServerStatus.Updating, + }, + ], + expireId: null, }, - ] - return this.http.rpcRequest({ method: 'db.patch', params: { patch } }) + } } async restartServer (params: RR.RestartServerReq): Promise { @@ -119,31 +142,39 @@ export class MockApiService extends ApiService { async setRegistryRaw (params: RR.SetRegistryReq): Promise { await pauseFor(2000) - const patch = [ - { - op: PatchOp.REPLACE, - path: '/server-info/registry', - value: params.url, + return { + response: null, + revision: { + id: this.nextSequence(), + patch: [ + { + op: PatchOp.REPLACE, + path: '/server-info/registry', + value: params.url, + }, + ], + expireId: null, }, - ] - return this.http.rpcRequest({ method: 'db.patch', params: { patch } }) + } } // notification async getNotificationsRaw (params: RR.GetNotificationsReq): Promise { await pauseFor(2000) - const patch = [ - { - op: PatchOp.REPLACE, - path: '/server-info/unread-notification-count', - value: 0, - }, - ] - const { revision } = await this.http.rpcRequest({ method: 'db.patch', params: { patch } }) as WithRevision return { response: Mock.Notifications, - revision, + revision: { + id: this.nextSequence(), + patch: [ + { + op: PatchOp.REPLACE, + path: '/server-info/unread-notification-count', + value: 0, + }, + ], + expireId: null, + }, } } @@ -161,36 +192,48 @@ export class MockApiService extends ApiService { async connectWifiRaw (params: RR.ConnectWifiReq): Promise { await pauseFor(2000) - const patch = [ - { - op: PatchOp.REPLACE, - path: '/server-info/wifi/selected', - value: params.ssid, + return { + response: null, + revision: { + id: this.nextSequence(), + patch: [ + { + op: PatchOp.REPLACE, + path: '/server-info/wifi/selected', + value: params.ssid, + }, + { + op: PatchOp.REPLACE, + path: '/server-info/wifi/connected', + value: params.ssid, + }, + ], + expireId: null, }, - { - op: PatchOp.REPLACE, - path: '/server-info/wifi/connected', - value: params.ssid, - }, - ] - return this.http.rpcRequest({ method: 'db.patch', params: { patch } }) + } } async deleteWifiRaw (params: RR.DeleteWifiReq): Promise { await pauseFor(2000) - const patch = [ - { - op: PatchOp.REPLACE, - path: '/server-info/wifi/selected', - value: null, + return { + response: null, + revision: { + id: this.nextSequence(), + patch: [ + { + op: PatchOp.REPLACE, + path: '/server-info/wifi/selected', + value: null, + }, + { + op: PatchOp.REPLACE, + path: '/server-info/wifi/connected', + value: null, + }, + ], + expireId: null, }, - { - op: PatchOp.REPLACE, - path: '/server-info/wifi/connected', - value: null, - }, - ] - return this.http.rpcRequest({ method: 'db.patch', params: { patch } }) + } } // ssh @@ -214,14 +257,20 @@ export class MockApiService extends ApiService { async createBackupRaw (params: RR.CreateBackupReq): Promise { await pauseFor(2000) - const patch = [ - { - op: PatchOp.REPLACE, - path: '/server-info/status', - value: ServerStatus.BackingUp, + return { + response: null, + revision: { + id: this.nextSequence(), + patch: [ + { + op: PatchOp.REPLACE, + path: '/server-info/status', + value: ServerStatus.BackingUp, + }, + ], + expireId: null, }, - ] - return this.http.rpcRequest({ method: 'db.patch', params: { patch } }) + } } async restoreBackupRaw (params: RR.RestoreBackupReq): Promise { @@ -270,14 +319,20 @@ export class MockApiService extends ApiService { 'read-complete': false, }, } - const patch = [ - { - op: PatchOp.ADD, - path: `/package-data/${params.id}`, - value: pkg, + return { + response: null, + revision: { + id: this.nextSequence(), + patch: [ + { + op: PatchOp.ADD, + path: `/package-data/${params.id}`, + value: pkg, + }, + ], + expireId: null, }, - ] - return this.http.rpcRequest({ method: 'db.patch', params: { patch } }) + } } async dryUpdatePackage (params: RR.DryUpdatePackageReq): Promise { @@ -297,31 +352,43 @@ export class MockApiService extends ApiService { async setPackageConfigRaw (params: RR.SetPackageConfigReq): Promise { await pauseFor(2000) - const patch = [ - { - op: PatchOp.REPLACE, - path: `/package-data/${params.id}/installed/status/configured`, - value: true, + return { + response: null, + revision: { + id: this.nextSequence(), + patch: [ + { + op: PatchOp.REPLACE, + path: `/package-data/${params.id}/installed/status/configured`, + value: true, + }, + { + op: PatchOp.REPLACE, + path: `/package-data/${params.id}/installed/status/main/status`, + value: PackageMainStatus.Running, + }, + ], + expireId: null, }, - { - op: PatchOp.REPLACE, - path: `/package-data/${params.id}/installed/status/main/status`, - value: PackageMainStatus.Running, - }, - ] - return this.http.rpcRequest({ method: 'db.patch', params: { patch } }) + } } async restorePackageRaw (params: RR.RestorePackageReq): Promise { await pauseFor(2000) - const patch = [ - { - op: PatchOp.REPLACE, - path: `/package-data/${params.id}/installed/status/main/status`, - value: PackageMainStatus.Restoring, + return { + response: null, + revision: { + id: this.nextSequence(), + patch: [ + { + op: PatchOp.REPLACE, + path: `/package-data/${params.id}/installed/status/main/status`, + value: PackageMainStatus.Restoring, + }, + ], + expireId: null, }, - ] - return this.http.rpcRequest({ method: 'db.patch', params: { patch } }) + } } async executePackageAction (params: RR.ExecutePackageActionReq): Promise { @@ -336,14 +403,20 @@ export class MockApiService extends ApiService { async startPackageRaw (params: RR.StartPackageReq): Promise { await pauseFor(2000) - const patch = [ - { - op: PatchOp.REPLACE, - path: `/package-data/${params.id}/installed/status/main/status`, - value: PackageMainStatus.Running, + return { + response: null, + revision: { + id: this.nextSequence(), + patch: [ + { + op: PatchOp.REPLACE, + path: `/package-data/${params.id}/installed/status/main/status`, + value: PackageMainStatus.Running, + }, + ], + expireId: null, }, - ] - return this.http.rpcRequest({ method: 'db.patch', params: { patch } }) + } } async dryStopPackage (params: RR.DryStopPackageReq): Promise { @@ -353,26 +426,20 @@ export class MockApiService extends ApiService { async stopPackageRaw (params: RR.StopPackageReq): Promise { await pauseFor(2000) - const patch = [ - { - op: PatchOp.REPLACE, - path: `/package-data/${params.id}/installed/status/main/status`, - value: PackageMainStatus.Stopping, + return { + response: null, + revision: { + id: this.nextSequence(), + patch: [ + { + op: PatchOp.REPLACE, + path: `/package-data/${params.id}/installed/status/main/status`, + value: PackageMainStatus.Stopping, + }, + ], + expireId: null, }, - ] - const res = await this.http.rpcRequest>({ method: 'db.patch', params: { patch } }) - setTimeout(() => { - const patch = [ - { - op: PatchOp.REPLACE, - path: `/package-data/${params.id}/installed/status/main/status`, - value: PackageMainStatus.Stopped, - }, - ] - this.http.rpcRequest>({ method: 'db.patch', params: { patch } }) - }, 2000) - - return res + } } async dryRemovePackage (params: RR.DryRemovePackageReq): Promise { @@ -382,14 +449,20 @@ export class MockApiService extends ApiService { async removePackageRaw (params: RR.RemovePackageReq): Promise { await pauseFor(2000) - const patch = [ - { - op: PatchOp.REPLACE, - path: `/package-data/${params.id}/state`, - value: PackageState.Removing, + return { + response: null, + revision: { + id: this.nextSequence(), + patch: [ + { + op: PatchOp.REPLACE, + path: `/package-data/${params.id}/state`, + value: PackageState.Removing, + }, + ], + expireId: null, }, - ] - return this.http.rpcRequest({ method: 'db.patch', params: { patch } }) + } } async dryConfigureDependency (params: RR.DryConfigureDependencyReq): Promise { @@ -420,4 +493,10 @@ export class MockApiService extends ApiService { await pauseFor(2000) return Mock.AvailableShow[params.id][params.version || 'latest'] } + + private nextSequence () { + console.log('next') + this.sequence++ + return this.sequence + } } diff --git a/ui/src/app/services/api/mock-app-fixures.ts b/ui/src/app/services/api/mock-app-fixures.ts index a0d13e168..0dbbbf708 100644 --- a/ui/src/app/services/api/mock-app-fixures.ts +++ b/ui/src/app/services/api/mock-app-fixures.ts @@ -1,5 +1,5 @@ import { DependencyErrorType, DockerIoFormat, Manifest, PackageDataEntry, PackageMainStatus, PackageState, ServerStatus } from 'src/app/services/patch-db/data-model' -import { NotificationLevel, RR, ServerNotification, ServerNotifications } from './api-types' +import { Metric, NotificationLevel, RR, ServerNotification, ServerNotifications } from './api-types' export module Mock { @@ -888,6 +888,25 @@ export module Mock { } } + 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: RR.GetServerLogsRes = [ { timestamp: '2019-12-26T14:20:30.872Z',