Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEAT] Add disk usage stats handling in Dashboard #534

Merged
merged 1 commit into from
Nov 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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