feat: refine widget interactions
This commit is contained in:
1 parent
a8a7545f40
commit
842e86012d
107 files changed
+3144
-1233
No files matched your search
@@ -1,13 +1,14 @@
|
||||
"use client";
|
||||
|
||||
import { useMemo } from "react";
|
||||
import { Box, Card, SimpleGrid } from "@mantine/core";
|
||||
import { Box, Card, Center, SimpleGrid, Text } from "@mantine/core";
|
||||
import dayjs from "dayjs";
|
||||
import duration from "dayjs/plugin/duration";
|
||||
import objectSupport from "dayjs/plugin/objectSupport";
|
||||
import relativeTime from "dayjs/plugin/relativeTime";
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { useI18n } from "@homarr/translation/client";
|
||||
|
||||
import type { WidgetComponentProps } from "../../definition";
|
||||
import { WifiVariant } from "./variants/wifi-variant";
|
||||
@@ -22,10 +23,12 @@ export default function NetworkControllerNetworkStatusWidget({
|
||||
integrationIds,
|
||||
displayMode,
|
||||
width,
|
||||
height,
|
||||
}: WidgetComponentProps<"networkControllerStatus">) {
|
||||
const { data: summaries = [] } = clientApi.widget.networkController.summary.useQuery({
|
||||
const { data: summaries = [], isPending } = clientApi.widget.networkController.summary.useQuery({
|
||||
integrationIds,
|
||||
});
|
||||
const t = useI18n();
|
||||
|
||||
const data = useMemo(() => summaries.flatMap(({ summary }) => summary), [summaries]);
|
||||
const isAdvanced = displayMode === "advanced";
|
||||
@@ -33,18 +36,39 @@ export default function NetworkControllerNetworkStatusWidget({
|
||||
const countWifiUsers = data.reduce((sum, summary) => sum + summary.wifi.users, 0);
|
||||
const countLanGuests = data.reduce((sum, summary) => sum + summary.lan.guests, 0);
|
||||
const countLanUsers = data.reduce((sum, summary) => sum + summary.lan.users, 0);
|
||||
const useHorizontalStats = !isAdvanced && height < 150 && width >= 200;
|
||||
|
||||
if (isPending || data.length === 0) {
|
||||
return (
|
||||
<Center h="100%" p="sm">
|
||||
<Text c="dimmed" size="sm" ta="center">
|
||||
{isPending ? t("common.action.loading") : t("widget.networkControllerStatus.error.integrationsDisconnected")}
|
||||
</Text>
|
||||
</Center>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box p={isAdvanced ? "md" : "sm"} h="100%">
|
||||
<Box p={isAdvanced ? "md" : height < 120 ? "xs" : "sm"} h="100%">
|
||||
<SimpleGrid cols={isAdvanced && width >= 560 ? 2 : 1} h="100%" spacing="sm">
|
||||
{(isAdvanced || options.content === "wifi") && (
|
||||
<Card p={isAdvanced ? "md" : 0} withBorder={isAdvanced}>
|
||||
<WifiVariant countGuests={countWifiGuests} countUsers={countWifiUsers} compact={!isAdvanced} />
|
||||
<WifiVariant
|
||||
countGuests={countWifiGuests}
|
||||
countUsers={countWifiUsers}
|
||||
compact={!isAdvanced}
|
||||
horizontal={useHorizontalStats}
|
||||
/>
|
||||
</Card>
|
||||
)}
|
||||
{(isAdvanced || options.content === "wired") && (
|
||||
<Card p={isAdvanced ? "md" : 0} withBorder={isAdvanced}>
|
||||
<WiredVariant countGuests={countLanGuests} countUsers={countLanUsers} compact={!isAdvanced} />
|
||||
<WiredVariant
|
||||
countGuests={countLanGuests}
|
||||
countUsers={countLanUsers}
|
||||
compact={!isAdvanced}
|
||||
horizontal={useHorizontalStats}
|
||||
/>
|
||||
</Card>
|
||||
)}
|
||||
</SimpleGrid>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Group, Stack, Text } from "@mantine/core";
|
||||
import { Group, SimpleGrid, Text } from "@mantine/core";
|
||||
import { IconWifi } from "@tabler/icons-react";
|
||||
|
||||
import { useScopedI18n } from "@homarr/translation/client";
|
||||
@@ -9,10 +9,12 @@ export const WifiVariant = ({
|
||||
countGuests,
|
||||
countUsers,
|
||||
compact = false,
|
||||
horizontal = false,
|
||||
}: {
|
||||
countUsers: number;
|
||||
countGuests: number;
|
||||
compact?: boolean;
|
||||
horizontal?: boolean;
|
||||
}) => {
|
||||
const t = useScopedI18n("widget.networkControllerStatus.card");
|
||||
return (
|
||||
@@ -23,10 +25,10 @@ export const WifiVariant = ({
|
||||
{t("variants.wifi.name")}
|
||||
</Text>
|
||||
</Group>
|
||||
<Stack gap={compact ? "sm" : "lg"}>
|
||||
<SimpleGrid cols={horizontal ? 2 : 1} spacing={compact ? "sm" : "lg"}>
|
||||
<StatRow label={t("users.label")} value={countUsers} compact={compact} />
|
||||
<StatRow label={t("guests.label")} value={countGuests} compact={compact} />
|
||||
</Stack>
|
||||
</SimpleGrid>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Group, Stack, Text } from "@mantine/core";
|
||||
import { Group, SimpleGrid, Text } from "@mantine/core";
|
||||
import { IconNetwork } from "@tabler/icons-react";
|
||||
|
||||
import { useScopedI18n } from "@homarr/translation/client";
|
||||
@@ -9,10 +9,12 @@ export const WiredVariant = ({
|
||||
countGuests,
|
||||
countUsers,
|
||||
compact = false,
|
||||
horizontal = false,
|
||||
}: {
|
||||
countUsers: number;
|
||||
countGuests: number;
|
||||
compact?: boolean;
|
||||
horizontal?: boolean;
|
||||
}) => {
|
||||
const t = useScopedI18n("widget.networkControllerStatus.card");
|
||||
return (
|
||||
@@ -23,10 +25,10 @@ export const WiredVariant = ({
|
||||
{t("variants.wired.name")}
|
||||
</Text>
|
||||
</Group>
|
||||
<Stack gap={compact ? "sm" : "lg"}>
|
||||
<SimpleGrid cols={horizontal ? 2 : 1} spacing={compact ? "sm" : "lg"}>
|
||||
<StatRow label={t("users.label")} value={countUsers} compact={compact} />
|
||||
<StatRow label={t("guests.label")} value={countGuests} compact={compact} />
|
||||
</Stack>
|
||||
</SimpleGrid>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { Badge, Card, Group, List, ScrollArea, SimpleGrid, Stack, Text, useMantineTheme } from "@mantine/core";
|
||||
import { Badge, Card, Center, Group, List, ScrollArea, SimpleGrid, Stack, Text, useMantineTheme } from "@mantine/core";
|
||||
import { IconCircleCheckFilled, IconCircleXFilled } from "@tabler/icons-react";
|
||||
import dayjs from "dayjs";
|
||||
import duration from "dayjs/plugin/duration";
|
||||
@@ -19,19 +19,31 @@ dayjs.extend(duration);
|
||||
export default function NetworkControllerSummaryWidget({
|
||||
integrationIds,
|
||||
width,
|
||||
height,
|
||||
displayMode,
|
||||
}: WidgetComponentProps<"networkControllerSummary">) {
|
||||
const { data: summaries = [] } = clientApi.widget.networkController.summary.useQuery({
|
||||
const { data: summaries = [], isPending } = clientApi.widget.networkController.summary.useQuery({
|
||||
integrationIds,
|
||||
});
|
||||
|
||||
const t = useI18n();
|
||||
|
||||
const isAdvanced = displayMode === "advanced";
|
||||
const isDense = !isAdvanced && height < 160;
|
||||
const columns = isAdvanced && width >= 720 ? 2 : 1;
|
||||
|
||||
if (isPending || summaries.length === 0) {
|
||||
return (
|
||||
<Center h="100%" p="sm">
|
||||
<Text c="dimmed" size="sm" ta="center">
|
||||
{isPending ? t("common.action.loading") : t("widget.networkControllerSummary.error.integrationsDisconnected")}
|
||||
</Text>
|
||||
</Center>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ScrollArea h="100%" p={isAdvanced ? "md" : "sm"}>
|
||||
<ScrollArea h="100%" p={isAdvanced ? "md" : isDense ? "xs" : "sm"}>
|
||||
<SimpleGrid cols={columns} spacing="sm">
|
||||
{summaries.map(({ integration, summary }) => (
|
||||
<Card key={integration.id} p={isAdvanced ? "md" : "xs"} withBorder={summaries.length > 1 || isAdvanced}>
|
||||
@@ -46,9 +58,9 @@ export default function NetworkControllerSummaryWidget({
|
||||
</Badge>
|
||||
</Group>
|
||||
)}
|
||||
<List spacing="xs" center>
|
||||
<List.Item icon={<StatusIcon status={summary.wanStatus} />}>WAN</List.Item>
|
||||
<List.Item icon={<StatusIcon status={summary.www.status} />}>
|
||||
<List spacing={isDense ? 2 : "xs"} center size={isDense ? "sm" : undefined}>
|
||||
<List.Item icon={<StatusIcon status={summary.wanStatus} size={isDense ? 16 : 20} />}>WAN</List.Item>
|
||||
<List.Item icon={<StatusIcon status={summary.www.status} size={isDense ? 16 : 20} />}>
|
||||
<Text>
|
||||
WWW
|
||||
<Text c="dimmed" size="sm" ms="xs" span>
|
||||
@@ -56,15 +68,15 @@ export default function NetworkControllerSummaryWidget({
|
||||
</Text>
|
||||
</Text>
|
||||
</List.Item>
|
||||
<List.Item icon={<StatusIcon status={summary.wifi.status} />}>
|
||||
<List.Item icon={<StatusIcon status={summary.wifi.status} size={isDense ? 16 : 20} />}>
|
||||
Wi-Fi{isAdvanced ? ` · ${summary.wifi.users + summary.wifi.guests}` : ""}
|
||||
</List.Item>
|
||||
{isAdvanced && (
|
||||
<List.Item icon={<StatusIcon status={summary.lan.status} />}>
|
||||
<List.Item icon={<StatusIcon status={summary.lan.status} size={20} />}>
|
||||
LAN · {summary.lan.users + summary.lan.guests}
|
||||
</List.Item>
|
||||
)}
|
||||
<List.Item icon={<StatusIcon status={summary.vpn.status} />}>
|
||||
<List.Item icon={<StatusIcon status={summary.vpn.status} size={isDense ? 16 : 20} />}>
|
||||
<Text>
|
||||
VPN
|
||||
<Text c="dimmed" size="sm" ms="xs" span>
|
||||
@@ -81,11 +93,13 @@ export default function NetworkControllerSummaryWidget({
|
||||
);
|
||||
}
|
||||
|
||||
const StatusIcon = ({ status }: { status?: "enabled" | "disabled" }) => {
|
||||
const StatusIcon = ({ status, size }: { status?: "enabled" | "disabled"; size: number }) => {
|
||||
const mantineTheme = useMantineTheme();
|
||||
if (status === "enabled") {
|
||||
return <IconCircleCheckFilled size={20} color={mantineTheme.colors.green[6]} />;
|
||||
return <IconCircleCheckFilled aria-label={status} size={size} color={mantineTheme.colors.green[6]} />;
|
||||
}
|
||||
if (status === "disabled") return <IconCircleXFilled size={20} color={mantineTheme.colors.red[6]} />;
|
||||
return <IconCircleXFilled size={20} color={mantineTheme.colors.gray[6]} />;
|
||||
if (status === "disabled") {
|
||||
return <IconCircleXFilled aria-label={status} size={size} color={mantineTheme.colors.red[6]} />;
|
||||
}
|
||||
return <IconCircleXFilled aria-label="unknown" size={size} color={mantineTheme.colors.gray[6]} />;
|
||||
};
|
||||
Reference in new issue
Block a user