From 1bb1a8f628c674962f5509e0ba0d8de20d36e9d0 Mon Sep 17 00:00:00 2001
From: Angel <122130728+taos15@users.noreply.github.com>
Date: Fri, 1 Sep 2023 15:59:01 -0400
Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20Adguard=20logic=20and=20several?=
=?UTF-8?q?=20small=20bugs?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/server/api/routers/dns-hole/router.ts | 35 ++++--
src/tools/server/sdk/adGuard/adGuard.ts | 41 ++++--
src/tools/server/sdk/pihole/piHole.ts | 14 ++-
src/widgets/dnshole/DnsHoleControls.tsx | 146 +++++++++++++++++-----
src/widgets/dnshole/DnsHoleSummary.tsx | 11 +-
5 files changed, 198 insertions(+), 49 deletions(-)
diff --git a/src/server/api/routers/dns-hole/router.ts b/src/server/api/routers/dns-hole/router.ts
index 4d8bb8a76..6de3f91a3 100644
--- a/src/server/api/routers/dns-hole/router.ts
+++ b/src/server/api/routers/dns-hole/router.ts
@@ -1,3 +1,4 @@
+import Consola from 'consola';
import { z } from 'zod';
import { findAppProperty } from '~/tools/client/app-properties';
import { getConfig } from '~/tools/config/getConfig';
@@ -14,19 +15,25 @@ export const dnsHoleRouter = createTRPCRouter({
z.object({
action: z.enum(['enable', 'disable']),
configName: z.string(),
+ appsToChange: z.optional(z.array(z.string())),
})
)
.mutation(async ({ input }) => {
const config = getConfig(input.configName);
const applicableApps = config.apps.filter(
- (x) => x.integration?.type && ['pihole', 'adGuardHome'].includes(x.integration?.type)
+ (app) =>
+ app.id &&
+ app.integration?.type &&
+ input.appsToChange?.includes(app.id) &&
+ ['pihole', 'adGuardHome'].includes(app.integration?.type)
);
await Promise.all(
applicableApps.map(async (app) => {
if (app.integration?.type === 'pihole') {
await processPiHole(app, input.action === 'enable');
+
return;
}
@@ -72,8 +79,6 @@ export const dnsHoleRouter = createTRPCRouter({
}
);
- //const data: AdStatistics = ;
-
data.adsBlockedTodayPercentage = data.adsBlockedToday / data.dnsQueriesToday;
if (Number.isNaN(data.adsBlockedTodayPercentage)) {
data.adsBlockedTodayPercentage = 0;
@@ -90,22 +95,38 @@ const processAdGuard = async (app: ConfigAppType, enable: boolean) => {
);
if (enable) {
- await adGuard.disable();
+ try {
+ await adGuard.enable();
+ } catch (error) {
+ Consola.error((error as Error).message);
+ }
return;
}
- await adGuard.enable();
+ try {
+ await adGuard.disable();
+ } catch (error) {
+ Consola.error((error as Error).message);
+ }
};
const processPiHole = async (app: ConfigAppType, enable: boolean) => {
const pihole = new PiHoleClient(app.url, findAppProperty(app, 'apiKey'));
if (enable) {
- await pihole.enable();
+ try {
+ await pihole.enable();
+ } catch (error) {
+ Consola.error((error as Error).message);
+ }
return;
}
- await pihole.disable();
+ try {
+ await pihole.disable();
+ } catch (error) {
+ Consola.error((error as Error).message);
+ }
};
const collectPiHoleSummary = async (app: ConfigAppType) => {
diff --git a/src/tools/server/sdk/adGuard/adGuard.ts b/src/tools/server/sdk/adGuard/adGuard.ts
index 38a05102c..c6e0d4620 100644
--- a/src/tools/server/sdk/adGuard/adGuard.ts
+++ b/src/tools/server/sdk/adGuard/adGuard.ts
@@ -1,4 +1,7 @@
+import axios from 'axios';
+import Consola from 'consola';
import { z } from 'zod';
+
import { trimStringEnding } from '../../../shared/strings';
import {
adGuardApiFilteringStatusSchema,
@@ -60,19 +63,41 @@ export class AdGuard {
await this.changeProtectionStatus(false);
}
async enable() {
- await this.changeProtectionStatus(false);
+ await this.changeProtectionStatus(true);
}
+ /**
+ * Make a post request to the AdGuard API to change the protection status based on the value of newStatus
+ * @param {boolean} newStatus - The new status of the protection
+ * @param {number} duration - Duration of a pause, in milliseconds. Enabled should be false.
+ * @returns {string} - The response from the AdGuard API
+ */
private async changeProtectionStatus(newStatus: boolean, duration = 0) {
- await fetch(`${this.baseHostName}/control/protection`, {
- method: 'POST',
- body: JSON.stringify({
- enabled: newStatus,
- duration,
- }),
- });
+ try {
+ const { data }: { data: string } = await axios.post(
+ `${this.baseHostName}/control/protection`,
+ {
+ enabled: newStatus,
+ duration,
+ },
+ {
+ headers: {
+ Authorization: `Basic ${this.getAuthorizationHeaderValue()}`,
+ },
+ }
+ );
+ return data;
+ } catch (error) {
+ if (axios.isAxiosError(error)) {
+ Consola.error(error.message);
+ }
+ }
}
+ /**
+ * It return a base64 username:password string
+ * @returns {string} The base64 encoded username and password
+ */
private getAuthorizationHeaderValue() {
return Buffer.from(`${this.username}:${this.password}`).toString('base64');
}
diff --git a/src/tools/server/sdk/pihole/piHole.ts b/src/tools/server/sdk/pihole/piHole.ts
index 13ffd2b35..fcd22b605 100644
--- a/src/tools/server/sdk/pihole/piHole.ts
+++ b/src/tools/server/sdk/pihole/piHole.ts
@@ -62,6 +62,18 @@ export class PiHoleClient {
);
}
- return json as PiHoleApiStatusChangeResponse;
+ for(let loops = 0; loops < 10; loops++){
+ const summary = await this.getSummary()
+ if (summary.status === action + 'd'){
+ return json as PiHoleApiStatusChangeResponse;
+ }
+ await new Promise ((resolve) => { setTimeout(resolve, 50)});
+ }
+
+ return Promise.reject(
+ new Error(
+ `Although PiHole received the command, it failed to update it's status: ${json}`
+ )
+ )
}
}
diff --git a/src/widgets/dnshole/DnsHoleControls.tsx b/src/widgets/dnshole/DnsHoleControls.tsx
index dbd7a6b19..7f2835035 100644
--- a/src/widgets/dnshole/DnsHoleControls.tsx
+++ b/src/widgets/dnshole/DnsHoleControls.tsx
@@ -1,16 +1,25 @@
-import { Badge, Box, Button, Card, Group, Image, SimpleGrid, Stack, Text } from '@mantine/core';
+import {
+ Badge,
+ Box,
+ Button,
+ Card,
+ Group,
+ Image,
+ SimpleGrid,
+ Stack,
+ Text,
+ UnstyledButton,
+} from '@mantine/core';
import { useElementSize } from '@mantine/hooks';
import { IconDeviceGamepad, IconPlayerPlay, IconPlayerStop } from '@tabler/icons-react';
import { useTranslation } from 'next-i18next';
import { api } from '~/utils/api';
import { useConfigContext } from '../../config/provider';
-import { queryClient } from '../../tools/server/configurations/tanstack/queryClient.tool';
import { defineWidget } from '../helper';
import { WidgetLoading } from '../loading';
import { IWidget } from '../widgets';
import { useDnsHoleSummeryQuery } from './DnsHoleSummary';
-import { PiholeApiSummaryType } from './type';
const definition = defineWidget({
id: 'dns-hole-controls',
@@ -31,29 +40,83 @@ interface DnsHoleControlsWidgetProps {
widget: IDnsHoleControlsWidget;
}
+/**
+ *
+ * @param fetching - a expression that return a boolean if the data is been fetched
+ * @param currentStatus the current status of the dns integration, either enabled or disabled
+ * @returns
+ */
+const dnsLightStatus = (
+ fetching: boolean,
+ currentStatus: 'enabled' | 'disabled'
+): 'blue' | 'green' | 'red' => {
+ if (fetching) {
+ return 'blue';
+ }
+ if (currentStatus === 'enabled') {
+ return 'green';
+ }
+ return 'red';
+};
+
function DnsHoleControlsWidgetTile({ widget }: DnsHoleControlsWidgetProps) {
- const { isInitialLoading, data } = useDnsHoleSummeryQuery();
- const { mutateAsync } = useDnsHoleControlMutation();
+ const { isInitialLoading, data, isFetching: fetchingDnsSummary } = useDnsHoleSummeryQuery();
+ const { mutateAsync, isLoading: changingStatus } = useDnsHoleControlMutation();
const { width, ref } = useElementSize();
const { t } = useTranslation('common');
const { name: configName, config } = useConfigContext();
+ const trpcUtils = api.useContext();
+
if (isInitialLoading || !data || !configName) {
return ;
}
+ type getDnsStatusAcc = {
+ enabled: string[];
+ disabled: string[];
+ };
+
+ const getDnsStatus = () => {
+ const dnsList = data?.status.reduce(
+ (acc: getDnsStatusAcc, dns) => {
+ if (dns.status === 'enabled') {
+ acc.enabled.push(dns.appId);
+ } else if (dns.status === 'disabled') {
+ acc.disabled.push(dns.appId);
+ }
+ return acc;
+ },
+ { enabled: [], disabled: [] }
+ );
+
+ if (dnsList.enabled.length === 0 && dnsList.disabled.length === 0) {
+ return undefined;
+ }
+ return dnsList;
+ };
+
+ const reFetchSummaryDns = () => {
+ trpcUtils.dnsHole.summary.invalidate();
+ };
+
return (
- 275 ? 2 : 1} verticalSpacing="0.25rem" spacing="0.25rem">
+ 275 ? 2 : 1} spacing="0.25rem">
- {data.status.map((status, index) => {
- const app = config?.apps.find((x) => x.id === status.appId);
+ {data.status.map((dnsHole, index) => {
+ const app = config?.apps.find((x) => x.id === dnsHole.appId);
if (!app) {
return null;
}
return (
-
+
({
@@ -102,7 +170,43 @@ function DnsHoleControlsWidgetTile({ widget }: DnsHoleControlsWidgetProps) {
{app.name}
-
+ {
+ await mutateAsync({
+ action: dnsHole.status === 'enabled' ? 'disable' : 'enable',
+ configName,
+ appsToChange: [app.id],
+ },{
+ onSettled: () => {
+ reFetchSummaryDns();
+ }
+ });
+ }}
+ disabled={fetchingDnsSummary || changingStatus}
+ >
+ ({
+ root: {
+ '&:hover': {
+ background:
+ theme.colorScheme === 'dark'
+ ? theme.colors.dark[4]
+ : theme.colors.gray[2],
+ },
+ '&:active': {
+ background:
+ theme.colorScheme === 'dark'
+ ? theme.colors.dark[5]
+ : theme.colors.gray[3],
+ },
+ },
+ })}
+ >
+ {t(dnsHole.status)}
+
+
@@ -112,24 +216,6 @@ function DnsHoleControlsWidgetTile({ widget }: DnsHoleControlsWidgetProps) {
);
}
-
-const StatusBadge = ({ status }: { status: PiholeApiSummaryType['status'] }) => {
- const { t } = useTranslation('common');
- if (status === 'enabled') {
- return (
-
- {t('enabled')}
-
- );
- }
-
- return (
-
- {t('disabled')}
-
- );
-};
-
const useDnsHoleControlMutation = () => api.dnsHole.control.useMutation();
export default definition;
diff --git a/src/widgets/dnshole/DnsHoleSummary.tsx b/src/widgets/dnshole/DnsHoleSummary.tsx
index 51c949ddd..e9fb8e90f 100644
--- a/src/widgets/dnshole/DnsHoleSummary.tsx
+++ b/src/widgets/dnshole/DnsHoleSummary.tsx
@@ -58,8 +58,13 @@ function DnsHoleSummaryWidgetTile({ widget }: DnsHoleSummaryWidgetProps) {
return (
- {stats.map((item) => (
-
+ {stats.map((item, index) => (
+
))}
);
@@ -107,7 +112,7 @@ export const useDnsHoleSummeryQuery = () => {
configName: configName!,
},
{
- refetchInterval: 3 * 60 * 1000,
+ staleTime: 1000 * 60 * 2,
}
);
};