Skip to content

Commit

Permalink
feat: add dns disable timer (#2029)
Browse files Browse the repository at this point in the history
  • Loading branch information
hillaliy committed May 23, 2024
1 parent cb2b28c commit afdca50
Show file tree
Hide file tree
Showing 8 changed files with 272 additions and 77 deletions.
1 change: 1 addition & 0 deletions public/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"disabled": "Disabled",
"enableAll": "Enable all",
"disableAll": "Disable all",
"setTimer": "Set timer",
"version": "Version",
"changePosition": "Change position",
"remove": "Remove",
Expand Down
7 changes: 7 additions & 0 deletions public/locales/en/modules/dns-hole-controls.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,12 @@
"text": "There was a problem connecting to your DNS Hole(s). Please verify your configuration/integration(s)."
}
}
},
"durationModal": {
"title": "Set disable duration time",
"hours": "Hours",
"minutes": "Minutes",
"unlimited": "leave empty for unlimited",
"set": "Set"
}
}
13 changes: 7 additions & 6 deletions src/server/api/routers/dns-hole/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export const dnsHoleRouter = createTRPCRouter({
.input(
z.object({
action: z.enum(['enable', 'disable']),
duration: z.number(),
configName: z.string(),
appsToChange: z.optional(z.array(z.string())),
})
Expand All @@ -32,12 +33,12 @@ export const dnsHoleRouter = createTRPCRouter({
await Promise.all(
applicableApps.map(async (app) => {
if (app.integration?.type === 'pihole') {
await processPiHole(app, input.action === 'enable');
await processPiHole(app, input.action === 'enable', input.duration);

return;
}

await processAdGuard(app, input.action === 'enable');
await processAdGuard(app, input.action === 'enable', input.duration);
})
);
}),
Expand Down Expand Up @@ -89,7 +90,7 @@ export const dnsHoleRouter = createTRPCRouter({
}),
});

const processAdGuard = async (app: ConfigAppType, enable: boolean) => {
const processAdGuard = async (app: ConfigAppType, enable: boolean, duration: number = 0) => {
const adGuard = new AdGuard(
app.url,
findAppProperty(app, 'username'),
Expand All @@ -106,13 +107,13 @@ const processAdGuard = async (app: ConfigAppType, enable: boolean) => {
}

try {
await adGuard.disable();
await adGuard.disable(duration);
} catch (error) {
Consola.error((error as Error).message);
}
};

const processPiHole = async (app: ConfigAppType, enable: boolean) => {
const processPiHole = async (app: ConfigAppType, enable: boolean, duration: number = 0) => {
const pihole = new PiHoleClient(app.url, findAppProperty(app, 'apiKey'));

if (enable) {
Expand All @@ -125,7 +126,7 @@ const processPiHole = async (app: ConfigAppType, enable: boolean) => {
}

try {
await pihole.disable();
await pihole.disable(duration);
} catch (error) {
Consola.error((error as Error).message);
}
Expand Down
8 changes: 4 additions & 4 deletions src/tools/server/sdk/adGuard/adGuard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ export class AdGuard {
.reduce((sum, filter) => filter.rules_count + sum, 0);
}

async disable() {
await this.changeProtectionStatus(false);
async disable(duration: number) {
await this.changeProtectionStatus(false, duration);
}
async enable() {
await this.changeProtectionStatus(true);
Expand All @@ -69,7 +69,7 @@ export class AdGuard {
/**
* 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.
* @param {number} duration - Duration of a pause, in seconds. Enabled should be false.
* @returns {string} - The response from the AdGuard API
*/
private async changeProtectionStatus(newStatus: boolean, duration = 0) {
Expand All @@ -78,7 +78,7 @@ export class AdGuard {
`${this.baseHostName}/control/protection`,
{
enabled: newStatus,
duration,
duration: duration * 1000,
},
{
headers: {
Expand Down
11 changes: 7 additions & 4 deletions src/tools/server/sdk/pihole/piHole.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,19 @@ export class PiHoleClient {
return response.status === 'enabled';
}

async disable() {
const response = await this.sendStatusChangeRequest('disable');
async disable(duration: number) {
const response = await this.sendStatusChangeRequest('disable', duration);
return response.status === 'disabled';
}

private async sendStatusChangeRequest(
action: 'enable' | 'disable'
action: 'enable' | 'disable',
duration = 0
): Promise<PiHoleApiStatusChangeResponse> {
const response = await fetch(
`${this.baseHostName}/admin/api.php?${action}&auth=${this.apiToken}`
duration !== 0
? `${this.baseHostName}/admin/api.php?${action}=${duration}&auth=${this.apiToken}`
: `${this.baseHostName}/admin/api.php?${action}&auth=${this.apiToken}`
);

if (response.status !== 200) {
Expand Down
181 changes: 120 additions & 61 deletions src/widgets/dnshole/DnsHoleControls.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,37 @@
import {
ActionIcon,
Badge,
Box,
Button,
Card,
Center,
Flex,
Group,
Image,
SimpleGrid,
Stack,
Text,
Title,
Tooltip,
UnstyledButton,
} from '@mantine/core';
import { useElementSize } from '@mantine/hooks';
import { IconDeviceGamepad, IconPlayerPlay, IconPlayerStop } from '@tabler/icons-react';
import { useDisclosure } from '@mantine/hooks';
import {
IconClockPause,
IconDeviceGamepad,
IconPlayerPlay,
IconPlayerStop,
} from '@tabler/icons-react';
import { useSession } from 'next-auth/react';
import { useTranslation } from 'next-i18next';
import { useState } from 'react';
import { useConfigContext } from '~/config/provider';
import { api } from '~/utils/api';

import { defineWidget } from '../helper';
import { WidgetLoading } from '../loading';
import { IWidget } from '../widgets';
import { useDnsHoleSummeryQuery } from './DnsHoleSummary';
import { TimerModal } from './TimerModal';

const definition = defineWidget({
id: 'dns-hole-controls',
Expand Down Expand Up @@ -69,9 +78,10 @@ const dnsLightStatus = (

function DnsHoleControlsWidgetTile({ widget }: DnsHoleControlsWidgetProps) {
const { data: sessionData } = useSession();
const [opened, { close, open }] = useDisclosure(false);
const [appId, setAppId] = useState('');
const { isInitialLoading, data, isFetching: fetchingDnsSummary } = useDnsHoleSummeryQuery();
const { mutateAsync, isLoading: changingStatus } = useDnsHoleControlMutation();
const { width, ref } = useElementSize();
const { t } = useTranslation(['common', 'modules/dns-hole-controls']);

const enableControls = sessionData?.user.isAdmin ?? false;
Expand Down Expand Up @@ -124,10 +134,17 @@ function DnsHoleControlsWidgetTile({ widget }: DnsHoleControlsWidgetProps) {
return dnsList;
};

const toggleDns = async (action: 'enable' | 'disable', appsToChange?: string[]) => {
const toggleDns = async (
action: 'enable' | 'disable',
appsToChange?: string[],
hours: number = 0,
minutes: number = 0
) => {
const duration = hours * 3600 + minutes * 60;
await mutateAsync(
{
action,
duration,
configName,
appsToChange,
},
Expand All @@ -137,40 +154,68 @@ function DnsHoleControlsWidgetTile({ widget }: DnsHoleControlsWidgetProps) {
},
}
);
setAppId('');
};

return (
<Stack justify="space-between" h={'100%'} spacing="0.25rem">
<Stack h="100%" spacing="0.25rem">
{enableControls && widget.properties.showToggleAllButtons && (
<SimpleGrid
ref={ref}
cols={width > 275 ? 2 : 1}
verticalSpacing="0.25rem"
spacing="0.25rem"
>
<Button
onClick={() => toggleDns('enable', getDnsStatus()?.disabled)}
disabled={getDnsStatus()?.disabled.length === 0 || fetchingDnsSummary || changingStatus}
leftIcon={<IconPlayerPlay size={20} />}
variant="light"
color="green"
h="2rem"
>
{t('enableAll')}
</Button>
<Button
onClick={() => toggleDns('disable', getDnsStatus()?.enabled)}
disabled={getDnsStatus()?.enabled.length === 0 || fetchingDnsSummary || changingStatus}
leftIcon={<IconPlayerStop size={20} />}
variant="light"
color="red"
h="2rem"
>
{t('disableAll')}
</Button>
</SimpleGrid>
<Flex gap="xs">
<Tooltip label={t('enableAll')}>
<Button
onClick={() => toggleDns('enable', getDnsStatus()?.disabled)}
disabled={
getDnsStatus()?.disabled.length === 0 || fetchingDnsSummary || changingStatus
}
variant="light"
color="green"
fullWidth
h="2rem"
>
<IconPlayerPlay size={20} />
</Button>
</Tooltip>

<Tooltip label={t('setTimer')}>
<Button
onClick={open}
disabled={
getDnsStatus()?.enabled.length === 0 || fetchingDnsSummary || changingStatus
}
variant="light"
color="yellow"
fullWidth
h="2rem"
>
<IconClockPause size={20} />
</Button>
</Tooltip>

<Tooltip label={t('disableAll')}>
<Button
onClick={() => toggleDns('disable', getDnsStatus()?.enabled)}
disabled={
getDnsStatus()?.enabled.length === 0 || fetchingDnsSummary || changingStatus
}
variant="light"
color="red"
fullWidth
h="2rem"
>
<IconPlayerStop size={20} />
</Button>
</Tooltip>
</Flex>
)}

<TimerModal
toggleDns={toggleDns}
getDnsStatus={getDnsStatus}
opened={opened}
close={close}
appId={appId}
/>

<Stack
spacing="0.25rem"
display="flex"
Expand Down Expand Up @@ -203,36 +248,50 @@ function DnsHoleControlsWidgetTile({ widget }: DnsHoleControlsWidgetProps) {
</Box>
<Stack spacing="0rem">
<Text>{app.name}</Text>
<UnstyledButton
onClick={() =>
toggleDns(dnsHole.status === 'enabled' ? 'disable' : 'enable', [app.id])
}
disabled={fetchingDnsSummary || changingStatus}
style={{ pointerEvents: enableControls ? 'auto' : 'none' }}
>
<Badge
variant="dot"
color={dnsLightStatus(fetchingDnsSummary || changingStatus, dnsHole.status)}
styles={(theme) => ({
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],
<Flex direction="row" gap="md">
<UnstyledButton
onClick={() =>
toggleDns(dnsHole.status === 'enabled' ? 'disable' : 'enable', [app.id])
}
disabled={fetchingDnsSummary || changingStatus}
style={{ pointerEvents: enableControls ? 'auto' : 'none' }}
>
<Badge
variant="dot"
color={dnsLightStatus(fetchingDnsSummary || changingStatus, dnsHole.status)}
styles={(theme) => ({
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)}
</Badge>
</UnstyledButton>
<ActionIcon
size={20}
radius="xl"
top="2.67px"
variant="default"
onClick={() => {
setAppId(app.id);
open();
}}
>
{t(dnsHole.status)}
</Badge>
</UnstyledButton>
<IconClockPause size={20} color="red" />
</ActionIcon>
</Flex>
</Stack>
</Group>
</Card>
Expand Down
4 changes: 2 additions & 2 deletions src/widgets/dnshole/DnsHoleSummary.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Box, Card, Center, Container, Flex, Text } from '@mantine/core';
import { Card, Center, Container, Flex, Text } from '@mantine/core';
import { useElementSize } from '@mantine/hooks';
import {
IconAd,
Expand Down Expand Up @@ -112,7 +112,7 @@ export const useDnsHoleSummeryQuery = () => {
configName: configName!,
},
{
staleTime: 1000 * 60 * 2,
refetchInterval: 1000 * 60 * 2,
}
);
};
Expand Down
Loading

0 comments on commit afdca50

Please sign in to comment.