fix: address dashboard review feedback
This commit is contained in:
1 parent
ebe7a36ba9
commit
cd86b10167
9 files changed
+93
-118
No files matched your search
@@ -5,6 +5,8 @@
|
||||
}
|
||||
|
||||
.errorIndicator {
|
||||
top: calc(var(--mantine-spacing-md) + var(--mantine-spacing-xl));
|
||||
inset-inline-end: 4px;
|
||||
z-index: 1;
|
||||
padding: 2px;
|
||||
border: 1px solid rgb(from var(--mantine-primary-color-filled) r g b / calc(var(--opacity, 1) * 0.45));
|
||||
|
||||
@@ -257,7 +257,7 @@ const CalendarBase = ({
|
||||
}}
|
||||
/>
|
||||
{hasErrors && (
|
||||
<Group className={classes.errorIndicator} gap={0} pos="absolute" top={4} right={4}>
|
||||
<Group className={classes.errorIndicator} gap={0} pos="absolute">
|
||||
<IntegrationErrorIndicator results={failedIntegrations} />
|
||||
<WidgetQueryErrorIndicator error={queryError} label={t("name")} />
|
||||
</Group>
|
||||
|
||||
@@ -3,22 +3,3 @@
|
||||
--mantine-datatable-background-color: transparent !important;
|
||||
--mantine-datatable-striped-color: transparent !important;
|
||||
}
|
||||
|
||||
.homarr-data-table.docker-table,
|
||||
.homarr-data-table.docker-table .mantine-datatable-table,
|
||||
.homarr-data-table.docker-table .mantine-datatable-table thead,
|
||||
.homarr-data-table.docker-table .mantine-datatable-table tbody,
|
||||
.homarr-data-table.docker-table .mantine-datatable-table tfoot,
|
||||
.homarr-data-table.docker-table .mantine-datatable-table tr,
|
||||
.homarr-data-table.docker-table th,
|
||||
.homarr-data-table.docker-table td {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.homarr-data-table.docker-table .mantine-datatable-table tbody tr:hover {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.homarr-data-table.docker-table th {
|
||||
backdrop-filter: none;
|
||||
}
|
||||
@@ -1,20 +1,4 @@
|
||||
/* Keep the downloads table translucent so the board wallpaper and opacity remain visible. */
|
||||
.homarr-data-table.downloads-table,
|
||||
.homarr-data-table.downloads-table .mantine-datatable-table,
|
||||
.homarr-data-table.downloads-table .mantine-datatable-table thead,
|
||||
.homarr-data-table.downloads-table .mantine-datatable-table tbody,
|
||||
.homarr-data-table.downloads-table .mantine-datatable-table tfoot,
|
||||
.homarr-data-table.downloads-table .mantine-datatable-table tr,
|
||||
.homarr-data-table.downloads-table th,
|
||||
.homarr-data-table.downloads-table td,
|
||||
.homarr-data-table.downloads-table .mantine-datatable-row-expansion-cell {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.homarr-data-table.downloads-table .mantine-datatable-table tbody tr:hover {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.homarr-data-table.downloads-table th {
|
||||
backdrop-filter: none;
|
||||
/* Row expansion styling */
|
||||
.downloads-table .mantine-datatable-row-expansion-cell {
|
||||
background-color: color-mix(in srgb, var(--mantine-color-body) 40%, transparent) !important;
|
||||
}
|
||||
@@ -219,7 +219,7 @@ export const getRssEntryDisplay = ({
|
||||
descriptionLines,
|
||||
}: RssEntryDisplayInput) => ({
|
||||
showDescription: isAdvanced || (!hideDescription && !isTiny),
|
||||
showImage: isAdvanced || (showPosterImage && !isDense),
|
||||
showImage: isAdvanced || (showPosterImage && !isTiny),
|
||||
showSource: isAdvanced || !isDense,
|
||||
descriptionLineClamp: isAdvanced ? undefined : descriptionLines,
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { Box, Card, Group, ScrollArea, SimpleGrid, Text, Tooltip } from "@mantine/core";
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
@@ -18,6 +18,34 @@ import { NoIntegrationDataError } from "../errors/no-data-integration";
|
||||
|
||||
type DiskDisplayMode = WidgetComponentProps<"systemDisks">["options"]["displayMode"];
|
||||
|
||||
const advancedColumnBreakpoints = [
|
||||
{ minimumWidth: 1820, columns: 7 },
|
||||
{ minimumWidth: 1560, columns: 6 },
|
||||
{ minimumWidth: 1300, columns: 5 },
|
||||
{ minimumWidth: 1040, columns: 4 },
|
||||
{ minimumWidth: 780, columns: 3 },
|
||||
{ minimumWidth: 520, columns: 2 },
|
||||
{ minimumWidth: 0, columns: 1 },
|
||||
] as const;
|
||||
|
||||
const getSystemDisksLayout = (width: number, height: number, diskCount: number, isAdvanced: boolean) => {
|
||||
if (!isAdvanced) {
|
||||
return {
|
||||
columns: 1,
|
||||
showSecondaryText: height >= 160 && width >= 260,
|
||||
showTemperature: height >= 100 && width >= 220,
|
||||
};
|
||||
}
|
||||
|
||||
const breakpoint = advancedColumnBreakpoints.find((candidate) => width >= candidate.minimumWidth);
|
||||
const columns = Math.min(diskCount, breakpoint?.columns ?? 1);
|
||||
return {
|
||||
columns: Math.max(1, columns),
|
||||
showSecondaryText: true,
|
||||
showTemperature: true,
|
||||
};
|
||||
};
|
||||
|
||||
export const clampPercentage = (percentage: number): number => Math.min(100, Math.max(0, percentage));
|
||||
|
||||
export const getDisplayText = (
|
||||
@@ -201,6 +229,27 @@ export default function SystemResources({
|
||||
const data = results.filter(
|
||||
(entry): entry is typeof entry & { healthInfo: NonNullable<typeof entry.healthInfo> } => entry.healthInfo !== null,
|
||||
);
|
||||
const disks = data.flatMap((entry) => {
|
||||
const fileSystem = filterStorageVolumes(
|
||||
entry.healthInfo.fileSystem,
|
||||
options.visibleStorageVolumes,
|
||||
entry.integrationId,
|
||||
);
|
||||
const smart = filterStorageVolumes(entry.healthInfo.smart, options.visibleStorageVolumes, entry.integrationId);
|
||||
return fileSystem.map((item) => ({
|
||||
integrationId: entry.integrationId,
|
||||
integrationName: entry.integrationName,
|
||||
item,
|
||||
smartItem: smart.find(
|
||||
(candidate) => normalizeStorageDeviceName(candidate.deviceName) === normalizeStorageDeviceName(item.deviceName),
|
||||
),
|
||||
}));
|
||||
});
|
||||
const isAdvanced = displayMode === "advanced";
|
||||
const layout = useMemo(
|
||||
() => getSystemDisksLayout(width, height, disks.length, isAdvanced),
|
||||
[disks.length, height, isAdvanced, width],
|
||||
);
|
||||
const queryIndicators = (
|
||||
<Group gap={0}>
|
||||
<IntegrationErrorIndicator results={results} />
|
||||
@@ -219,40 +268,18 @@ export default function SystemResources({
|
||||
if (isInitialWidgetQueryPending(healthQuery)) return <WidgetQueryLoadingState />;
|
||||
if (data.length === 0) return emptyState;
|
||||
|
||||
const disks = data.flatMap((entry) => {
|
||||
const fileSystem = filterStorageVolumes(
|
||||
entry.healthInfo.fileSystem,
|
||||
options.visibleStorageVolumes,
|
||||
entry.integrationId,
|
||||
);
|
||||
const smart = filterStorageVolumes(entry.healthInfo.smart, options.visibleStorageVolumes, entry.integrationId);
|
||||
return fileSystem.map((item) => ({
|
||||
integrationId: entry.integrationId,
|
||||
integrationName: entry.integrationName,
|
||||
item,
|
||||
smartItem: smart.find(
|
||||
(candidate) => normalizeStorageDeviceName(candidate.deviceName) === normalizeStorageDeviceName(item.deviceName),
|
||||
),
|
||||
}));
|
||||
});
|
||||
|
||||
const hasNoFileSystems = data.every((entry) => entry.healthInfo.fileSystem.length === 0);
|
||||
const hasFailedSource = results.some((entry) => Boolean(entry.error));
|
||||
if (hasNoFileSystems && !hasFailedSource && !healthQuery.error) throw new NoIntegrationDataError();
|
||||
if (hasNoFileSystems || disks.length === 0) return emptyState;
|
||||
|
||||
const isAdvanced = displayMode === "advanced";
|
||||
const minimumCardWidth = 260;
|
||||
const columns = isAdvanced ? Math.max(1, Math.min(disks.length, Math.floor(width / minimumCardWidth))) : 1;
|
||||
const cellWidth = width / columns;
|
||||
|
||||
return (
|
||||
<Box h="100%" pos="relative">
|
||||
<Box pos="absolute" top={4} right={8} style={{ zIndex: 2 }}>
|
||||
{queryIndicators}
|
||||
</Box>
|
||||
<ScrollArea h="100%">
|
||||
<SimpleGrid cols={columns} spacing="xs" p="xs">
|
||||
<SimpleGrid cols={layout.columns} spacing="xs" p="xs">
|
||||
{disks.map(({ integrationId, integrationName, item, smartItem }) => {
|
||||
const freeText = t("status.free", {
|
||||
percentage: String(Math.round(100 - clampPercentage(item.percentage))),
|
||||
@@ -275,8 +302,8 @@ export default function SystemResources({
|
||||
freeText={advancedDisplayTexts.free}
|
||||
smartStatus={smartItem?.overallStatus}
|
||||
isAdvanced={isAdvanced}
|
||||
showSecondaryText={isAdvanced || (height >= 160 && cellWidth >= 260)}
|
||||
showTemperature={isAdvanced || (height >= 100 && cellWidth >= 220)}
|
||||
showSecondaryText={layout.showSecondaryText}
|
||||
showTemperature={layout.showTemperature}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -230,18 +230,19 @@ const SystemCharts = ({
|
||||
const labelDisplayMode = isAdvanced ? "textWithIcon" : options.labelDisplayMode;
|
||||
const chartCount = visibleCharts.length;
|
||||
const chartColumns = isAdvanced && width >= 560 ? 2 : 1;
|
||||
const compactChartGap = 8;
|
||||
const chartHeight = isAdvanced
|
||||
? 180
|
||||
: `calc((100% - ${Math.max(0, chartCount - 1) * 8}px) / ${Math.max(1, chartCount)})`;
|
||||
: `calc((100% - ${Math.max(0, chartCount - 1) * compactChartGap}px) / ${Math.max(1, chartCount)})`;
|
||||
|
||||
return (
|
||||
<Stack gap={isAdvanced ? "xs" : 8} h={isAdvanced ? "auto" : height} miw={0}>
|
||||
<Stack gap={isAdvanced ? "xs" : compactChartGap} h={isAdvanced ? "auto" : height} miw={0}>
|
||||
{showTitle && (
|
||||
<Text size="sm" fw={600} truncate="end">
|
||||
{integrationName}
|
||||
</Text>
|
||||
)}
|
||||
<SimpleGrid cols={chartColumns} spacing="xs" style={{ flex: 1, minHeight: 0 }}>
|
||||
<SimpleGrid cols={chartColumns} spacing={isAdvanced ? "xs" : compactChartGap} style={{ flex: 1, minHeight: 0 }}>
|
||||
{chartCount === 0 && (
|
||||
<Center h="100%">
|
||||
<Text size="sm" c="dimmed">
|
||||
|
||||
Reference in new issue
Block a user