fix: Fix typecheck, lint issues and errors brought to dnshole summary. (#916)

This commit is contained in:
SeDemal
2024-08-04 20:46:27 +02:00
committed by GitHub
parent 65c6854e44
commit 0cec1dbb17
5 changed files with 50 additions and 65 deletions

View File

@@ -1,16 +1,16 @@
"use client"; "use client";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import type { BoxProps } from "@mantine/core"; import { ActionIcon, Badge, Box, Button, Card, Flex, Image, Stack, Text, Tooltip, UnstyledButton } from "@mantine/core";
import { ActionIcon, Badge, Box, Button, Card, Flex, Image, Tooltip, UnstyledButton } from "@mantine/core";
import { useDisclosure } from "@mantine/hooks"; import { useDisclosure } from "@mantine/hooks";
import { IconClockPause, IconPlayerPlay, IconPlayerStop } from "@tabler/icons-react"; import { IconClockPause, IconPlayerPlay, IconPlayerStop } from "@tabler/icons-react";
import { clientApi } from "@homarr/api/client"; import { clientApi } from "@homarr/api/client";
import { integrationDefs } from "@homarr/definitions"; import { integrationDefs } from "@homarr/definitions";
import type { TranslationFunction } from "@homarr/translation";
import { useI18n } from "@homarr/translation/client"; import { useI18n } from "@homarr/translation/client";
import type { WidgetComponentProps, WidgetProps } from "../../definition"; import type { WidgetComponentProps } from "../../definition";
import { NoIntegrationSelectedError } from "../../errors"; import { NoIntegrationSelectedError } from "../../errors";
import TimerModal from "./TimerModal"; import TimerModal from "./TimerModal";
@@ -71,9 +71,9 @@ export default function DnsHoleControlsWidget({ options, integrationIds }: Widge
const allDisabled = status.every((item) => !item.enabled); const allDisabled = status.every((item) => !item.enabled);
return ( return (
<Box h="100%" {...boxPropsByLayout(options.layout)}> <Flex h="100%" direction="column" gap={0} p="2.5cqmin">
{options.showToggleAllButtons && ( {options.showToggleAllButtons && (
<Flex gap="xs" m="2.5cqmin" p="2.5cqmin"> <Flex gap="2.5cqmin">
<Tooltip label={t("widget.dnsHoleControls.controls.enableAll")}> <Tooltip label={t("widget.dnsHoleControls.controls.enableAll")}>
<Button <Button
onClick={() => { onClick={() => {
@@ -112,12 +112,14 @@ export default function DnsHoleControlsWidget({ options, integrationIds }: Widge
</Flex> </Flex>
)} )}
{data.map((integrationData) => <Stack gap="2.5cqmin" flex={1} justify={options.showToggleAllButtons ? "flex-end" : "space-evenly"}>
ControlsCard(integrationData.integrationId, integrationData.integrationKind, toggleDns, status, open, t), {data.map((integrationData) =>
)} ControlsCard(integrationData.integrationId, integrationData.integrationKind, toggleDns, status, open, t),
)}
</Stack>
<TimerModal opened={opened} close={close} integrationIds={integrationIds} disableDns={disableDns} /> <TimerModal opened={opened} close={close} integrationIds={integrationIds} disableDns={disableDns} />
</Box> </Flex>
); );
} }
@@ -127,26 +129,24 @@ const ControlsCard = (
toggleDns: (integrationId: string) => void, toggleDns: (integrationId: string) => void,
status: { integrationId: string; enabled: boolean }[], status: { integrationId: string; enabled: boolean }[],
open: () => void, open: () => void,
t: ReturnType<typeof useI18n>, t: TranslationFunction,
) => { ) => {
const integrationStatus = status.find((item) => item.integrationId === integrationId); const integrationStatus = status.find((item) => item.integrationId === integrationId);
const isEnabled = integrationStatus?.enabled ?? false; const isEnabled = integrationStatus?.enabled ?? false;
const integrationDef = integrationKind === "piHole" ? integrationDefs.piHole : integrationDefs.adGuardHome; const integrationDef = integrationKind === "piHole" ? integrationDefs.piHole : integrationDefs.adGuardHome;
return ( return (
<Card key={integrationId} withBorder m="2.5cqmin" p="2.5cqmin" radius="md"> <Card key={integrationId} withBorder p="2.5cqmin" radius="2.5cqmin">
<Flex> <Flex>
<Box m="1.5cqmin" p="1.5cqmin"> <Box m="1.5cqmin" p="1.5cqmin">
<Image src={integrationDef.iconUrl} width={50} height={50} fit="contain" /> <Image src={integrationDef.iconUrl} width="50cqmin" height="50cqmin" fit="contain" />
</Box> </Box>
<Flex direction="column" m="1.5cqmin" p="1.5cqmin" gap="1cqmin"> <Flex direction="column" m="1.5cqmin" p="1.5cqmin" gap="1cqmin">
<Badge variant="default">{integrationDef.name}</Badge> <Text>{integrationDef.name}</Text>
<Flex direction="row" gap="2cqmin"> <Flex direction="row" gap="2cqmin">
<UnstyledButton onClick={() => toggleDns(integrationId)}> <UnstyledButton onClick={() => toggleDns(integrationId)}>
<Badge variant="dot" color={dnsLightStatus(isEnabled)}> <Badge variant="dot" color={dnsLightStatus(isEnabled)}>
{isEnabled {t(`widget.dnsHoleControls.controls.${isEnabled ? "enabled" : "disabled"}`)}
? t("widget.dnsHoleControls.controls.enabled")
: t("widget.dnsHoleControls.controls.disabled")}
</Badge> </Badge>
</UnstyledButton> </UnstyledButton>
<ActionIcon disabled={!isEnabled} size={20} radius="xl" top="2.67px" variant="default" onClick={open}> <ActionIcon disabled={!isEnabled} size={20} radius="xl" top="2.67px" variant="default" onClick={open}>
@@ -158,22 +158,3 @@ const ControlsCard = (
</Card> </Card>
); );
}; };
const boxPropsByLayout = (layout: WidgetProps<"dnsHoleControls">["options"]["layout"]): BoxProps => {
if (layout === "grid") {
return {
display: "grid",
style: {
gridTemplateColumns: "1fr 1fr",
gridTemplateRows: "1fr 1fr",
},
};
}
return {
display: "flex",
style: {
flexDirection: layout,
},
};
};

View File

@@ -9,13 +9,6 @@ export const { definition, componentLoader, serverDataLoader } = createWidgetDef
showToggleAllButtons: factory.switch({ showToggleAllButtons: factory.switch({
defaultValue: true, defaultValue: true,
}), }),
layout: factory.select({
options: (["grid", "row", "column"] as const).map((value) => ({
value,
label: (t) => t(`widget.dnsHoleControls.option.layout.option.${value}.label`),
})),
defaultValue: "grid",
}),
})), })),
supportedIntegrations: ["piHole", "adGuardHome"], supportedIntegrations: ["piHole", "adGuardHome"],
errors: { errors: {

View File

@@ -4,7 +4,7 @@ import { api } from "@homarr/api/server";
import type { WidgetProps } from "../../definition"; import type { WidgetProps } from "../../definition";
export default async function getServerDataAsync({ integrationIds }: WidgetProps<"dnsHoleSummary">) { export default async function getServerDataAsync({ integrationIds }: WidgetProps<"dnsHoleControls">) {
if (integrationIds.length === 0) { if (integrationIds.length === 0) {
return { return {
initialData: [], initialData: [],
@@ -19,9 +19,9 @@ export default async function getServerDataAsync({ integrationIds }: WidgetProps
return { return {
initialData: currentDns, initialData: currentDns,
}; };
} catch (error) { } catch {
return { return {
initialData: undefined, initialData: [],
}; };
} }
} }

View File

@@ -1,12 +1,12 @@
"use client"; "use client";
import { useMemo } from "react";
import type { BoxProps } from "@mantine/core"; import type { BoxProps } from "@mantine/core";
import { Box, Card, Flex, Text } from "@mantine/core"; import { Box, Card, Flex, Text } from "@mantine/core";
import { useElementSize } from "@mantine/hooks"; import { useElementSize } from "@mantine/hooks";
import { IconBarrierBlock, IconPercentage, IconSearch, IconWorldWww } from "@tabler/icons-react"; import { IconBarrierBlock, IconPercentage, IconSearch, IconWorldWww } from "@tabler/icons-react";
import type { RouterOutputs } from "@homarr/api"; import type { RouterOutputs } from "@homarr/api";
import { clientApi } from "@homarr/api/client";
import { formatNumber } from "@homarr/common"; import { formatNumber } from "@homarr/common";
import type { stringOrTranslation, TranslationFunction } from "@homarr/translation"; import type { stringOrTranslation, TranslationFunction } from "@homarr/translation";
import { translateIfNecessary } from "@homarr/translation"; import { translateIfNecessary } from "@homarr/translation";
@@ -16,27 +16,23 @@ import type { TablerIcon } from "@homarr/ui";
import type { WidgetComponentProps, WidgetProps } from "../../definition"; import type { WidgetComponentProps, WidgetProps } from "../../definition";
import { NoIntegrationSelectedError } from "../../errors"; import { NoIntegrationSelectedError } from "../../errors";
export default function DnsHoleSummaryWidget({ options, integrationIds }: WidgetComponentProps<"dnsHoleSummary">) { export default function DnsHoleSummaryWidget({
options,
integrationIds,
serverData,
}: WidgetComponentProps<"dnsHoleSummary">) {
const integrationId = integrationIds.at(0); const integrationId = integrationIds.at(0);
if (!integrationId) { if (!integrationId) {
throw new NoIntegrationSelectedError(); throw new NoIntegrationSelectedError();
} }
const [data] = clientApi.widget.dnsHole.summary.useSuspenseQuery( const data = useMemo(() => (serverData?.initialData ?? []).flatMap((summary) => summary.summary), [serverData]);
{
integrationIds,
},
{
refetchOnMount: false,
retry: false,
},
);
return ( return (
<Box h="100%" {...boxPropsByLayout(options.layout)}> <Box h="100%" {...boxPropsByLayout(options.layout)}>
{stats.map((item, index) => ( {stats.map((item, index) => (
<StatCard key={index} item={item} usePiHoleColors={options.usePiHoleColors} data={data[0]?.summary} /> <StatCard key={index} item={item} usePiHoleColors={options.usePiHoleColors} data={data} />
))} ))}
</Box> </Box>
); );
@@ -45,15 +41,22 @@ export default function DnsHoleSummaryWidget({ options, integrationIds }: Widget
const stats = [ const stats = [
{ {
icon: IconBarrierBlock, icon: IconBarrierBlock,
value: ({ adsBlockedToday }) => formatNumber(adsBlockedToday, 2), value: (data) =>
formatNumber(
data.reduce((count, { adsBlockedToday }) => count + adsBlockedToday, 0),
2,
),
label: (t) => t("widget.dnsHoleSummary.data.adsBlockedToday"), label: (t) => t("widget.dnsHoleSummary.data.adsBlockedToday"),
color: "rgba(240, 82, 60, 0.4)", // RED color: "rgba(240, 82, 60, 0.4)", // RED
}, },
{ {
icon: IconPercentage, icon: IconPercentage,
value: ({ adsBlockedTodayPercentage }, t) => value: (data, t) =>
t("common.rtl", { t("common.rtl", {
value: formatNumber(adsBlockedTodayPercentage, 2), value: formatNumber(
data.reduce((count, { adsBlockedTodayPercentage }) => count + adsBlockedTodayPercentage, 0),
2,
),
symbol: "%", symbol: "%",
}), }),
label: (t) => t("widget.dnsHoleSummary.data.adsBlockedTodayPercentage"), label: (t) => t("widget.dnsHoleSummary.data.adsBlockedTodayPercentage"),
@@ -61,13 +64,21 @@ const stats = [
}, },
{ {
icon: IconSearch, icon: IconSearch,
value: ({ dnsQueriesToday }) => formatNumber(dnsQueriesToday, 2), value: (data) =>
formatNumber(
data.reduce((count, { dnsQueriesToday }) => count + dnsQueriesToday, 0),
2,
),
label: (t) => t("widget.dnsHoleSummary.data.dnsQueriesToday"), label: (t) => t("widget.dnsHoleSummary.data.dnsQueriesToday"),
color: "rgba(0, 175, 218, 0.4)", // BLUE color: "rgba(0, 175, 218, 0.4)", // BLUE
}, },
{ {
icon: IconWorldWww, icon: IconWorldWww,
value: ({ domainsBeingBlocked }) => formatNumber(domainsBeingBlocked, 2), value: (data) =>
formatNumber(
data.reduce((count, { domainsBeingBlocked }) => count + domainsBeingBlocked, 0),
2,
),
label: (t) => t("widget.dnsHoleSummary.data.domainsBeingBlocked"), label: (t) => t("widget.dnsHoleSummary.data.domainsBeingBlocked"),
color: "rgba(0, 176, 96, 0.4)", // GREEN color: "rgba(0, 176, 96, 0.4)", // GREEN
}, },
@@ -75,14 +86,14 @@ const stats = [
interface StatItem { interface StatItem {
icon: TablerIcon; icon: TablerIcon;
value: (x: RouterOutputs["widget"]["dnsHole"]["summary"], t: TranslationFunction) => string; value: (x: RouterOutputs["widget"]["dnsHole"]["summary"][number]["summary"][], t: TranslationFunction) => string;
label: stringOrTranslation; label: stringOrTranslation;
color: string; color: string;
} }
interface StatCardProps { interface StatCardProps {
item: StatItem; item: StatItem;
data: RouterOutputs["widget"]["dnsHole"]["summary"]; data: RouterOutputs["widget"]["dnsHole"]["summary"][number]["summary"][];
usePiHoleColors: boolean; usePiHoleColors: boolean;
} }
const StatCard = ({ item, data, usePiHoleColors }: StatCardProps) => { const StatCard = ({ item, data, usePiHoleColors }: StatCardProps) => {

View File

@@ -21,7 +21,7 @@ export default async function getServerDataAsync({ integrationIds }: WidgetProps
}; };
} catch { } catch {
return { return {
initialData: undefined, initialData: [],
}; };
} }
} }