From f3aaa816b0fcf70ba818a3b3d2828054c5a257e9 Mon Sep 17 00:00:00 2001 From: SquirrelDevelopper Date: Fri, 29 Nov 2024 16:06:03 +0100 Subject: [PATCH] Add disk usage stats handling in Dashboard This commit introduces the ability to track and display disk usage statistics, both used and free, in the Dashboard component. It updates the shared library, server, and client implementations to support numeric storage values to improve type safety and precision. Additionally, it --- .../Dashboard/Components/MainChartCard.tsx | 84 +++++++++++++++---- .../devices/devicestatsdashboard.validator.ts | 13 ++- server/src/data/database/model/DeviceStat.ts | 20 ++--- server/src/services/DeviceStatsUseCases.ts | 30 ++++++- shared-lib/src/enums/stats.ts | 4 +- shared-lib/src/types/api.ts | 10 +-- 6 files changed, 124 insertions(+), 37 deletions(-) diff --git a/client/src/pages/Dashboard/Components/MainChartCard.tsx b/client/src/pages/Dashboard/Components/MainChartCard.tsx index a5e31ce5..b5bb8052 100644 --- a/client/src/pages/Dashboard/Components/MainChartCard.tsx +++ b/client/src/pages/Dashboard/Components/MainChartCard.tsx @@ -18,28 +18,29 @@ import { TabsProps, Typography, } from 'antd'; -import QueueAnim from 'rc-queue-anim'; import React, { useEffect, useState, useCallback, useMemo } from 'react'; -import { API } from 'ssm-shared-lib'; +import { API, StatsType } from 'ssm-shared-lib'; import styles from '../Analysis.less'; const { RangePicker } = DatePicker; const MainChartCard: React.FC = () => { const { initialState } = useModel('@@initialState'); - const { currentUser }: { currentUser: API.CurrentUser } = initialState || {}; + const { currentUser }: { currentUser?: API.CurrentUser } = initialState || {}; const [loading, setLoading] = useState(false); - const [graphData, setGraphData] = useState([]); + const [graphData, setGraphData] = useState([]); const [topTenData, setTopTenData] = useState< - { name: string; value: number }[] + { name: string; value: number }[] | undefined >([]); const [devices, setDevices] = useState( currentUser?.devices?.overview ?.filter((e) => e.status !== Devicestatus.UNMANAGED) .map((e) => e.uuid) || [], ); - const [type, setType] = useState('cpu'); + const [type, setType] = useState( + StatsType.DeviceStatsType.CPU, + ); const [rangePickerValue, setRangePickerValue] = useState( getTimeDistance('year'), ); @@ -133,16 +134,16 @@ const MainChartCard: React.FC = () => { }, y: { labelFill: '#fff', - labelFormatter: (v: any) => `${v}%`, + labelFormatter: (v: any) => `${parseFloat(v)?.toFixed(2)}%`, }, }, tooltip: { channel: 'y', - valueFormatter: (d: string) => `${parseFloat(d).toFixed(2)}%`, + valueFormatter: (d: string) => `${parseFloat(d)?.toFixed(2)}%`, }, yAxis: { label: { - formatter: (v: number) => `${v.toFixed(2)}%`, + formatter: (v: number) => `${v?.toFixed(2)}%`, }, }, }), @@ -158,13 +159,13 @@ const MainChartCard: React.FC = () => { ); const handleTabChange = (key: string) => { - setType(key); + setType(key as StatsType.DeviceStatsType); }; const items: TabsProps['items'] = useMemo( () => [ { - key: 'cpu', + key: StatsType.DeviceStatsType.CPU, label: 'CPU', children: ( @@ -175,9 +176,11 @@ const MainChartCard: React.FC = () => {
-

CPU Average Ranking

+

+ CPU Usage % Average Ranking +

    - {topTenData.slice(0, 10).map((item, i) => ( + {topTenData?.slice(0, 10).map((item, i) => (
  • { {item.name} - {item.value.toFixed(2)}% + {item.value?.toFixed(2)}%
  • ))} @@ -205,7 +208,7 @@ const MainChartCard: React.FC = () => { ), }, { - key: 'memFree', + key: StatsType.DeviceStatsType.MEM_USED, label: 'MEM', children: ( @@ -216,9 +219,11 @@ const MainChartCard: React.FC = () => {
    -

    Average Memory Ranking

    +

    + Memory Usage % Average Ranking +

      - {topTenData.slice(0, 10).map((item, i) => ( + {topTenData?.slice(0, 10).map((item, i) => (
    • { {item.name} - {item.value.toFixed(2)}% + {item.value?.toFixed(2)}% + +
    • + ))} +
    +
    + +
    + ), + }, + { + key: StatsType.DeviceStatsType.DISK_USED, + label: 'DISK', + children: ( + + +
    + +
    + + +
    +

    + Disk Usage % Average Ranking +

    +
      + {topTenData?.slice(0, 10).map((item, i) => ( +
    • + + {i + 1} + + + {item.name} + + + {item.value?.toFixed(2)}%
    • ))} diff --git a/server/src/controllers/rest/devices/devicestatsdashboard.validator.ts b/server/src/controllers/rest/devices/devicestatsdashboard.validator.ts index fcc3c934..8fc732d5 100644 --- a/server/src/controllers/rest/devices/devicestatsdashboard.validator.ts +++ b/server/src/controllers/rest/devices/devicestatsdashboard.validator.ts @@ -1,4 +1,5 @@ import { body, param, query } from 'express-validator'; +import { StatsType } from 'ssm-shared-lib'; import validator from '../../../middlewares/Validator'; export const getDashboardAveragedStatsValidator = [ @@ -6,7 +7,11 @@ export const getDashboardAveragedStatsValidator = [ body('devices.*').isUUID().withMessage('Invalid uuid'), query('from').exists().notEmpty().isISO8601().withMessage('From in query is invalid'), query('to').exists().notEmpty().isISO8601().withMessage('From in query is invalid'), - param('type').exists().notEmpty().isString().withMessage('Type is invalid'), + param('type') + .exists() + .notEmpty() + .isIn(Object.values(StatsType.DeviceStatsType)) + .withMessage('Type is invalid'), validator, ]; @@ -15,6 +20,10 @@ export const getDashboardStatValidator = [ body('devices.*').isUUID().withMessage('Invalid uuid'), query('from').exists().notEmpty().isISO8601().withMessage('From in query is invalid'), query('to').exists().notEmpty().isISO8601().withMessage('From in query is invalid'), - param('type').exists().notEmpty().isString().withMessage('Type is invalid'), + param('type') + .exists() + .notEmpty() + .isIn(Object.values(StatsType.DeviceStatsType)) + .withMessage('Type is invalid'), validator, ]; diff --git a/server/src/data/database/model/DeviceStat.ts b/server/src/data/database/model/DeviceStat.ts index 640adddb..a7e853b8 100644 --- a/server/src/data/database/model/DeviceStat.ts +++ b/server/src/data/database/model/DeviceStat.ts @@ -6,11 +6,11 @@ export const COLLECTION_NAME = 'devicestats'; export default interface DeviceStat { device: Device; - storageTotalGb?: string; - storageUsedGb?: string; - storageFreeGb?: string; - storageUsedPercentage?: string; - storageFreePercentage?: string; + storageTotalGb?: number; + storageUsedGb?: number; + storageFreeGb?: number; + storageUsedPercentage?: number; + storageFreePercentage?: number; cpuUsage?: number; memTotalMb?: number; memTotalUsedMb?: number; @@ -31,23 +31,23 @@ const schema = new Schema( index: true, }, storageTotalGb: { - type: Schema.Types.String, + type: Schema.Types.Number, required: false, }, storageUsedGb: { - type: Schema.Types.String, + type: Schema.Types.Number, required: false, }, storageFreeGb: { - type: Schema.Types.String, + type: Schema.Types.Number, required: false, }, storageUsedPercentage: { - type: Schema.Types.String, + type: Schema.Types.Number, required: false, }, storageFreePercentage: { - type: Schema.Types.String, + type: Schema.Types.Number, required: false, }, cpuUsage: { diff --git a/server/src/services/DeviceStatsUseCases.ts b/server/src/services/DeviceStatsUseCases.ts index dc0fe891..e9c00cb8 100644 --- a/server/src/services/DeviceStatsUseCases.ts +++ b/server/src/services/DeviceStatsUseCases.ts @@ -94,6 +94,20 @@ async function getStatsByDevicesAndType( from, to, ); + case StatsType.DeviceStatsType.DISK_USED: + return await DeviceStatRepo.findStatsByDevicesAndType( + devices, + '$storageUsedPercentage', + from, + to, + ); + case StatsType.DeviceStatsType.DISK_FREE: + return await DeviceStatRepo.findStatsByDevicesAndType( + devices, + '$storageFreePercentage', + from, + to, + ); default: throw new Error('Unknown Type'); } @@ -103,7 +117,7 @@ async function getSingleAveragedStatsByDevicesAndType( devices: Device[], from: Date, to: Date, - type?: string, + type?: StatsType.DeviceStatsType, ): Promise<[{ value: string; name: string }] | null> { logger.info( `findSingleAveragedStatByDevicesAndType - type: ${type}, from: ${from}, to: ${to}, nb devices: ${devices.length}`, @@ -130,6 +144,20 @@ async function getSingleAveragedStatsByDevicesAndType( from, to, ); + case StatsType.DeviceStatsType.DISK_USED: + return await DeviceStatRepo.findSingleAveragedStatByDevicesAndType( + devices, + '$storageUsedPercentage', + from, + to, + ); + case StatsType.DeviceStatsType.DISK_FREE: + return await DeviceStatRepo.findSingleAveragedStatByDevicesAndType( + devices, + '$storageFreePercentage', + from, + to, + ); default: throw new Error('Unknown Type'); } diff --git a/shared-lib/src/enums/stats.ts b/shared-lib/src/enums/stats.ts index a0ebd59a..46df9d67 100644 --- a/shared-lib/src/enums/stats.ts +++ b/shared-lib/src/enums/stats.ts @@ -7,5 +7,7 @@ export enum DeviceStatsType { CPU = 'cpu', MEM_USED = 'memUsed', MEM_FREE = 'memFree', - CONTAINERS = 'containers' + CONTAINERS = 'containers', + DISK_USED = 'diskUsed', + DISK_FREE = 'diskFree' } diff --git a/shared-lib/src/types/api.ts b/shared-lib/src/types/api.ts index f619447f..e2e0cf1d 100644 --- a/shared-lib/src/types/api.ts +++ b/shared-lib/src/types/api.ts @@ -520,11 +520,11 @@ export type MemInfo = { }; export type DriveInfo = { - storageTotalGb?: string; - storageUsedGb?: string; - storageFreeGb?: string; - storageUsedPercentage?: string; - storageFreePercentage?: string; + storageTotalGb?: number; + storageUsedGb?: number; + storageFreeGb?: number; + storageUsedPercentage?: number; + storageFreePercentage?: number; }; export type DeviceInfo = {