fix: Fix typecheck, lint issues and errors brought to dnshole summary. (#916)
This commit is contained in:
@@ -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,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -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: {
|
||||||
|
|||||||
@@ -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: [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) => {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export default async function getServerDataAsync({ integrationIds }: WidgetProps
|
|||||||
};
|
};
|
||||||
} catch {
|
} catch {
|
||||||
return {
|
return {
|
||||||
initialData: undefined,
|
initialData: [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user