diff --git a/photon-client/package.json b/photon-client/package.json index cf2485b2d0..1dd42013c8 100644 --- a/photon-client/package.json +++ b/photon-client/package.json @@ -18,6 +18,7 @@ "@mdi/font": "^7.4.47", "@msgpack/msgpack": "^3.1.2", "axios": "^1.11.0", + "echarts": "^6.0.0", "jspdf": "^3.0.1", "pinia": "^3.0.2", "three": "^0.178.0", diff --git a/photon-client/pnpm-lock.yaml b/photon-client/pnpm-lock.yaml index b93de81445..ace715fd64 100644 --- a/photon-client/pnpm-lock.yaml +++ b/photon-client/pnpm-lock.yaml @@ -20,6 +20,9 @@ importers: axios: specifier: ^1.11.0 version: 1.11.0 + echarts: + specifier: ^6.0.0 + version: 6.0.0 jspdf: specifier: ^3.0.1 version: 3.0.1 @@ -840,6 +843,9 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} + echarts@6.0.0: + resolution: {integrity: sha512-Tte/grDQRiETQP4xz3iZWSvoHrkCQtwqd6hs+mifXcjrCuo2iKWbajFObuLJVBlDIJlOzgQPd1hsaKt/3+OMkQ==} + entities@4.5.0: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} @@ -1385,6 +1391,9 @@ packages: peerDependencies: typescript: '>=4.8.4' + tslib@2.3.0: + resolution: {integrity: sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==} + tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} @@ -1525,6 +1534,9 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + zrender@6.0.0: + resolution: {integrity: sha512-41dFXEEXuJpNecuUQq6JlbybmnHaqqpGlbH1yxnA5V9MMP4SbohSVZsJIwz+zdjQXSSlR1Vc34EgH1zxyTDvhg==} + snapshots: '@babel/helper-string-parser@7.27.1': {} @@ -2035,7 +2047,7 @@ snapshots: typescript: 5.8.3 vue: 3.5.13(typescript@5.8.3) - '@vuetify/loader-shared@2.1.0(vue@3.5.13(typescript@5.8.3))(vuetify@3.8.3)': + '@vuetify/loader-shared@2.1.0(vue@3.5.13(typescript@5.8.3))(vuetify@3.8.3(typescript@5.8.3)(vite-plugin-vuetify@2.1.1)(vue@3.5.13(typescript@5.8.3)))': dependencies: upath: 2.0.1 vue: 3.5.13(typescript@5.8.3) @@ -2188,6 +2200,11 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 + echarts@6.0.0: + dependencies: + tslib: 2.3.0 + zrender: 6.0.0 + entities@4.5.0: {} es-define-property@1.0.1: {} @@ -2746,6 +2763,8 @@ snapshots: dependencies: typescript: 5.8.3 + tslib@2.3.0: {} + tslib@2.8.1: {} type-check@0.4.0: @@ -2781,7 +2800,7 @@ snapshots: vite-plugin-vuetify@2.1.1(vite@7.0.5(@types/node@22.15.14)(sass@1.89.2))(vue@3.5.13(typescript@5.8.3))(vuetify@3.8.3): dependencies: - '@vuetify/loader-shared': 2.1.0(vue@3.5.13(typescript@5.8.3))(vuetify@3.8.3) + '@vuetify/loader-shared': 2.1.0(vue@3.5.13(typescript@5.8.3))(vuetify@3.8.3(typescript@5.8.3)(vite-plugin-vuetify@2.1.1)(vue@3.5.13(typescript@5.8.3))) debug: 4.4.0 upath: 2.0.1 vite: 7.0.5(@types/node@22.15.14)(sass@1.89.2) @@ -2851,3 +2870,7 @@ snapshots: xml-name-validator@4.0.0: {} yocto-queue@0.1.0: {} + + zrender@6.0.0: + dependencies: + tslib: 2.3.0 diff --git a/photon-client/src/components/settings/DeviceCard.vue b/photon-client/src/components/settings/DeviceCard.vue new file mode 100644 index 0000000000..abd8b31dc5 --- /dev/null +++ b/photon-client/src/components/settings/DeviceCard.vue @@ -0,0 +1,662 @@ +@ -0,0 +1,565 @@ + + + + + diff --git a/photon-client/src/components/settings/DeviceControlCard.vue b/photon-client/src/components/settings/DeviceControlCard.vue deleted file mode 100644 index 76783f6dcb..0000000000 --- a/photon-client/src/components/settings/DeviceControlCard.vue +++ /dev/null @@ -1,462 +0,0 @@ - - - - - diff --git a/photon-client/src/components/settings/MetricsCard.vue b/photon-client/src/components/settings/MetricsCard.vue deleted file mode 100644 index 952e7c4ad0..0000000000 --- a/photon-client/src/components/settings/MetricsCard.vue +++ /dev/null @@ -1,292 +0,0 @@ - - - - - diff --git a/photon-client/src/components/settings/MetricsChart.vue b/photon-client/src/components/settings/MetricsChart.vue new file mode 100644 index 0000000000..2006749c0b --- /dev/null +++ b/photon-client/src/components/settings/MetricsChart.vue @@ -0,0 +1,210 @@ + + + + + diff --git a/photon-client/src/stores/StateStore.ts b/photon-client/src/stores/StateStore.ts index 2c4e7edabf..79d52a360c 100644 --- a/photon-client/src/stores/StateStore.ts +++ b/photon-client/src/stores/StateStore.ts @@ -16,6 +16,11 @@ export interface NTConnectionStatus { clients?: number; } +interface NetworkUsageEntry { + time: number; + usage: number; +} + interface StateStore { backendConnected: boolean; websocket?: AutoReconnectingWebsocket; @@ -24,6 +29,7 @@ interface StateStore { sidebarFolded: boolean; logMessages: LogMessage[]; currentCameraUniqueName: string; + networkUsageHistory: NetworkUsageEntry[]; backendResults: Record; multitagResultBuffer: Record; @@ -62,6 +68,7 @@ export const useStateStore = defineStore("state", { localStorage.getItem("sidebarFolded") === null ? false : localStorage.getItem("sidebarFolded") === "true", logMessages: [], currentCameraUniqueName: Object.keys(cameraStore.cameras)[0], + networkUsageHistory: [], backendResults: { 0: { @@ -165,6 +172,10 @@ export const useStateStore = defineStore("state", { color: data.color, timeout: data.timeout || 2000 }; + }, + addNetworkUsageHistory(bytes: number) { + // this.networkUsageHistory.push({ time: Date.now(), usage: bytes / 1e6 }); + // while (this.networkUsageHistory.length > MAX_METRIC_HISTORY) this.networkUsageHistory.shift(); } } }); diff --git a/photon-client/src/stores/settings/GeneralSettingsStore.ts b/photon-client/src/stores/settings/GeneralSettingsStore.ts index 28b0f7ad03..5559995ce2 100644 --- a/photon-client/src/stores/settings/GeneralSettingsStore.ts +++ b/photon-client/src/stores/settings/GeneralSettingsStore.ts @@ -10,15 +10,26 @@ import { NetworkConnectionType } from "@/types/SettingTypes"; import { useStateStore } from "@/stores/StateStore"; import axios from "axios"; import type { WebsocketSettingsUpdate } from "@/types/WebsocketDataTypes"; +import { ref } from "vue"; + +interface MetricsEntry { + time: number; + metrics: MetricData; +} interface GeneralSettingsStore { general: GeneralSettings; network: NetworkSettings; lighting: LightingSettings; metrics: MetricData; + metricsHistory: MetricsEntry[]; currentFieldLayout; } +const MAX_METRIC_HISTORY = 60; +const UPDATE_INTERVAL_MS = 900; +const updateTimeElapsed = ref(true); + export const useSettingsStore = defineStore("settings", { state: (): GeneralSettingsStore => ({ general: { @@ -66,6 +77,7 @@ export const useSettingsStore = defineStore("settings", { ipAddress: undefined, uptime: undefined }, + metricsHistory: [], currentFieldLayout: { field: { length: 16.4592, @@ -100,6 +112,14 @@ export const useSettingsStore = defineStore("settings", { ipAddress: data.ipAddress || undefined, uptime: data.uptime || undefined }; + if (updateTimeElapsed.value) { + updateTimeElapsed.value = false; + setTimeout(() => (updateTimeElapsed.value = true), UPDATE_INTERVAL_MS); + + const now = Date.now(); + this.metricsHistory.push({ time: now, metrics: this.metrics }); + while (this.metricsHistory.length > MAX_METRIC_HISTORY) this.metricsHistory.shift(); + } }, updateGeneralSettingsFromWebsocket(data: WebsocketSettingsUpdate) { this.general = { diff --git a/photon-client/src/types/SettingTypes.ts b/photon-client/src/types/SettingTypes.ts index 59fa0d39a8..2e47a08eed 100644 --- a/photon-client/src/types/SettingTypes.ts +++ b/photon-client/src/types/SettingTypes.ts @@ -36,6 +36,7 @@ export interface MetricData { npuUsage?: number[]; ipAddress?: string; uptime?: number; + // networkUsage?: number; } export enum NetworkConnectionType { diff --git a/photon-client/src/views/GeneralSettingsView.vue b/photon-client/src/views/GeneralSettingsView.vue index 09a2ae08c4..134407e382 100644 --- a/photon-client/src/views/GeneralSettingsView.vue +++ b/photon-client/src/views/GeneralSettingsView.vue @@ -1,24 +1,21 @@