Move history from query data to ref and avoid rerenders
This commit is contained in:
@@ -1,6 +1,9 @@
|
|||||||
import { QueryClient, QueryClientProvider, useQuery, useQueryClient } from '@tanstack/react-query';
|
import { QueryClient, QueryClientProvider, useQuery } from '@tanstack/react-query';
|
||||||
|
import { useSetAtom } from 'jotai';
|
||||||
|
import { useEffect, useRef } from 'react';
|
||||||
import './App.css';
|
import './App.css';
|
||||||
import { fetchDynamicData, fetchHistoryData, fetchStaticData } from './api';
|
import { fetchDynamicData, fetchHistoryData, fetchStaticData } from './api';
|
||||||
|
import { historyAtom } from './atoms';
|
||||||
import { Cpu } from './components/chart-cards/cpu';
|
import { Cpu } from './components/chart-cards/cpu';
|
||||||
import { Disks } from './components/chart-cards/disks';
|
import { Disks } from './components/chart-cards/disks';
|
||||||
import { Memory } from './components/chart-cards/memory';
|
import { Memory } from './components/chart-cards/memory';
|
||||||
@@ -16,7 +19,8 @@ const refetchInterval = Number(import.meta.env.CLIENT_REFETCH_INTERVAL);
|
|||||||
const queryClient = new QueryClient();
|
const queryClient = new QueryClient();
|
||||||
|
|
||||||
const Main = () => {
|
const Main = () => {
|
||||||
const queryClient = useQueryClient();
|
const history = useRef<HistoryNormalized>(null!);
|
||||||
|
const setHistoryRef = useSetAtom(historyAtom);
|
||||||
const staticQuery = useQuery({
|
const staticQuery = useQuery({
|
||||||
queryKey: ['static'],
|
queryKey: ['static'],
|
||||||
queryFn: fetchStaticData,
|
queryFn: fetchStaticData,
|
||||||
@@ -26,28 +30,50 @@ const Main = () => {
|
|||||||
|
|
||||||
const historyQuery = useQuery({
|
const historyQuery = useQuery({
|
||||||
queryKey: ['history'],
|
queryKey: ['history'],
|
||||||
queryFn: fetchHistoryData,
|
queryFn: async () => {
|
||||||
|
const data = await fetchHistoryData();
|
||||||
|
const maxes = {} as HistoryNormalized['maxes'];
|
||||||
|
|
||||||
|
for (const key of ['net', 'disks', 'temps'] as const) {
|
||||||
|
maxes[key] = data.reduce((max, slice) => Math.max(max, ...slice[key]), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
history.current = {
|
||||||
|
data,
|
||||||
|
maxes,
|
||||||
|
};
|
||||||
|
return data;
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const dynamicQuery = useQuery({
|
const dynamicQuery = useQuery({
|
||||||
queryKey: ['dynamic'],
|
queryKey: ['dynamic'],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const data = await fetchDynamicData();
|
const data = await fetchDynamicData();
|
||||||
queryClient.setQueryData(['history'], (historyOld: HistorySlice[]) => {
|
|
||||||
const history = historyOld.slice();
|
if (history.current) {
|
||||||
while (history.length && history[0].timestamp < Date.now() - (serverSteps + 3) * serverRefreshInterval) {
|
while (
|
||||||
history.shift();
|
history.current.data.length &&
|
||||||
|
history.current.data[0].timestamp < Date.now() - (serverSteps + 3) * serverRefreshInterval
|
||||||
|
) {
|
||||||
|
history.current.data.shift();
|
||||||
}
|
}
|
||||||
|
|
||||||
history.push(data);
|
history.current.data.push(data);
|
||||||
return history;
|
|
||||||
});
|
for (const key of ['net', 'disks', 'temps'] as const) {
|
||||||
|
history.current.maxes[key] = history.current.data.reduce((max, slice) => Math.max(max, ...slice[key]), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
refetchInterval: refetchInterval,
|
refetchInterval: refetchInterval,
|
||||||
|
retry: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
useSetTheme(window.matchMedia('(prefers-color-scheme: dark)').matches);
|
useSetTheme(window.matchMedia('(prefers-color-scheme: dark)').matches);
|
||||||
|
useEffect(() => setHistoryRef(history), [setHistoryRef]);
|
||||||
|
|
||||||
const isLoading = staticQuery.isLoading || dynamicQuery.isLoading || historyQuery.isLoading;
|
const isLoading = staticQuery.isLoading || dynamicQuery.isLoading || historyQuery.isLoading;
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ type StaticData = {
|
|||||||
brand: string;
|
brand: string;
|
||||||
name: string;
|
name: string;
|
||||||
vendor_id: string;
|
vendor_id: string;
|
||||||
|
threads: number;
|
||||||
};
|
};
|
||||||
host_name: string;
|
host_name: string;
|
||||||
kernel_version: string;
|
kernel_version: string;
|
||||||
@@ -23,3 +24,8 @@ type HistorySlice = {
|
|||||||
temps: number[];
|
temps: number[];
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type HistoryNormalized = {
|
||||||
|
data: HistorySlice[];
|
||||||
|
maxes: Record<'net' | 'disks' | 'temps', number>;
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import { atom } from 'jotai';
|
import { atom } from 'jotai';
|
||||||
|
import type { MutableRefObject } from 'react';
|
||||||
|
|
||||||
export const highFpsAtom = atom(true);
|
export const highFpsAtom = atom(true);
|
||||||
export const siAtom = atom(false);
|
export const siAtom = atom(false);
|
||||||
|
export const historyAtom = atom<MutableRefObject<HistoryNormalized>>({ current: null! });
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Legend, type LegendProps } from '@/components/chart-cards/common/legend';
|
import { Legend, type LegendByDataProps, type LegendByKeyProps } from '@/components/chart-cards/common/legend';
|
||||||
import type { FormatOptions } from '@/utils/format';
|
import type { FormatOptions } from '@/utils/format';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import { CanvasChart } from './chart';
|
import { CanvasChart } from './chart';
|
||||||
@@ -6,7 +6,7 @@ import { CanvasChart } from './chart';
|
|||||||
type Props = {
|
type Props = {
|
||||||
title: ReactNode;
|
title: ReactNode;
|
||||||
subtitle?: ReactNode;
|
subtitle?: ReactNode;
|
||||||
legend?: LegendProps;
|
legend?: Omit<LegendByKeyProps, 'dataKey'> | LegendByDataProps;
|
||||||
formatOptions?: FormatOptions;
|
formatOptions?: FormatOptions;
|
||||||
domain?: [number, number];
|
domain?: [number, number];
|
||||||
hardDomain?: boolean;
|
hardDomain?: boolean;
|
||||||
@@ -22,7 +22,8 @@ export const ChartCard = ({ dataKey, legend, hueOffset = 0, title, subtitle, for
|
|||||||
|
|
||||||
{subtitle}
|
{subtitle}
|
||||||
|
|
||||||
{legend && (
|
{legend && (dataKey || 'values' in legend) && (
|
||||||
|
// @ts-expect-error: not inferable, but safe
|
||||||
<Legend
|
<Legend
|
||||||
hueOffset={hueOffset}
|
hueOffset={hueOffset}
|
||||||
formatOptions={formatOptions}
|
formatOptions={formatOptions}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
|
import { historyAtom } from '@/atoms';
|
||||||
import { useAnimationFrame } from '@/hooks/use-animation-frame';
|
import { useAnimationFrame } from '@/hooks/use-animation-frame';
|
||||||
import { getFillColor, getStrokeColor } from '@/utils/colors';
|
import { getFillColor, getStrokeColor } from '@/utils/colors';
|
||||||
import type { FormatOptions } from '@/utils/format';
|
import type { FormatOptions } from '@/utils/format';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useAtomValue } from 'jotai';
|
||||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import './index.css';
|
import './index.css';
|
||||||
import { YAxis } from './y-axis';
|
import { YAxis } from './y-axis';
|
||||||
|
|
||||||
@@ -24,21 +25,9 @@ const xFromTimestamp = (timestamp: number, width: number) =>
|
|||||||
|
|
||||||
export const CanvasChart = ({ total, hueOffset = 0, domain, hardDomain, dataKey, formatOptions }: Props) => {
|
export const CanvasChart = ({ total, hueOffset = 0, domain, hardDomain, dataKey, formatOptions }: Props) => {
|
||||||
const canvasRef = useRef<HTMLCanvasElement>(null!);
|
const canvasRef = useRef<HTMLCanvasElement>(null!);
|
||||||
const { data: historyData } = useQuery<HistorySlice[]>({ queryKey: ['history'] });
|
const history = useAtomValue(historyAtom);
|
||||||
const targetMax = useMemo(() => {
|
const [lockedTargetMax, setLockedTargetMax] = useState(domain?.[1] ?? 0);
|
||||||
if (domain && hardDomain) {
|
const max = useRef(lockedTargetMax);
|
||||||
return domain[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
const historyMax = (historyData ?? []).reduce((max, slice) => Math.max(max, ...slice[dataKey]), 0);
|
|
||||||
|
|
||||||
if (!domain || historyMax > domain[1]) {
|
|
||||||
return historyMax;
|
|
||||||
}
|
|
||||||
|
|
||||||
return domain[1];
|
|
||||||
}, [domain, hardDomain, historyData, dataKey]);
|
|
||||||
const max = useRef(targetMax);
|
|
||||||
|
|
||||||
const [width, setWidth] = useState(640);
|
const [width, setWidth] = useState(640);
|
||||||
const [height, setHeight] = useState(480);
|
const [height, setHeight] = useState(480);
|
||||||
@@ -61,10 +50,28 @@ export const CanvasChart = ({ total, hueOffset = 0, domain, hardDomain, dataKey,
|
|||||||
// Redraw chart
|
// Redraw chart
|
||||||
useAnimationFrame(() => {
|
useAnimationFrame(() => {
|
||||||
const ctx = canvasRef.current.getContext('2d');
|
const ctx = canvasRef.current.getContext('2d');
|
||||||
if (!ctx || !historyData) {
|
if (!ctx || !history.current) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const targetMax = (() => {
|
||||||
|
if (domain && hardDomain) {
|
||||||
|
return domain[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
const historyMax = history.current.maxes[dataKey as keyof typeof history.current.maxes] ?? 0;
|
||||||
|
|
||||||
|
if (!domain || historyMax > domain[1]) {
|
||||||
|
return historyMax;
|
||||||
|
}
|
||||||
|
|
||||||
|
return domain[1];
|
||||||
|
})();
|
||||||
|
|
||||||
|
if (lockedTargetMax !== targetMax) {
|
||||||
|
setLockedTargetMax(targetMax);
|
||||||
|
}
|
||||||
|
|
||||||
if (!hardDomain) {
|
if (!hardDomain) {
|
||||||
max.current = (4 * max.current + targetMax) / 5;
|
max.current = (4 * max.current + targetMax) / 5;
|
||||||
}
|
}
|
||||||
@@ -96,16 +103,16 @@ export const CanvasChart = ({ total, hueOffset = 0, domain, hardDomain, dataKey,
|
|||||||
|
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.moveTo(-xMargin, height);
|
ctx.moveTo(-xMargin, height);
|
||||||
ctx.lineTo(-xMargin, height - (height * (historyData[0][dataKey][i] ?? 0)) / max.current);
|
ctx.lineTo(-xMargin, height - (height * (history.current.data[0][dataKey][i] ?? 0)) / max.current);
|
||||||
|
|
||||||
for (const { timestamp, [dataKey]: values } of historyData) {
|
for (const { timestamp, [dataKey]: values } of history.current.data) {
|
||||||
const x = xFromTimestamp(timestamp, width);
|
const x = xFromTimestamp(timestamp, width);
|
||||||
const y = height - (height * (values[i] ?? 0)) / max.current;
|
const y = height - (height * (values[i] ?? 0)) / max.current;
|
||||||
|
|
||||||
ctx.lineTo(x, y);
|
ctx.lineTo(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.lineTo(width + xMargin, height - (height * (historyData[0][dataKey][i] ?? 0)) / max.current);
|
ctx.lineTo(width + xMargin, height - (height * (history.current.data[0][dataKey][i] ?? 0)) / max.current);
|
||||||
ctx.lineTo(width + xMargin, height);
|
ctx.lineTo(width + xMargin, height);
|
||||||
|
|
||||||
if (type === 'fill') ctx.fill();
|
if (type === 'fill') ctx.fill();
|
||||||
@@ -121,7 +128,7 @@ export const CanvasChart = ({ total, hueOffset = 0, domain, hardDomain, dataKey,
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='chart'>
|
<div className='chart'>
|
||||||
<YAxis max={targetMax} formatOptions={formatOptions} />
|
<YAxis max={lockedTargetMax} formatOptions={formatOptions} />
|
||||||
|
|
||||||
<div className='canvas-wrapper'>
|
<div className='canvas-wrapper'>
|
||||||
<canvas ref={canvasRef} width={width} height={height} />
|
<canvas ref={canvasRef} width={width} height={height} />
|
||||||
|
|||||||
@@ -1,25 +1,24 @@
|
|||||||
import { getTextColor } from '@/utils/colors';
|
import { getTextColor } from '@/utils/colors';
|
||||||
import { type FormatOptions, formatValue } from '@/utils/format';
|
import { type FormatOptions, formatValue } from '@/utils/format';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { useMemo } from 'react';
|
import { memo, useMemo } from 'react';
|
||||||
import './index.css';
|
import './index.css';
|
||||||
|
|
||||||
type LegendCommonProps = {
|
export type LegendCommonProps = {
|
||||||
labels: string[];
|
labels: string[];
|
||||||
formatOptions?: FormatOptions;
|
formatOptions?: FormatOptions;
|
||||||
hueOffset?: number;
|
hueOffset?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type LegendByKeyProps = LegendCommonProps & {
|
export type LegendByKeyProps = LegendCommonProps & {
|
||||||
dataKey: Exclude<keyof HistorySlice, 'timestamp'>;
|
dataKey: Exclude<keyof HistorySlice, 'timestamp'>;
|
||||||
|
totals?: number[];
|
||||||
};
|
};
|
||||||
|
|
||||||
type LegendByDataProps = LegendCommonProps & {
|
export type LegendByDataProps = LegendCommonProps & {
|
||||||
values: string[] | number[];
|
values: string[] | number[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type LegendProps = LegendByDataProps | LegendByKeyProps;
|
|
||||||
|
|
||||||
export const LegendByData = ({ labels, formatOptions, hueOffset = 0, values }: LegendByDataProps) => (
|
export const LegendByData = ({ labels, formatOptions, hueOffset = 0, values }: LegendByDataProps) => (
|
||||||
<div className='legend-wrapper'>
|
<div className='legend-wrapper'>
|
||||||
{labels.map((label, index) => (
|
{labels.map((label, index) => (
|
||||||
@@ -36,15 +35,26 @@ export const LegendByData = ({ labels, formatOptions, hueOffset = 0, values }: L
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
export const LegendByKey = ({ dataKey, ...rest }: LegendByKeyProps) => {
|
export const LegendByKey = memo(({ dataKey, totals, formatOptions, ...rest }: LegendByKeyProps) => {
|
||||||
const { data: historyData } = useQuery<HistorySlice[]>({ queryKey: ['history'] });
|
const { data: dynamicData } = useQuery<HistorySlice>({ queryKey: ['dynamic'] });
|
||||||
const values = useMemo(() => historyData?.at(-1)?.[dataKey], [historyData, dataKey]);
|
const values = useMemo(
|
||||||
|
() =>
|
||||||
|
dynamicData?.[dataKey]?.map((value, index) => {
|
||||||
|
if (totals) {
|
||||||
|
return `${formatValue(value, formatOptions)} / ${formatValue(totals[index], formatOptions)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return formatValue(value, formatOptions);
|
||||||
|
}),
|
||||||
|
[dynamicData, dataKey, formatOptions, totals],
|
||||||
|
);
|
||||||
|
|
||||||
if (values) {
|
if (values) {
|
||||||
return <LegendByData {...rest} values={values} />;
|
return <LegendByData {...rest} values={values} />;
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
export const Legend = (props: LegendProps) => {
|
export const Legend = (props: LegendByDataProps | LegendByKeyProps) => {
|
||||||
if ('values' in props) {
|
if ('values' in props) {
|
||||||
return <LegendByData {...props} />;
|
return <LegendByData {...props} />;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,30 +3,25 @@ import { ChartCard } from './common/card';
|
|||||||
|
|
||||||
export const Cpu = () => {
|
export const Cpu = () => {
|
||||||
const { data: staticData } = useQuery<StaticData>({ queryKey: ['static'] });
|
const { data: staticData } = useQuery<StaticData>({ queryKey: ['static'] });
|
||||||
const { data: historyData } = useQuery<HistorySlice[]>({ queryKey: ['history'] });
|
|
||||||
|
|
||||||
if (!staticData || !historyData) {
|
if (!staticData) {
|
||||||
return <div />;
|
return <div />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const total_cpus = historyData[0].cpu.length;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
!!total_cpus && (
|
<ChartCard
|
||||||
<ChartCard
|
title='CPU'
|
||||||
title='CPU'
|
subtitle={
|
||||||
subtitle={
|
<h3>
|
||||||
<h3>
|
{staticData.cpu.brand}
|
||||||
{staticData.cpu.brand}
|
<small>{` (${staticData.cpu.threads} threads)`}</small>
|
||||||
<small>{` (${total_cpus} threads)`}</small>
|
</h3>
|
||||||
</h3>
|
}
|
||||||
}
|
domain={[0, 100]}
|
||||||
domain={[0, 100]}
|
hardDomain
|
||||||
hardDomain
|
formatOptions={{ prefix: false, units: '%' }}
|
||||||
formatOptions={{ prefix: false, units: '%' }}
|
dataKey={'cpu'}
|
||||||
dataKey={'cpu'}
|
total={staticData.cpu.threads}
|
||||||
total={total_cpus}
|
/>
|
||||||
/>
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,20 +1,13 @@
|
|||||||
import { siAtom } from '@/atoms';
|
import { siAtom } from '@/atoms';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
|
||||||
import { useAtomValue } from 'jotai';
|
import { useAtomValue } from 'jotai';
|
||||||
import { ChartCard } from './common/card';
|
import { ChartCard } from './common/card';
|
||||||
|
|
||||||
export const Disks = () => {
|
export const Disks = () => {
|
||||||
const { data: historyData } = useQuery<HistorySlice[]>({ queryKey: ['history'] });
|
|
||||||
const isSi = useAtomValue(siAtom);
|
const isSi = useAtomValue(siAtom);
|
||||||
|
|
||||||
if (!historyData) {
|
|
||||||
return <div />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ChartCard
|
<ChartCard
|
||||||
title='Disk activity'
|
title='Disk activity'
|
||||||
// @ts-expect-error: write a better union later
|
|
||||||
legend={{
|
legend={{
|
||||||
labels: ['Read', 'Write'],
|
labels: ['Read', 'Write'],
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -1,26 +1,13 @@
|
|||||||
import { siAtom } from '@/atoms';
|
import { siAtom } from '@/atoms';
|
||||||
import { formatValue } from '@/utils/format';
|
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { useAtomValue } from 'jotai';
|
import { useAtomValue } from 'jotai';
|
||||||
import { useMemo } from 'react';
|
|
||||||
import { ChartCard } from './common/card';
|
import { ChartCard } from './common/card';
|
||||||
|
|
||||||
export const Memory = () => {
|
export const Memory = () => {
|
||||||
const { data: staticData } = useQuery<StaticData>({ queryKey: ['static'] });
|
const { data: staticData } = useQuery<StaticData>({ queryKey: ['static'] });
|
||||||
const { data: historyData } = useQuery<HistorySlice[]>({ queryKey: ['history'] });
|
|
||||||
const isSi = useAtomValue(siAtom);
|
const isSi = useAtomValue(siAtom);
|
||||||
const formatOptions = { units: 'B', ...(isSi && { si: true }) };
|
|
||||||
|
|
||||||
const formatedTotals = useMemo(() => {
|
if (!staticData) {
|
||||||
if (!staticData) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
return [formatValue(staticData.total_memory, formatOptions), formatValue(staticData.total_swap, formatOptions)];
|
|
||||||
}, [staticData, formatOptions]);
|
|
||||||
|
|
||||||
const last = useMemo(() => historyData?.at(-1), [historyData]);
|
|
||||||
|
|
||||||
if (!staticData || !historyData || !last) {
|
|
||||||
return <div />;
|
return <div />;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,15 +15,12 @@ export const Memory = () => {
|
|||||||
<ChartCard
|
<ChartCard
|
||||||
title='Memory'
|
title='Memory'
|
||||||
legend={{
|
legend={{
|
||||||
values: [
|
totals: [staticData.total_memory, staticData.total_swap],
|
||||||
`${formatValue(last.mem[0], formatOptions)} / ${formatedTotals[0]}`,
|
|
||||||
`${formatValue(last.mem[1], formatOptions)} / ${formatedTotals[1]}`,
|
|
||||||
],
|
|
||||||
labels: ['Memory', 'Swap'],
|
labels: ['Memory', 'Swap'],
|
||||||
}}
|
}}
|
||||||
domain={[0, Math.max(staticData.total_memory, staticData.total_swap)]}
|
domain={[0, Math.max(staticData.total_memory, staticData.total_swap)]}
|
||||||
hardDomain
|
hardDomain
|
||||||
formatOptions={formatOptions}
|
formatOptions={{ units: 'B', ...(isSi && { si: true }) }}
|
||||||
dataKey={'mem'}
|
dataKey={'mem'}
|
||||||
total={2}
|
total={2}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,20 +1,13 @@
|
|||||||
import { siAtom } from '@/atoms';
|
import { siAtom } from '@/atoms';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
|
||||||
import { useAtomValue } from 'jotai';
|
import { useAtomValue } from 'jotai';
|
||||||
import { ChartCard } from './common/card';
|
import { ChartCard } from './common/card';
|
||||||
|
|
||||||
export const Network = () => {
|
export const Network = () => {
|
||||||
const { data: historyData } = useQuery<HistorySlice[]>({ queryKey: ['history'] });
|
|
||||||
const isSi = useAtomValue(siAtom);
|
const isSi = useAtomValue(siAtom);
|
||||||
|
|
||||||
if (!historyData) {
|
|
||||||
return <div />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ChartCard
|
<ChartCard
|
||||||
title='Network'
|
title='Network'
|
||||||
// @ts-expect-error: write a better union later
|
|
||||||
legend={{
|
legend={{
|
||||||
labels: ['Down', 'Up'],
|
labels: ['Down', 'Up'],
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -3,16 +3,14 @@ import { ChartCard } from './common/card';
|
|||||||
|
|
||||||
export const Temps = () => {
|
export const Temps = () => {
|
||||||
const { data: staticData } = useQuery<StaticData>({ queryKey: ['static'] });
|
const { data: staticData } = useQuery<StaticData>({ queryKey: ['static'] });
|
||||||
const { data: historyData } = useQuery<HistorySlice[]>({ queryKey: ['history'] });
|
|
||||||
|
|
||||||
if (!staticData || !historyData) {
|
if (!staticData) {
|
||||||
return <div />;
|
return <div />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ChartCard
|
<ChartCard
|
||||||
title='Temperatures'
|
title='Temperatures'
|
||||||
// @ts-expect-error: write a better union later
|
|
||||||
legend={{
|
legend={{
|
||||||
labels: staticData.components,
|
labels: staticData.components,
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -151,6 +151,7 @@ async fn static_sysinfo_get() -> impl IntoResponse {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let components = Components::new_with_refreshed_list();
|
let components = Components::new_with_refreshed_list();
|
||||||
|
let cpus = sys.cpus();
|
||||||
|
|
||||||
Response::builder()
|
Response::builder()
|
||||||
.header(axum::http::header::ORIGIN, "*")
|
.header(axum::http::header::ORIGIN, "*")
|
||||||
@@ -167,9 +168,10 @@ async fn static_sysinfo_get() -> impl IntoResponse {
|
|||||||
"total_memory": sys.total_memory(),
|
"total_memory": sys.total_memory(),
|
||||||
"total_swap": sys.total_swap(),
|
"total_swap": sys.total_swap(),
|
||||||
"cpu": {
|
"cpu": {
|
||||||
"name": sys.cpus()[0].name(),
|
"name": cpus[0].name(),
|
||||||
"vendor_id": sys.cpus()[0].vendor_id(),
|
"vendor_id": cpus[0].vendor_id(),
|
||||||
"brand": sys.cpus()[0].brand(),
|
"brand": cpus[0].brand(),
|
||||||
|
"threads": cpus.len(),
|
||||||
},
|
},
|
||||||
"components": components
|
"components": components
|
||||||
.iter()
|
.iter()
|
||||||
|
|||||||
Reference in New Issue
Block a user