Skip to content

Commit

Permalink
Merge pull request #534 from SquirrelCorporation/feat-disk-usage
Browse files Browse the repository at this point in the history
[FEAT] Add disk usage stats handling in Dashboard
  • Loading branch information
SquirrelDeveloper authored Nov 29, 2024
2 parents 6390d0c + f3aaa81 commit 5e9f9a4
Show file tree
Hide file tree
Showing 6 changed files with 124 additions and 37 deletions.
84 changes: 66 additions & 18 deletions client/src/pages/Dashboard/Components/MainChartCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<API.DeviceStat[] | undefined>([]);
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>(
StatsType.DeviceStatsType.CPU,
);
const [rangePickerValue, setRangePickerValue] = useState<any>(
getTimeDistance('year'),
);
Expand Down Expand Up @@ -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)}%`,
},
},
}),
Expand All @@ -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: (
<Row>
Expand All @@ -175,9 +176,11 @@ const MainChartCard: React.FC = () => {
</Col>
<Col xl={8} lg={12} md={12} sm={24} xs={24}>
<div className={styles.salesRank}>
<h4 className={styles.rankingTitle}>CPU Average Ranking</h4>
<h4 className={styles.rankingTitle}>
CPU Usage % Average Ranking
</h4>
<ul className={styles.rankingList}>
{topTenData.slice(0, 10).map((item, i) => (
{topTenData?.slice(0, 10).map((item, i) => (
<li
key={`${item.name}-${i}`}
className={styles.rankingItem}
Expand All @@ -194,7 +197,7 @@ const MainChartCard: React.FC = () => {
{item.name}
</span>
<span className={styles.rankingItemValue}>
{item.value.toFixed(2)}%
{item.value?.toFixed(2)}%
</span>
</li>
))}
Expand All @@ -205,7 +208,7 @@ const MainChartCard: React.FC = () => {
),
},
{
key: 'memFree',
key: StatsType.DeviceStatsType.MEM_USED,
label: 'MEM',
children: (
<Row>
Expand All @@ -216,9 +219,11 @@ const MainChartCard: React.FC = () => {
</Col>
<Col xl={8} lg={12} md={12} sm={24} xs={24}>
<div className={styles.salesRank}>
<h4 className={styles.rankingTitle}>Average Memory Ranking</h4>
<h4 className={styles.rankingTitle}>
Memory Usage % Average Ranking
</h4>
<ul className={styles.rankingList}>
{topTenData.slice(0, 10).map((item, i) => (
{topTenData?.slice(0, 10).map((item, i) => (
<li key={item.name}>
<span
className={`${styles.rankingItemNumber} ${i < 3 ? styles.active : ''}`}
Expand All @@ -232,7 +237,50 @@ const MainChartCard: React.FC = () => {
{item.name}
</span>
<span className={styles.rankingItemValue}>
{item.value.toFixed(2)}%
{item.value?.toFixed(2)}%
</span>
</li>
))}
</ul>
</div>
</Col>
</Row>
),
},
{
key: StatsType.DeviceStatsType.DISK_USED,
label: 'DISK',
children: (
<Row>
<Col xl={16} lg={12} md={12} sm={24} xs={24}>
<div className={styles.salesBar}>
<Line {...cpuConfig} />
</div>
</Col>
<Col xl={8} lg={12} md={12} sm={24} xs={24}>
<div className={styles.salesRank}>
<h4 className={styles.rankingTitle}>
Disk Usage % Average Ranking
</h4>
<ul className={styles.rankingList}>
{topTenData?.slice(0, 10).map((item, i) => (
<li
key={`${item.name}-${i}`}
className={styles.rankingItem}
>
<span
className={`${styles.rankingItemNumber} ${i < 3 ? styles.active : ''}`}
>
{i + 1}
</span>
<span
className={styles.rankingItemTitle}
title={item.name}
>
{item.name}
</span>
<span className={styles.rankingItemValue}>
{item.value?.toFixed(2)}%
</span>
</li>
))}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import { body, param, query } from 'express-validator';
import { StatsType } from 'ssm-shared-lib';
import validator from '../../../middlewares/Validator';

export const getDashboardAveragedStatsValidator = [
body('devices').exists().notEmpty().isArray().withMessage('Devices uuid in body required'),
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,
];

Expand All @@ -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,
];
20 changes: 10 additions & 10 deletions server/src/data/database/model/DeviceStat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -31,23 +31,23 @@ const schema = new Schema<DeviceStat>(
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: {
Expand Down
30 changes: 29 additions & 1 deletion server/src/services/DeviceStatsUseCases.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}
Expand All @@ -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}`,
Expand All @@ -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');
}
Expand Down
4 changes: 3 additions & 1 deletion shared-lib/src/enums/stats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
}
10 changes: 5 additions & 5 deletions shared-lib/src/types/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down

0 comments on commit 5e9f9a4

Please sign in to comment.