mirror of
https://github.com/fankes/beszel.git
synced 2025-10-19 17:59:28 +08:00
Add temperature chart filtering (#430)
- Refactored ContainerFilterBar to accept a dynamic store prop. - Updated filtering logic in ContainerChart to be case-insensitive.
This commit is contained in:
@@ -170,7 +170,7 @@ export default memo(function ContainerChart({
|
|||||||
content={<ChartTooltipContent filter={filter} contentFormatter={toolTipFormatter} />}
|
content={<ChartTooltipContent filter={filter} contentFormatter={toolTipFormatter} />}
|
||||||
/>
|
/>
|
||||||
{Object.keys(chartConfig).map((key) => {
|
{Object.keys(chartConfig).map((key) => {
|
||||||
const filtered = filter && !key.includes(filter)
|
const filtered = filter && !key.toLowerCase().includes(filter.toLowerCase())
|
||||||
let fillOpacity = filtered ? 0.05 : 0.4
|
let fillOpacity = filtered ? 0.05 : 0.4
|
||||||
let strokeOpacity = filtered ? 0.1 : 1
|
let strokeOpacity = filtered ? 0.1 : 1
|
||||||
return (
|
return (
|
||||||
|
@@ -18,8 +18,11 @@ import {
|
|||||||
} from "@/lib/utils"
|
} from "@/lib/utils"
|
||||||
import { ChartData } from "@/types"
|
import { ChartData } from "@/types"
|
||||||
import { memo, useMemo } from "react"
|
import { memo, useMemo } from "react"
|
||||||
|
import { $temperatureFilter } from "@/lib/stores"
|
||||||
|
import { useStore } from "@nanostores/react"
|
||||||
|
|
||||||
export default memo(function TemperatureChart({ chartData }: { chartData: ChartData }) {
|
export default memo(function TemperatureChart({ chartData }: { chartData: ChartData }) {
|
||||||
|
const filter = useStore($temperatureFilter)
|
||||||
const { yAxisWidth, updateYAxisWidth } = useYAxisWidth()
|
const { yAxisWidth, updateYAxisWidth } = useYAxisWidth()
|
||||||
|
|
||||||
if (chartData.systemStats.length === 0) {
|
if (chartData.systemStats.length === 0) {
|
||||||
@@ -86,22 +89,28 @@ export default memo(function TemperatureChart({ chartData }: { chartData: ChartD
|
|||||||
<ChartTooltipContent
|
<ChartTooltipContent
|
||||||
labelFormatter={(_, data) => formatShortDate(data[0].payload.created)}
|
labelFormatter={(_, data) => formatShortDate(data[0].payload.created)}
|
||||||
contentFormatter={(item) => decimalString(item.value) + " °C"}
|
contentFormatter={(item) => decimalString(item.value) + " °C"}
|
||||||
// indicator="line"
|
filter={filter}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
{colors.map((key) => (
|
{colors.map((key) => {
|
||||||
<Line
|
const filtered = filter && !key.toLowerCase().includes(filter.toLowerCase())
|
||||||
key={key}
|
let strokeOpacity = filtered ? 0.1 : 1
|
||||||
dataKey={key}
|
return (
|
||||||
name={key}
|
<Line
|
||||||
type="monotoneX"
|
key={key}
|
||||||
dot={false}
|
dataKey={key}
|
||||||
strokeWidth={1.5}
|
name={key}
|
||||||
stroke={newChartData.colors[key]}
|
type="monotoneX"
|
||||||
isAnimationActive={false}
|
dot={false}
|
||||||
/>
|
strokeWidth={1.5}
|
||||||
))}
|
stroke={newChartData.colors[key]}
|
||||||
|
strokeOpacity={strokeOpacity}
|
||||||
|
activeDot={{ opacity: filtered ? 0 : 1 }}
|
||||||
|
isAnimationActive={false}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})}
|
||||||
{colors.length < 12 && <ChartLegend content={<ChartLegendContent />} />}
|
{colors.length < 12 && <ChartLegend content={<ChartLegendContent />} />}
|
||||||
</LineChart>
|
</LineChart>
|
||||||
</ChartContainer>
|
</ChartContainer>
|
||||||
|
@@ -1,6 +1,15 @@
|
|||||||
import { t } from "@lingui/core/macro"
|
import { t } from "@lingui/core/macro"
|
||||||
import { Plural, Trans } from "@lingui/react/macro"
|
import { Plural, Trans } from "@lingui/react/macro"
|
||||||
import { $systems, pb, $chartTime, $containerFilter, $userSettings, $direction, $maxValues } from "@/lib/stores"
|
import {
|
||||||
|
$systems,
|
||||||
|
pb,
|
||||||
|
$chartTime,
|
||||||
|
$containerFilter,
|
||||||
|
$userSettings,
|
||||||
|
$direction,
|
||||||
|
$maxValues,
|
||||||
|
$temperatureFilter,
|
||||||
|
} from "@/lib/stores"
|
||||||
import { ChartData, ChartTimes, ContainerStatsRecord, GPUData, SystemRecord, SystemStatsRecord } from "@/types"
|
import { ChartData, ChartTimes, ContainerStatsRecord, GPUData, SystemRecord, SystemStatsRecord } from "@/types"
|
||||||
import { ChartType, Os } from "@/lib/enums"
|
import { ChartType, Os } from "@/lib/enums"
|
||||||
import React, { lazy, memo, useCallback, useEffect, useMemo, useRef, useState } from "react"
|
import React, { lazy, memo, useCallback, useEffect, useMemo, useRef, useState } from "react"
|
||||||
@@ -219,7 +228,7 @@ export default function SystemDetail({ name }: { name: string }) {
|
|||||||
cache.set(cs_cache_key, containerData)
|
cache.set(cs_cache_key, containerData)
|
||||||
}
|
}
|
||||||
if (containerData.length) {
|
if (containerData.length) {
|
||||||
!containerFilterBar && setContainerFilterBar(<ContainerFilterBar />)
|
!containerFilterBar && setContainerFilterBar(<FilterBar />)
|
||||||
} else if (containerFilterBar) {
|
} else if (containerFilterBar) {
|
||||||
setContainerFilterBar(null)
|
setContainerFilterBar(null)
|
||||||
}
|
}
|
||||||
@@ -557,6 +566,7 @@ export default function SystemDetail({ name }: { name: string }) {
|
|||||||
grid={grid}
|
grid={grid}
|
||||||
title={t`Temperature`}
|
title={t`Temperature`}
|
||||||
description={t`Temperatures of system sensors`}
|
description={t`Temperatures of system sensors`}
|
||||||
|
cornerEl={<FilterBar store={$temperatureFilter} />}
|
||||||
>
|
>
|
||||||
<TemperatureChart chartData={chartData} />
|
<TemperatureChart chartData={chartData} />
|
||||||
</ChartCard>
|
</ChartCard>
|
||||||
@@ -654,12 +664,12 @@ export default function SystemDetail({ name }: { name: string }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function ContainerFilterBar() {
|
function FilterBar({ store = $containerFilter }: { store?: typeof $containerFilter }) {
|
||||||
const containerFilter = useStore($containerFilter)
|
const containerFilter = useStore(store)
|
||||||
const { t } = useLingui()
|
const { t } = useLingui()
|
||||||
|
|
||||||
const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
$containerFilter.set(e.target.value)
|
store.set(e.target.value)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -672,7 +682,7 @@ function ContainerFilterBar() {
|
|||||||
size="icon"
|
size="icon"
|
||||||
aria-label="Clear"
|
aria-label="Clear"
|
||||||
className="absolute right-1 top-1/2 -translate-y-1/2 h-7 w-7 text-gray-500 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-100"
|
className="absolute right-1 top-1/2 -translate-y-1/2 h-7 w-7 text-gray-500 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-100"
|
||||||
onClick={() => $containerFilter.set("")}
|
onClick={() => store.set("")}
|
||||||
>
|
>
|
||||||
<XIcon className="h-4 w-4" />
|
<XIcon className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
|
@@ -129,7 +129,7 @@ const ChartTooltipContent = React.forwardRef<
|
|||||||
|
|
||||||
React.useMemo(() => {
|
React.useMemo(() => {
|
||||||
if (filter) {
|
if (filter) {
|
||||||
payload = payload?.filter((item) => (item.name as string)?.includes(filter))
|
payload = payload?.filter((item) => (item.name as string)?.toLowerCase().includes(filter.toLowerCase()))
|
||||||
}
|
}
|
||||||
if (itemSorter) {
|
if (itemSorter) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
@@ -38,6 +38,9 @@ $userSettings.subscribe((value) => {
|
|||||||
/** Container chart filter */
|
/** Container chart filter */
|
||||||
export const $containerFilter = atom("")
|
export const $containerFilter = atom("")
|
||||||
|
|
||||||
|
/** Temperature chart filter */
|
||||||
|
export const $temperatureFilter = atom("")
|
||||||
|
|
||||||
/** Fallback copy to clipboard dialog content */
|
/** Fallback copy to clipboard dialog content */
|
||||||
export const $copyContent = atom("")
|
export const $copyContent = atom("")
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user