fix(system-health): improve responsive styles (#2566)

This commit is contained in:
Meier Lukas
2025-03-11 22:15:01 +01:00
committed by GitHub
parent a53e7aaee5
commit 268daee4a6
8 changed files with 297 additions and 277 deletions

View File

@@ -31,6 +31,7 @@ const running = (total: number, current: Resource) => {
export const ClusterHealthMonitoring = ({
integrationId,
options,
width,
}: WidgetComponentProps<"healthMonitoring"> & { integrationId: string }) => {
const t = useI18n();
const [healthData] = clientApi.widget.healthMonitoring.getClusterHealthStatus.useSuspenseQuery(
@@ -72,14 +73,15 @@ export const ClusterHealthMonitoring = ({
const cpuPercent = maxCpu ? (usedCpu / maxCpu) * 100 : 0;
const memPercent = maxMem ? (usedMem / maxMem) * 100 : 0;
const isTiny = width < 256;
return (
<Stack h="100%">
<Group justify="center" wrap="nowrap" pt="md">
<Text fz="md" tt="uppercase" fw={700} c="dimmed" ta="center">
<Stack h="100%" p="xs" gap={isTiny ? "xs" : "md"}>
<Group justify="center" wrap="nowrap">
<Text fz={isTiny ? 8 : "xs"} tt="uppercase" fw={700} c="dimmed" ta="center">
{formatUptime(uptime, t)}
</Text>
</Group>
<SummaryHeader cpu={cpuPercent} memory={memPercent} />
<SummaryHeader cpu={cpuPercent} memory={memPercent} isTiny={isTiny} />
<Accordion variant="contained" chevronPosition="right" multiple defaultValue={["node"]}>
<ResourceAccordionItem
value="node"
@@ -90,8 +92,9 @@ export const ClusterHealthMonitoring = ({
totalCount: healthData.nodes.length,
sectionIndicatorRequirement: options.sectionIndicatorRequirement,
})}
isTiny={isTiny}
>
<ResourceTable type="node" data={healthData.nodes} />
<ResourceTable type="node" data={healthData.nodes} isTiny={isTiny} />
</ResourceAccordionItem>
<ResourceAccordionItem
@@ -103,8 +106,9 @@ export const ClusterHealthMonitoring = ({
totalCount: healthData.vms.length,
sectionIndicatorRequirement: options.sectionIndicatorRequirement,
})}
isTiny={isTiny}
>
<ResourceTable type="qemu" data={healthData.vms} />
<ResourceTable type="qemu" data={healthData.vms} isTiny={isTiny} />
</ResourceAccordionItem>
<ResourceAccordionItem
@@ -116,8 +120,9 @@ export const ClusterHealthMonitoring = ({
totalCount: healthData.lxcs.length,
sectionIndicatorRequirement: options.sectionIndicatorRequirement,
})}
isTiny={isTiny}
>
<ResourceTable type="lxc" data={healthData.lxcs} />
<ResourceTable type="lxc" data={healthData.lxcs} isTiny={isTiny} />
</ResourceAccordionItem>
<ResourceAccordionItem
@@ -129,8 +134,9 @@ export const ClusterHealthMonitoring = ({
totalCount: healthData.storages.length,
sectionIndicatorRequirement: options.sectionIndicatorRequirement,
})}
isTiny={isTiny}
>
<ResourceTable type="storage" data={healthData.storages} />
<ResourceTable type="storage" data={healthData.storages} isTiny={isTiny} />
</ResourceAccordionItem>
</Accordion>
</Stack>
@@ -140,45 +146,50 @@ export const ClusterHealthMonitoring = ({
interface SummaryHeaderProps {
cpu: number;
memory: number;
isTiny: boolean;
}
const SummaryHeader = ({ cpu, memory }: SummaryHeaderProps) => {
const SummaryHeader = ({ cpu, memory, isTiny }: SummaryHeaderProps) => {
const t = useI18n();
return (
<Center>
<Group wrap="nowrap">
<Group wrap="wrap" justify="center" gap="xs">
<Flex direction="row">
<RingProgress
roundCaps
size={60}
thickness={6}
size={isTiny ? 32 : 48}
thickness={isTiny ? 2 : 4}
label={
<Center>
<IconCpu />
<IconCpu size={isTiny ? 12 : 20} />
</Center>
}
sections={[{ value: cpu, color: cpu > 75 ? "orange" : "green" }]}
/>
<Stack align="center" justify="center" gap={0}>
<Text fw={500}>{t("widget.healthMonitoring.cluster.summary.cpu")}</Text>
<Text>{cpu.toFixed(1)}%</Text>
<Text fw={500} size={isTiny ? "xs" : "sm"}>
{t("widget.healthMonitoring.cluster.summary.cpu")}
</Text>
<Text size={isTiny ? "8px" : "xs"}>{cpu.toFixed(1)}%</Text>
</Stack>
</Flex>
<Flex>
<RingProgress
roundCaps
size={60}
thickness={6}
size={isTiny ? 32 : 48}
thickness={isTiny ? 2 : 4}
label={
<Center>
<IconBrain />
<IconBrain size={isTiny ? 12 : 20} />
</Center>
}
sections={[{ value: memory, color: memory > 75 ? "orange" : "green" }]}
/>
<Stack align="center" justify="center" gap={0}>
<Text fw={500}>{t("widget.healthMonitoring.cluster.summary.memory")}</Text>
<Text>{memory.toFixed(1)}%</Text>
<Text size={isTiny ? "xs" : "sm"} fw={500}>
{t("widget.healthMonitoring.cluster.summary.memory")}
</Text>
<Text size={isTiny ? "8px" : "xs"}>{memory.toFixed(1)}%</Text>
</Stack>
</Flex>
</Group>

View File

@@ -13,6 +13,7 @@ interface ResourceAccordionItemProps {
activeCount: number;
totalCount: number;
};
isTiny: boolean;
}
export const ResourceAccordionItem = ({
@@ -21,13 +22,14 @@ export const ResourceAccordionItem = ({
icon: Icon,
badge,
children,
isTiny,
}: PropsWithChildren<ResourceAccordionItemProps>) => {
return (
<Accordion.Item value={value}>
<Accordion.Control icon={<Icon />}>
<Group style={{ rowGap: "0" }}>
<Text>{title}</Text>
<Badge variant="dot" color={badge.color} size="lg">
<Accordion.Control icon={isTiny ? null : <Icon size={16} />}>
<Group style={{ rowGap: "0" }} gap="xs">
<Text size="xs">{title}</Text>
<Badge variant="dot" color={badge.color} size="xs">
{badge.activeCount} / {badge.totalCount}
</Badge>
</Group>

View File

@@ -1,4 +1,4 @@
import { Group, Indicator, Popover, Table, Text } from "@mantine/core";
import { Group, Indicator, Popover, Table, TableTbody, TableThead, TableTr, Text } from "@mantine/core";
import type { Resource } from "@homarr/integrations/types";
import { useI18n } from "@homarr/translation/client";
@@ -8,36 +8,47 @@ import { ResourcePopover } from "./resource-popover";
interface ResourceTableProps {
type: Resource["type"];
data: Resource[];
isTiny: boolean;
}
export const ResourceTable = ({ type, data }: ResourceTableProps) => {
export const ResourceTable = ({ type, data, isTiny }: ResourceTableProps) => {
const t = useI18n();
return (
<Table highlightOnHover>
<thead>
<tr>
<Table.Th ta="start">{t("widget.healthMonitoring.cluster.table.header.name")}</Table.Th>
<TableThead>
<TableTr fz={isTiny ? "8px" : "xs"}>
<Table.Th ta="start" p={0}>
{t("widget.healthMonitoring.cluster.table.header.name")}
</Table.Th>
{type !== "storage" ? (
<Table.Th ta="start">{t("widget.healthMonitoring.cluster.table.header.cpu")}</Table.Th>
<Table.Th ta="start" p={0}>
{t("widget.healthMonitoring.cluster.table.header.cpu")}
</Table.Th>
) : null}
{type !== "storage" ? (
<Table.Th ta="start">{t("widget.healthMonitoring.cluster.table.header.memory")}</Table.Th>
<Table.Th ta="start" p={0}>
{t("widget.healthMonitoring.cluster.table.header.memory")}
</Table.Th>
) : null}
{type === "storage" ? (
<Table.Th ta="start">{t("widget.healthMonitoring.cluster.table.header.node")}</Table.Th>
<Table.Th ta="start" p={0}>
{t("widget.healthMonitoring.cluster.table.header.node")}
</Table.Th>
) : null}
</tr>
</thead>
<tbody>
</TableTr>
</TableThead>
<TableTbody>
{data.map((item) => {
return (
<ResourcePopover key={item.name} item={item}>
<Popover.Target>
<tr>
<TableTr fz={isTiny ? "8px" : "xs"}>
<td>
<Group wrap="nowrap">
<Indicator size={14} children={null} color={item.isRunning ? "green" : "yellow"} />
<Text lineClamp={1}>{item.name}</Text>
<Group wrap="nowrap" gap={isTiny ? 8 : "xs"}>
<Indicator size={isTiny ? 4 : 8} children={null} color={item.isRunning ? "green" : "yellow"} />
<Text lineClamp={1} fz={isTiny ? "8px" : "xs"}>
{item.name}
</Text>
</Group>
</td>
{item.type === "storage" ? (
@@ -50,12 +61,12 @@ export const ResourceTable = ({ type, data }: ResourceTableProps) => {
</td>
</>
)}
</tr>
</TableTr>
</Popover.Target>
</ResourcePopover>
);
})}
</tbody>
</TableTbody>
</Table>
);
};

View File

@@ -31,30 +31,20 @@ export default function HealthMonitoringWidget(props: WidgetComponentProps<"heal
}
return (
<ScrollArea
h="100%"
styles={{
viewport: {
'& div[style="min-width: 100%"]': {
display: "flex !important",
height: "100%",
},
},
}}
>
<ScrollArea h="100%">
<Tabs defaultValue={props.options.defaultTab} variant="outline">
<Tabs.List grow>
<Tabs.Tab value="system">
<Tabs.Tab value="system" fz="xs">
<b>{t("widget.healthMonitoring.tab.system")}</b>
</Tabs.Tab>
<Tabs.Tab value="cluster">
<Tabs.Tab value="cluster" fz="xs">
<b>{t("widget.healthMonitoring.tab.cluster")}</b>
</Tabs.Tab>
</Tabs.List>
<Tabs.Panel mt="lg" value="system">
<Tabs.Panel value="system">
<SystemHealthMonitoring {...props} integrationIds={otherIntegrationIds} />
</Tabs.Panel>
<Tabs.Panel mt="lg" value="cluster">
<Tabs.Panel value="cluster">
<ClusterHealthMonitoring integrationId={proxmoxIntegrationId} {...props} />
</Tabs.Panel>
</Tabs>

View File

@@ -1,33 +1,30 @@
import { Box, Center, RingProgress, Text } from "@mantine/core";
import { useElementSize } from "@mantine/hooks";
import { Center, RingProgress, Text } from "@mantine/core";
import { IconCpu } from "@tabler/icons-react";
import { progressColor } from "../system-health";
export const CpuRing = ({ cpuUtilization }: { cpuUtilization: number }) => {
const { width, ref } = useElementSize();
const fallbackWidth = width || 1; // See https://github.com/homarr-labs/homarr/issues/2196
export const CpuRing = ({ cpuUtilization, isTiny }: { cpuUtilization: number; isTiny: boolean }) => {
return (
<Box ref={ref} w="100%" h="100%" className="health-monitoring-cpu">
<RingProgress
className="health-monitoring-cpu-utilization"
roundCaps
size={fallbackWidth * 0.95}
thickness={fallbackWidth / 10}
label={
<Center style={{ flexDirection: "column" }}>
<Text className="health-monitoring-cpu-utilization-value" size="sm">{`${cpuUtilization.toFixed(2)}%`}</Text>
<IconCpu className="health-monitoring-cpu-utilization-icon" size={30} />
</Center>
}
sections={[
{
value: Number(cpuUtilization.toFixed(2)),
color: progressColor(Number(cpuUtilization.toFixed(2))),
},
]}
/>
</Box>
<RingProgress
className="health-monitoring-cpu"
roundCaps
size={isTiny ? 50 : 100}
thickness={isTiny ? 4 : 8}
label={
<Center style={{ flexDirection: "column" }}>
<Text
className="health-monitoring-cpu-utilization-value"
size={isTiny ? "8px" : "xs"}
>{`${cpuUtilization.toFixed(2)}%`}</Text>
<IconCpu className="health-monitoring-cpu-utilization-icon" size={isTiny ? 8 : 16} />
</Center>
}
sections={[
{
value: Number(cpuUtilization.toFixed(2)),
color: progressColor(Number(cpuUtilization.toFixed(2))),
},
]}
/>
);
};

View File

@@ -1,39 +1,41 @@
import { Box, Center, RingProgress, Text } from "@mantine/core";
import { useElementSize } from "@mantine/hooks";
import { Center, RingProgress, Text } from "@mantine/core";
import { IconCpu } from "@tabler/icons-react";
import { progressColor } from "../system-health";
export const CpuTempRing = ({ fahrenheit, cpuTemp }: { fahrenheit: boolean; cpuTemp: number | undefined }) => {
const { width, ref } = useElementSize();
const fallbackWidth = width || 1; // See https://github.com/homarr-labs/homarr/issues/2196
export const CpuTempRing = ({
fahrenheit,
cpuTemp,
isTiny,
}: {
fahrenheit: boolean;
cpuTemp: number | undefined;
isTiny: boolean;
}) => {
if (!cpuTemp) {
return null;
}
return (
<Box ref={ref} w="100%" h="100%" className="health-monitoring-cpu-temperature">
<RingProgress
className="health-monitoring-cpu-temp"
roundCaps
size={fallbackWidth * 0.95}
thickness={fallbackWidth / 10}
label={
<Center style={{ flexDirection: "column" }}>
<Text className="health-monitoring-cpu-temp-value" size="sm">
{fahrenheit ? `${(cpuTemp * 1.8 + 32).toFixed(1)}°F` : `${cpuTemp.toFixed(1)}°C`}
</Text>
<IconCpu className="health-monitoring-cpu-temp-icon" size={30} />
</Center>
}
sections={[
{
value: cpuTemp,
color: progressColor(cpuTemp),
},
]}
/>
</Box>
<RingProgress
className="health-monitoring-cpu-temperature"
roundCaps
size={isTiny ? 50 : 100}
thickness={isTiny ? 4 : 8}
label={
<Center style={{ flexDirection: "column" }}>
<Text className="health-monitoring-cpu-temp-value" size={isTiny ? "8px" : "xs"}>
{fahrenheit ? `${(cpuTemp * 1.8 + 32).toFixed(1)}°F` : `${cpuTemp.toFixed(1)}°C`}
</Text>
<IconCpu className="health-monitoring-cpu-temp-icon" size={isTiny ? 8 : 16} />
</Center>
}
sections={[
{
value: cpuTemp,
color: progressColor(cpuTemp),
},
]}
/>
);
};

View File

@@ -1,38 +1,33 @@
import { Box, Center, RingProgress, Text } from "@mantine/core";
import { useElementSize } from "@mantine/hooks";
import { Center, RingProgress, Text } from "@mantine/core";
import { IconBrain } from "@tabler/icons-react";
import { progressColor } from "../system-health";
export const MemoryRing = ({ available, used }: { available: string; used: string }) => {
const { width, ref } = useElementSize();
const fallbackWidth = width || 1; // See https://github.com/homarr-labs/homarr/issues/2196
export const MemoryRing = ({ available, used, isTiny }: { available: string; used: string; isTiny: boolean }) => {
const memoryUsage = formatMemoryUsage(available, used);
return (
<Box ref={ref} w="100%" h="100%" className="health-monitoring-memory">
<RingProgress
className="health-monitoring-memory-use"
roundCaps
size={fallbackWidth * 0.95}
thickness={fallbackWidth / 10}
label={
<Center style={{ flexDirection: "column" }}>
<Text className="health-monitoring-memory-value" size="sm">
{memoryUsage.memUsed.GB}GiB
</Text>
<IconBrain className="health-monitoring-memory-icon" size={30} />
</Center>
}
sections={[
{
value: Number(memoryUsage.memUsed.percent),
color: progressColor(Number(memoryUsage.memUsed.percent)),
tooltip: `${memoryUsage.memUsed.percent}%`,
},
]}
/>
</Box>
<RingProgress
className="health-monitoring-memory"
roundCaps
size={isTiny ? 50 : 100}
thickness={isTiny ? 4 : 8}
label={
<Center style={{ flexDirection: "column" }}>
<Text className="health-monitoring-memory-value" size={isTiny ? "8px" : "xs"}>
{memoryUsage.memUsed.GB}GiB
</Text>
<IconBrain className="health-monitoring-memory-icon" size={isTiny ? 8 : 16} />
</Center>
}
sections={[
{
value: Number(memoryUsage.memUsed.percent),
color: progressColor(Number(memoryUsage.memUsed.percent)),
tooltip: `${memoryUsage.memUsed.percent}%`,
},
]}
/>
);
};

View File

@@ -44,7 +44,11 @@ import classes from "./system-health.module.css";
dayjs.extend(duration);
export const SystemHealthMonitoring = ({ options, integrationIds }: WidgetComponentProps<"healthMonitoring">) => {
export const SystemHealthMonitoring = ({
options,
integrationIds,
width,
}: WidgetComponentProps<"healthMonitoring">) => {
const t = useI18n();
const [healthData] = clientApi.widget.healthMonitoring.getSystemHealthStatus.useSuspenseQuery(
{
@@ -79,6 +83,8 @@ export const SystemHealthMonitoring = ({ options, integrationIds }: WidgetCompon
},
);
const isTiny = width < 256;
return (
<Stack h="100%" gap="sm" className="health-monitoring">
{healthData.map(({ integrationId, integrationName, healthInfo, updatedAt }) => {
@@ -91,95 +97,92 @@ export const SystemHealthMonitoring = ({ options, integrationIds }: WidgetCompon
h="100%"
className={`health-monitoring-information health-monitoring-${integrationName}`}
p="sm"
pos="relative"
>
<Box className="health-monitoring-information-card" p="sm">
<Flex
className="health-monitoring-information-card-elements"
justify="space-between"
align="center"
key={integrationId}
<Box className="health-monitoring-information-card-section" pos="absolute" top={8} right={8}>
<Indicator
className="health-monitoring-updates-reboot-indicator"
inline
processing
styles={{ indicator: { pointerEvents: "none" } }}
color={healthInfo.rebootRequired ? "red" : healthInfo.availablePkgUpdates > 0 ? "blue" : "gray"}
position="top-end"
size={16}
label={healthInfo.availablePkgUpdates > 0 ? healthInfo.availablePkgUpdates : undefined}
disabled={!healthInfo.rebootRequired && healthInfo.availablePkgUpdates === 0}
>
<Box className="health-monitoring-information-card-section">
<Indicator
className="health-monitoring-updates-reboot-indicator"
inline
processing
color={healthInfo.rebootRequired ? "red" : healthInfo.availablePkgUpdates > 0 ? "blue" : "gray"}
position="top-end"
size="md"
label={healthInfo.availablePkgUpdates > 0 ? healthInfo.availablePkgUpdates : undefined}
disabled={!healthInfo.rebootRequired && healthInfo.availablePkgUpdates === 0}
>
<ActionIcon
className="health-monitoring-information-icon-avatar"
variant={"light"}
color="var(--mantine-color-text)"
size={40}
radius={board.itemRadius}
>
<IconInfoCircle className="health-monitoring-information-icon" size={30} onClick={open} />
</ActionIcon>
</Indicator>
<Modal
opened={opened}
onClose={close}
size="auto"
title={t("widget.healthMonitoring.popover.information")}
centered
>
<Stack gap="10px" className="health-monitoring-modal-stack">
<Divider />
<List className="health-monitoring-information-list" center spacing="xs">
<List.Item className="health-monitoring-information-processor" icon={<IconCpu2 size={30} />}>
{t("widget.healthMonitoring.popover.processor", { cpuModelName: healthInfo.cpuModelName })}
</List.Item>
<List.Item className="health-monitoring-information-memory" icon={<IconBrain size={30} />}>
{t("widget.healthMonitoring.popover.memory", { memory: memoryUsage.memTotal.GB })}
</List.Item>
<List.Item className="health-monitoring-information-memory" icon={<IconBrain size={30} />}>
{t("widget.healthMonitoring.popover.memoryAvailable", {
memoryAvailable: memoryUsage.memFree.GB,
percent: memoryUsage.memFree.percent,
})}
</List.Item>
<List.Item className="health-monitoring-information-version" icon={<IconVersions size={30} />}>
{t("widget.healthMonitoring.popover.version", {
version: healthInfo.version,
})}
</List.Item>
<List.Item className="health-monitoring-information-uptime" icon={<IconClock size={30} />}>
{formatUptime(healthInfo.uptime, t)}
</List.Item>
<List.Item className="health-monitoring-information-load-average" icon={<IconCpu size={30} />}>
{t("widget.healthMonitoring.popover.loadAverage")}
</List.Item>
<List m="xs" withPadding center spacing="xs" icon={<IconCpu size={30} />}>
<List.Item className="health-monitoring-information-load-average-1min">
{t("widget.healthMonitoring.popover.minute")} {healthInfo.loadAverage["1min"]}%
</List.Item>
<List.Item className="health-monitoring-information-load-average-5min">
{t("widget.healthMonitoring.popover.minutes", { count: 5 })}{" "}
{healthInfo.loadAverage["5min"]}%
</List.Item>
<List.Item className="health-monitoring-information-load-average-15min">
{t("widget.healthMonitoring.popover.minutes", { count: 15 })}{" "}
{healthInfo.loadAverage["15min"]}%
</List.Item>
</List>
</List>
</Stack>
</Modal>
</Box>
{options.cpu && <CpuRing cpuUtilization={healthInfo.cpuUtilization} />}
{options.cpu && <CpuTempRing fahrenheit={options.fahrenheit} cpuTemp={healthInfo.cpuTemp} />}
{options.memory && <MemoryRing available={healthInfo.memAvailable} used={healthInfo.memUsed} />}
</Flex>
{
<Text className="health-monitoring-status-update-time" c="dimmed" size="sm" ta="center">
{t("widget.healthMonitoring.popover.lastSeen", { lastSeen: dayjs(updatedAt).fromNow() })}
</Text>
}
<ActionIcon
className="health-monitoring-information-icon-avatar"
variant={"light"}
color="var(--mantine-color-text)"
size="sm"
radius={board.itemRadius}
>
<IconInfoCircle className="health-monitoring-information-icon" size={30} onClick={open} />
</ActionIcon>
</Indicator>
<Modal
opened={opened}
onClose={close}
size="auto"
title={t("widget.healthMonitoring.popover.information")}
centered
>
<Stack gap="10px" className="health-monitoring-modal-stack">
<Divider />
<List className="health-monitoring-information-list" center spacing="xs">
<List.Item className="health-monitoring-information-processor" icon={<IconCpu2 size={30} />}>
{t("widget.healthMonitoring.popover.processor", { cpuModelName: healthInfo.cpuModelName })}
</List.Item>
<List.Item className="health-monitoring-information-memory" icon={<IconBrain size={30} />}>
{t("widget.healthMonitoring.popover.memory", { memory: memoryUsage.memTotal.GB })}
</List.Item>
<List.Item className="health-monitoring-information-memory" icon={<IconBrain size={30} />}>
{t("widget.healthMonitoring.popover.memoryAvailable", {
memoryAvailable: memoryUsage.memFree.GB,
percent: memoryUsage.memFree.percent,
})}
</List.Item>
<List.Item className="health-monitoring-information-version" icon={<IconVersions size={30} />}>
{t("widget.healthMonitoring.popover.version", {
version: healthInfo.version,
})}
</List.Item>
<List.Item className="health-monitoring-information-uptime" icon={<IconClock size={30} />}>
{formatUptime(healthInfo.uptime, t)}
</List.Item>
<List.Item className="health-monitoring-information-load-average" icon={<IconCpu size={30} />}>
{t("widget.healthMonitoring.popover.loadAverage")}
</List.Item>
<List m="xs" withPadding center spacing="xs" icon={<IconCpu size={30} />}>
<List.Item className="health-monitoring-information-load-average-1min">
{t("widget.healthMonitoring.popover.minute")} {healthInfo.loadAverage["1min"]}%
</List.Item>
<List.Item className="health-monitoring-information-load-average-5min">
{t("widget.healthMonitoring.popover.minutes", { count: 5 })} {healthInfo.loadAverage["5min"]}%
</List.Item>
<List.Item className="health-monitoring-information-load-average-15min">
{t("widget.healthMonitoring.popover.minutes", { count: 15 })} {healthInfo.loadAverage["15min"]}%
</List.Item>
</List>
</List>
</Stack>
</Modal>
</Box>
<Flex className="health-monitoring-information-card-elements" justify="center" align="center" wrap="wrap">
{options.cpu && <CpuRing cpuUtilization={healthInfo.cpuUtilization} isTiny={isTiny} />}
{options.cpu && (
<CpuTempRing fahrenheit={options.fahrenheit} cpuTemp={healthInfo.cpuTemp} isTiny={isTiny} />
)}
{options.memory && (
<MemoryRing available={healthInfo.memAvailable} used={healthInfo.memUsed} isTiny={isTiny} />
)}
</Flex>
{
<Text className="health-monitoring-status-update-time" c="dimmed" size="xs" ta="center">
{t("widget.healthMonitoring.popover.lastSeen", { lastSeen: dayjs(updatedAt).fromNow() })}
</Text>
}
{options.fileSystem &&
disksData.map((disk) => {
return (
@@ -188,63 +191,72 @@ export const SystemHealthMonitoring = ({ options, integrationIds }: WidgetCompon
`health-monitoring-disk-card health-monitoring-disk-card-${integrationName}`,
classes.card,
)}
style={{ overflow: "visible" }}
key={disk.deviceName}
radius={board.itemRadius}
p="sm"
p="xs"
>
<Flex className="health-monitoring-disk-status" justify="space-between" align="center" mb="sm">
<Group gap="xs">
<IconServer className="health-monitoring-disk-icon" size="1rem" />
<Text className="dihealth-monitoring-disk-name" size={"md"}>
{disk.deviceName}
</Text>
</Group>
<Group gap="xs">
<IconTemperature className="health-monitoring-disk-temperature-icon" size="1rem" />
<Text className="health-monitoring-disk-temperature-value" size="md">
{options.fahrenheit
? `${(disk.temperature * 1.8 + 32).toFixed(1)}°F`
: `${disk.temperature}°C`}
</Text>
</Group>
<Group gap="xs">
<IconFileReport className="health-monitoring-disk-status-icon" size="1rem" />
<Text className="health-monitoring-disk-status-value" size="md">
{disk.overallStatus ? disk.overallStatus : "N/A"}
</Text>
</Group>
</Flex>
<Progress.Root className="health-monitoring-disk-use" radius={board.itemRadius} h="md">
<Tooltip label={disk.used}>
<Progress.Section
value={disk.percentage}
color={progressColor(disk.percentage)}
className="health-monitoring-disk-use-percentage"
>
<Progress.Label className="health-monitoring-disk-use-value" fz="xs">
{t("widget.healthMonitoring.popover.used")}
</Progress.Label>
</Progress.Section>
</Tooltip>
<Tooltip
label={
Number(disk.available) / 1024 ** 4 >= 1
? `${(Number(disk.available) / 1024 ** 4).toFixed(2)} TiB`
: `${(Number(disk.available) / 1024 ** 3).toFixed(2)} GiB`
}
<Stack gap="sm">
<Group
className="health-monitoring-disk-status"
justify="space-between"
align="center"
wrap="wrap"
gap={8}
>
<Progress.Section
className="health-monitoring-disk-available-percentage"
value={100 - disk.percentage}
color="default"
<Group gap={4} wrap="nowrap">
<IconServer className="health-monitoring-disk-icon" size="1rem" />
<Text className="dihealth-monitoring-disk-name" size="xs">
{disk.deviceName}
</Text>
</Group>
<Group gap={4} wrap="nowrap">
<IconTemperature className="health-monitoring-disk-temperature-icon" size="1rem" />
<Text className="health-monitoring-disk-temperature-value" size="xs">
{options.fahrenheit
? `${(disk.temperature * 1.8 + 32).toFixed(1)}°F`
: `${disk.temperature}°C`}
</Text>
</Group>
<Group gap={4} wrap="nowrap">
<IconFileReport className="health-monitoring-disk-status-icon" size="1rem" />
<Text className="health-monitoring-disk-status-value" size="xs">
{disk.overallStatus ? disk.overallStatus : "N/A"}
</Text>
</Group>
</Group>
<Progress.Root className="health-monitoring-disk-use" radius={board.itemRadius} h="md">
<Tooltip label={disk.used}>
<Progress.Section
value={disk.percentage}
color={progressColor(disk.percentage)}
className="health-monitoring-disk-use-percentage"
>
<Progress.Label className="health-monitoring-disk-use-value" fz="xs">
{t("widget.healthMonitoring.popover.used")}
</Progress.Label>
</Progress.Section>
</Tooltip>
<Tooltip
label={
Number(disk.available) / 1024 ** 4 >= 1
? `${(Number(disk.available) / 1024 ** 4).toFixed(2)} TiB`
: `${(Number(disk.available) / 1024 ** 3).toFixed(2)} GiB`
}
>
<Progress.Label className="health-monitoring-disk-available-value" fz="xs">
{t("widget.healthMonitoring.popover.available")}
</Progress.Label>
</Progress.Section>
</Tooltip>
</Progress.Root>
<Progress.Section
className="health-monitoring-disk-available-percentage"
value={100 - disk.percentage}
color="default"
>
<Progress.Label className="health-monitoring-disk-available-value" fz="xs">
{t("widget.healthMonitoring.popover.available")}
</Progress.Label>
</Progress.Section>
</Tooltip>
</Progress.Root>
</Stack>
</Card>
);
})}