← marzneshin
/
nightly
/
dashboard/src/modules/users/components/widgets/user-nodes-usage-widget/index.tsx
https://dev.kronoz.win/r/marzneshin.git
dashboard/src/modules/users/components/widgets/user-nodes-usage-widget/index.tsx
· TypeScript
Blame
Raw
1import { FC } from 'react';
2import { useTranslation } from 'react-i18next';
3import {
4 SectionWidget,
5 ChartLegendContent,
6 ChartContainer,
7 Awaiting,
8 ChartLegend,
9 ChartTooltip,
10 ChartTooltipContent,
11} from "@marzneshin/common/components";
12import { Area, AreaChart, CartesianGrid, YAxis, XAxis } from "recharts"
13import { useState } from "react";
14import { UserNodesUsageWidgetProps } from "./types";
15import { dateXAxisTicks, useFromNowInterval, SelectDateView, ChartDateInterval } from "@marzneshin/libs/stats-charts";
16import { useChartConfig, useTransformData } from "./hooks";
17import { format as formatByte } from '@chbphone55/pretty-bytes';
18import { useUserNodeUsagesQuery } from "@marzneshin/modules/users";
19import { UsageGraphSkeleton } from "./skeleton"
20
21export const UserNodesUsageWidget: FC<UserNodesUsageWidgetProps> = ({
22 user,
23}) => {
24 const { t } = useTranslation();
25 const [timeRange, setTimeRange] = useState("1d")
26 const { start, end } = useFromNowInterval(timeRange as ChartDateInterval);
27 const { data, isPending } = useUserNodeUsagesQuery({ username: user.username, start, end })
28 const chartData = useTransformData(data);
29 const config = useChartConfig(data);
30 const [totalAmount, totalMetric] = formatByte(data.total);
31
32 return (
33 <Awaiting
34 Component={
35 <SectionWidget
36 title={<div className="hstack justify-between w-full">{t("page.users.settings.nodes-usage.title")}</div>}
37 description={t("page.users.settings.nodes-usage.desc")}
38 options={
39 <div className="vstack justify-end w-full">
40 <span className="text-lg leading-none sm:text-2xl w-full">
41 {totalAmount} {totalMetric}
42 </span>
43 <span className="text-sm flex justify-end text-muted-foreground w-full">
44 Total
45 </span>
46 </div>
47 }
48 footer={
49 <SelectDateView timeRange={timeRange} setTimeRange={setTimeRange} />
50 }
51 >
52 <ChartContainer
53 className="aspect-auto h-[320px] w-full"
54 config={config}>
55 <AreaChart
56 accessibilityLayer
57 data={chartData}
58 margin={{
59 left: 13,
60 top: 13,
61 right: 12,
62 }}
63 >
64 <CartesianGrid vertical={false} />
65 <YAxis
66 tickLine={false}
67 axisLine={false}
68 tickMargin={8}
69 tickFormatter={(value) => {
70 const [amount, metric] = formatByte(value)
71 return `${amount} ${metric}`
72 }}
73 />
74 <XAxis
75 dataKey="datetime"
76 tickLine={false}
77 axisLine={false}
78 tickMargin={8}
79 tickFormatter={(value) => dateXAxisTicks(value, timeRange as ChartDateInterval)}
80 />
81 <ChartTooltip
82 cursor={false}
83 content={
84 <ChartTooltipContent
85 indicator='line'
86 labelFormatter={(value) => {
87 return new Date(value).toLocaleDateString("en-US", {
88 month: "short",
89 day: "numeric",
90 year: "numeric",
91 hour: "numeric"
92 })
93 }}
94 valueFormatter={(value) => {
95 const [amount, metric] = formatByte(value as number)
96 return `${amount} ${metric}`
97 }}
98 />
99 }
100 />
101 <defs>
102 {data.node_usages.map(node =>
103 <linearGradient key={node.node_name} id={node.node_name} x1="0" y1="0" x2="0" y2="1">
104 <stop
105 offset="5%"
106 stopColor={config[node.node_name].color}
107 stopOpacity={0.1}
108 />
109 <stop
110 offset="95%"
111 stopColor={config[node.node_name].color}
112 stopOpacity={0.8}
113 />
114 </linearGradient>
115 )}
116 </defs>
117 {data.node_usages.map(node =>
118 <Area
119 dataKey={node.node_name}
120 key={node.node_name}
121 type="natural"
122 fill={config[node.node_name].color}
123 fillOpacity={0.4}
124 stackId={node.node_id}
125 stroke={config[node.node_name].color}
126 />
127 )}
128 <ChartLegend content={<ChartLegendContent />} />
129 </AreaChart>
130 </ChartContainer>
131 </SectionWidget>
132 }
133 Skeleton={<UsageGraphSkeleton />}
134 isFetching={isPending}
135 />
136 );
137};