Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Add dns disable timer #2029

Merged
merged 9 commits into from
May 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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,
},
hillaliy marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -137,40 +154,68 @@ function DnsHoleControlsWidgetTile({ widget }: DnsHoleControlsWidgetProps) {
},
}
);
setAppId('');
};

return (
<Stack justify="space-between" h={'100%'} spacing="0.25rem">
hillaliy marked this conversation as resolved.
Show resolved Hide resolved
<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}
hillaliy marked this conversation as resolved.
Show resolved Hide resolved
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"
hillaliy marked this conversation as resolved.
Show resolved Hide resolved
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