diff --git a/app/adomin/create_stats_view_config.ts b/app/adomin/create_stats_view_config.ts index 93e6b3a..07d2fb8 100644 --- a/app/adomin/create_stats_view_config.ts +++ b/app/adomin/create_stats_view_config.ts @@ -31,6 +31,21 @@ export interface StatsViewConfig { * https://tabler.io/icons */ icon?: string + /** + * How to display the stats + * Use the 'name' property of the stats to define where to put the component on the grid, and it's size + * + * Example: + * +```ts +const gridTemplateAreas = ` +"stat-name-1 stat-name-2" +"stat-name-3 stat-name-3" +` +``` + * if you need a different layout for small screens, you can use pass an object with "normal" and "sm" property + */ + gridTemplateAreas?: string | { normal: string; sm: string } } type ChartDataRow = [string, number] @@ -39,7 +54,7 @@ type ChartMultipleSeriesDataRow = { name: string; data: ChartDataRow[]; color?: type ChartRowData = ChartMultipleSeriesDataRow[] | ChartDataRow[] -interface ChartKickOptions { +interface ChartkickOptions { /** Title of x axis */ xtitle?: string /** Title of y axis */ @@ -96,11 +111,22 @@ interface ChartKickOptions { empty?: string } -interface AdominStat { +interface ChartkickStat extends AdominStatBase { /** * Type of the chart to display */ type: 'pie' | 'bar' | 'column' | 'line' | 'area' + /** + * Options for the chart + */ + options?: ChartkickOptions + /** + * function to fetch the data to displayed in the chart + */ + dataFetcher: () => Promise +} + +interface AdominStatBase { /** * Label of the stat, displayed in the frontend */ @@ -111,20 +137,41 @@ interface AdominStat { * (e.g. in the react key prop) */ name: string +} + +export interface KpiStatOptions { + /** + * If true, the value should be a number between 0-100 and will be displayed as a percentage + * @default false + */ + isPercentage?: boolean + /** + * Color of the mui CircularProgress + */ + color?: string +} + +export interface KpiStat extends AdominStatBase { + /** + * Type of the chart to display + */ + type: 'kpi' /** * function to fetch the data to displayed in the chart */ - dataFetcher: () => Promise + dataFetcher: () => Promise /** * Options for the chart */ - options?: ChartKickOptions + options?: KpiStatOptions } +export type AdominStat = ChartkickStat | KpiStat + export type StatsViewConfigStaticOptions = Omit export const createStatsViewConfig = (options: StatsViewConfigStaticOptions): StatsViewConfig => { - const { name, stats, label, visibilityCheck, isHidden, icon } = options + const { name, stats, label, visibilityCheck, isHidden, icon, gridTemplateAreas } = options return { type: 'stats', @@ -134,5 +181,6 @@ export const createStatsViewConfig = (options: StatsViewConfigStaticOptions): St visibilityCheck, isHidden, icon, + gridTemplateAreas, } } diff --git a/app/adomin/routes/stats/get_stat_config.ts b/app/adomin/routes/stats/get_stat_config.ts index 2e22377..6afc415 100644 --- a/app/adomin/routes/stats/get_stat_config.ts +++ b/app/adomin/routes/stats/get_stat_config.ts @@ -46,7 +46,7 @@ export const getStatConfigRoute = async (ctx: HttpContext) => { return response.notFound({ error: `View '${viewString}' not found` }) } - const { label, name, isHidden, visibilityCheck } = statConfig + const { label, name, isHidden, visibilityCheck, type, gridTemplateAreas, icon } = statConfig const visibilityCheckResult = await computeRightsCheck(ctx, visibilityCheck) @@ -59,5 +59,8 @@ export const getStatConfigRoute = async (ctx: HttpContext) => { label, isHidden: isHidden ?? false, stats: frontendStatConfig, + type, + gridTemplateAreas, + icon, } } diff --git a/app/adomin/routes/stats/get_cumulative_table_data.ts b/app/adomin/routes/stats/helpers/get_cumulative_table_data.ts similarity index 100% rename from app/adomin/routes/stats/get_cumulative_table_data.ts rename to app/adomin/routes/stats/helpers/get_cumulative_table_data.ts diff --git a/app/adomin/routes/stats/group_by_helpers.ts b/app/adomin/routes/stats/helpers/group_by_helpers.ts similarity index 100% rename from app/adomin/routes/stats/group_by_helpers.ts rename to app/adomin/routes/stats/helpers/group_by_helpers.ts diff --git a/app/adomin/routes/stats/helpers/instance_metrics.ts b/app/adomin/routes/stats/helpers/instance_metrics.ts new file mode 100644 index 0000000..22ea669 --- /dev/null +++ b/app/adomin/routes/stats/helpers/instance_metrics.ts @@ -0,0 +1,61 @@ +import { Duration } from 'luxon' +import os from 'node:os' + +const LOAD_AVG_TYPES = ['1m', '5m', '15m'] as const + +type LoadAvgType = (typeof LOAD_AVG_TYPES)[number] + +const LOAD_AVG_BY_TYPE: Record = { + '1m': 0, + '5m': 1, + '15m': 2, +} + +export const getCpuUsageEstimate = (loadAvgType: LoadAvgType) => { + const index = LOAD_AVG_BY_TYPE[loadAvgType] + const loadAvg = os.loadavg()[index] + const numCpus = os.cpus().length + const cpuUsagePercentage = (loadAvg / numCpus) * 100 + + return roundWithNDigits(cpuUsagePercentage, 2) +} + +export const getMemoryUsage = () => { + const freeMemory = os.freemem() + const totalMemory = os.totalmem() + const memoryUsed = totalMemory - freeMemory + const memoryUsagePercentage = (memoryUsed / totalMemory) * 100 + + return { + used: getGBFromBytes(memoryUsed), + total: getGBFromBytes(totalMemory), + percentage: roundWithNDigits(memoryUsagePercentage, 2), + } +} + +export const getUptime = () => { + const sysUptimeInSeconds = os.uptime() + const uptimeDuration = Duration.fromObject({ seconds: sysUptimeInSeconds }) + + const days = Math.floor(uptimeDuration.as('days')) + const hours = Math.floor(uptimeDuration.shiftTo('hours').hours % 24) + const minutes = Math.floor(uptimeDuration.shiftTo('minutes').minutes % 60) + + return { + days, + hours, + minutes, + } +} + +function roundWithNDigits(num: number, n: number) { + const factor = Math.pow(10, n) + return Math.round(num * factor) / factor +} + +function getGBFromBytes(bytes: number) { + const BYTES_TO_GB_RATIO = 1_000_000_000 + const value = roundWithNDigits(bytes / BYTES_TO_GB_RATIO, 1) + + return value +} diff --git a/app/test_adomin_config.ts b/app/test_adomin_config.ts index 48b2d8d..20932d7 100644 --- a/app/test_adomin_config.ts +++ b/app/test_adomin_config.ts @@ -1,8 +1,13 @@ +/* eslint-disable unicorn/no-await-expression-member */ import { AdominViewConfig } from '#adomin/adomin_config.types' import { createFolderViewConfig } from '#adomin/create_folder_view_config' import { createModelViewConfig } from '#adomin/create_model_view_config' import { createStatsViewConfig } from '#adomin/create_stats_view_config' -import { groupByDate, groupByDayOfWeek, groupByHour } from '#adomin/routes/stats/group_by_helpers' +import { + groupByDate, + groupByDayOfWeek, + groupByHour, +} from '#adomin/routes/stats/helpers/group_by_helpers' import Idea from '#models/idea' import Profile from '#models/profile' import Test from '#models/test' @@ -268,6 +273,44 @@ const FAKE_STATS_CONFIG = createStatsViewConfig({ icon: 'chart-bar', }) +const KPI_STATS_CONFIG = createStatsViewConfig({ + label: 'KPI stats', + name: 'kpiStats', + stats: [ + { + type: 'kpi', + label: 's1', + name: 's1', + dataFetcher: async () => '54h', + }, + { + type: 'kpi', + label: 's2', + name: 's2', + dataFetcher: async () => 88, + options: { isPercentage: true }, + }, + { + type: 'column', + label: 's3', + name: 's3', + dataFetcher: async () => [ + ['a', 15], + ['b', 20], + ['c', 44], + ], + }, + ], + gridTemplateAreas: { + normal: `"s1 s2" + "s3 s3"`, + sm: `"s1" + "s2" + "s3"`, + }, + icon: 'chart-bar', +}) + const FOLDER_FOUR = createFolderViewConfig({ label: 'Dossier 4', name: 'folder4', @@ -279,7 +322,7 @@ const FOLDER_FOUR = createFolderViewConfig({ createFolderViewConfig({ label: 'Dossier 6', name: 'folder6', - views: [FAKE_STATS_CONFIG], + views: [FAKE_STATS_CONFIG, KPI_STATS_CONFIG], icon: 'folder', }), ], diff --git a/docs/src/content/docs/reference/views/stats/index.mdx b/docs/src/content/docs/reference/views/stats/index.mdx index 088ee32..08610b5 100644 --- a/docs/src/content/docs/reference/views/stats/index.mdx +++ b/docs/src/content/docs/reference/views/stats/index.mdx @@ -350,7 +350,10 @@ import { FileTree } from '@astrojs/starlight/components'; - create_stats_view_config.ts [Open on GitHub](https://github.com/galadrimteam/adomin/blob/main/app/adomin/create_stats_view_config.ts) - routes - stats - - group_by_helpers.ts [Open on GitHub](https://github.com/galadrimteam/adomin/blob/main/app/adomin/routes/stats/group_by_helpers.ts) + - helpers + - group_by_helpers.ts [Open on GitHub](https://github.com/galadrimteam/adomin/blob/main/app/adomin/routes/stats/helpers/group_by_helpers.ts) + - get_cumulative_table_data.ts [Open on GitHub](https://github.com/galadrimteam/adomin/blob/main/app/adomin/routes/stats/helpers/get_cumulative_table_data.ts) + - instance_metrics.ts [Open on GitHub](https://github.com/galadrimteam/adomin/blob/main/app/adomin/routes/stats/helpers/instance_metrics.ts) diff --git a/tsconfig.json b/tsconfig.json index c4957a4..dd3b2ee 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -22,5 +22,6 @@ "#config/*": ["./config/*.js"], "#adomin/*": ["./app/adomin/*.js"] } - } + }, + "exclude": ["docs"] }