Compare commits

...
Author SHA1 Message Date
Thomas Camlong 678e24cd2b fix: refine Beszel stats period controls 2026-08-10 09:26:51 +02:00
3 changed files with 79 additions and 58 deletions

No files matched your search

@@ -17,7 +17,7 @@ import beszelSystemStats from "./img/beszel-system-stats.png";
<WidgetHeader widget={beszelSystemStatsWidget} categories={["System Monitoring", "Charts"]} />
This widget displays time-series charts for a selected Beszel system, including CPU, memory, disk, network, and Docker container metrics. The Disk Usage chart combines the root disk and every extra filesystem reported by Beszel. You can use the realtime Live view or select a historical period ranging from 1 hour to 30 days. Live view keeps a one-minute window and pauses while the browser tab is hidden. If the live stream drops, the widget now shows the connection failure state immediately and can switch back to historical data.
This widget displays time-series charts for a selected Beszel system, including CPU, memory, disk, network, and Docker container metrics. The Disk Usage chart combines the root disk and every extra filesystem reported by Beszel. Hover over or focus the widget controls to switch between the realtime Live view and historical periods ranging from 1 hour to 30 days. Live view keeps a one-minute window and pauses while the browser tab is hidden. If the live stream drops, the widget now shows the connection failure state immediately and can switch back to historical data.
### Screenshots
@@ -1,28 +1,38 @@
.beszelStatsSystemToggle {
position: absolute;
top: 6px;
left: 6px;
z-index: 1;
.beszelStatsControls {
opacity: 0;
transition: opacity 0.15s ease;
transition: opacity 150ms ease-out;
pointer-events: none;
}
.beszelStatsContainer:hover .beszelStatsSystemToggle,
.beszelStatsContainer:focus-within .beszelStatsSystemToggle {
.beszelStatsContainer:hover .beszelStatsControls,
.beszelStatsContainer:focus-within .beszelStatsControls {
opacity: 1;
pointer-events: auto;
}
.beszelStatsSystemToggle {
max-width: 50%;
flex-shrink: 0;
}
.beszelStatsPeriodToggle {
flex: 1;
min-width: 0;
}
.beszelStatsPeriodButton {
flex: 1;
}
@media (any-pointer: coarse) {
.beszelStatsSystemToggle {
.beszelStatsControls {
opacity: 1;
pointer-events: auto;
}
}
@media (prefers-reduced-motion: reduce) {
.beszelStatsSystemToggle {
.beszelStatsControls {
transition: none;
}
}
@@ -1,7 +1,7 @@
"use client";
import { useCallback, useMemo, useRef } from "react";
import { Box, Button, Center, Loader, Menu, ScrollArea, SegmentedControl, Select, Stack, Text } from "@mantine/core";
import { Box, Button, Center, Group, Loader, Menu, ScrollArea, Select, Stack, Text } from "@mantine/core";
import { IconQuestionMark, IconServer, IconServerOff } from "@tabler/icons-react";
import { getQueryKey } from "@trpc/react-query";
@@ -185,54 +185,65 @@ export default function BeszelSystemStatsWidget({
style={{ pointerEvents: isEditMode ? "none" : undefined }}
>
<Stack gap="md" p="sm">
{!isEditMode && systems.length > 1 && (
<Menu position="bottom-start" withArrow shadow="md" withinPortal>
<Menu.Target>
<Button
variant="default"
size="compact-xs"
leftSection={<IconServer size={14} />}
className={classes.beszelStatsSystemToggle}
{!isEditMode && (
<Group gap="xs" wrap="nowrap" className={classes.beszelStatsControls}>
{systems.length > 1 && (
<Menu position="bottom-start" withArrow shadow="md" withinPortal>
<Menu.Target>
<Button
variant="default"
size="compact-xs"
leftSection={<IconServer size={14} />}
className={classes.beszelStatsSystemToggle}
disabled={isSelectionSavePending}
>
{selectedLabel}
</Button>
</Menu.Target>
<Menu.Dropdown>
{systems.map((system) => (
<Menu.Item
key={system.value}
fz="xs"
fw={system.value === selectedValue ? 600 : 400}
c={system.value !== selectedValue ? "dimmed" : undefined}
disabled={isSelectionSavePending}
onClick={() => handleSelectSystem(system.value)}
>
{system.label}
</Menu.Item>
))}
</Menu.Dropdown>
</Menu>
)}
{width >= 560 ? (
<Button.Group className={classes.beszelStatsPeriodToggle}>
{periodOptions.map((period) => (
<Button
key={period.value}
size="compact-xs"
variant={period.value === options.timePeriod ? "filled" : "default"}
className={classes.beszelStatsPeriodButton}
aria-pressed={period.value === options.timePeriod}
disabled={isSelectionSavePending}
onClick={() => handleTimePeriod(period.value)}
>
{period.label}
</Button>
))}
</Button.Group>
) : (
<Select
size="xs"
value={options.timePeriod}
onChange={(value) => value && handleTimePeriod(value)}
disabled={isSelectionSavePending}
>
{selectedLabel}
</Button>
</Menu.Target>
<Menu.Dropdown>
{systems.map((system) => (
<Menu.Item
key={system.value}
fz="xs"
fw={system.value === selectedValue ? 600 : 400}
c={system.value !== selectedValue ? "dimmed" : undefined}
disabled={isSelectionSavePending}
onClick={() => handleSelectSystem(system.value)}
>
{system.label}
</Menu.Item>
))}
</Menu.Dropdown>
</Menu>
data={periodOptions}
className={classes.beszelStatsPeriodToggle}
/>
)}
</Group>
)}
{!isEditMode &&
(width >= 560 ? (
<SegmentedControl
size="xs"
fullWidth
value={options.timePeriod}
onChange={handleTimePeriod}
disabled={isSelectionSavePending}
data={periodOptions}
/>
) : (
<Select
size="xs"
value={options.timePeriod}
onChange={(value) => value && handleTimePeriod(value)}
disabled={isSelectionSavePending}
data={periodOptions}
/>
))}
<BeszelStatsView
integrationIds={selectedSystem ? [selectedSystem.integrationId] : []}
systemId={selectedSystem?.systemId ?? ""}