From 9c48e96939c939b82a57b5c6690872faac8ded26 Mon Sep 17 00:00:00 2001 From: Dimitry Kolyshev Date: Fri, 24 Mar 2023 15:11:47 +0300 Subject: [PATCH] Pull request: 1333-protection-pause vol.1 Merge in DNS/adguard-home from 1333-protection-pause-1 to master Squashed commit of the following: commit 5ff98385bc5ff66e214d80782eb4dc41e344aa38 Merge: 97f94a54 0bc3ef89 Author: Dimitry Kolyshev Date: Fri Mar 24 19:08:21 2023 +0700 Merge remote-tracking branch 'origin/master' into 1333-protection-pause-1 commit 97f94a5498ac221f88f2f7dfef4b255f4945329e Author: Arseny Lisin Date: Fri Mar 24 13:03:20 2023 +0200 Fix protection timer bugs commit 1cc61af1996bd803f3fa638cb9e2388470072bf0 Merge: 5a144ea3 235ce458 Author: Dimitry Kolyshev Date: Thu Mar 23 22:27:47 2023 +0700 Merge remote-tracking branch 'origin/1333-protection-pause-1' into 1333-protection-pause-1 commit 5a144ea3a48c3d0d5e57dd14232ab7a8e77a8c1e Author: Dimitry Kolyshev Date: Thu Mar 23 22:25:08 2023 +0700 dnsforward: imp code commit 235ce458a62b3152f36e32580ed0226a56580ec6 Author: Ainar Garipov Date: Thu Mar 23 17:35:06 2023 +0300 dnsforward: imp locks commit 0ea3a0a176b810a2b3f0b307aa406fe1670c9219 Merge: 52f66810 df61741f Author: Dimitry Kolyshev Date: Thu Mar 23 19:30:41 2023 +0700 Merge remote-tracking branch 'origin/master' into 1333-protection-pause-1 # Conflicts: # CHANGELOG.md # openapi/CHANGELOG.md commit 52f668109673286a50909c042e6352cd803e8eed Merge: 9a7eb7b3 306c1983 Author: Dimitry Kolyshev Date: Thu Mar 23 11:23:50 2023 +0700 Merge remote-tracking branch 'origin/master' into 1333-protection-pause-1 # Conflicts: # CHANGELOG.md # internal/dnsforward/http.go commit 9a7eb7b3ab2b5f6ad321aa3245d33839c3aa6fbd Author: Arseny Lisin Date: Wed Mar 22 06:56:55 2023 +0200 Review fix commit 5612d51252ba91842bd6811baec1c91136bb3bf2 Merge: c0a918a5 c3edab43 Author: Dimitry Kolyshev Date: Tue Mar 21 22:00:39 2023 +0700 Merge remote-tracking branch 'origin/master' into 1333-protection-pause-1 # Conflicts: # client/src/__locales/en.json commit c0a918a518ad9b37041aed159d215516258bc987 Author: Arseny Lisin Date: Tue Mar 21 12:13:18 2023 +0200 Review fix commit 34faa61cc1e6210a612e7a2f4895a1504df37680 Author: Arseny Lisin Date: Tue Mar 21 10:43:37 2023 +0200 Fix props to new api commit 158e582373863495f0e0ca177d7b365cc66ad671 Author: Arseny Lisin Date: Mon Mar 20 18:44:34 2023 +0200 Review fix commit 9e8b8c3778b8e1dfad0d39e44f70886dfd3aeb9a Author: Dimitry Kolyshev Date: Mon Mar 20 22:31:28 2023 +0700 all: docs commit 761a203f53b535ca235cfe62f289bd0e02b90be2 Merge: d0b07231 48431f8b Author: Dimitry Kolyshev Date: Mon Mar 20 22:26:13 2023 +0700 Merge remote-tracking branch 'origin/master' into 1333-protection-pause-1 commit d0b07231b6f29b534930f1fcfc82b4934c295ff8 Merge: ea448760 a2053526 Author: Dimitry Kolyshev Date: Mon Mar 13 13:00:52 2023 +0700 Merge remote-tracking branch 'origin/master' into 1333-protection-pause-1 # Conflicts: # CHANGELOG.md # client/src/components/App/index.css # internal/dnsforward/config.go commit ea4487608a9c81d25f155ff63fee7c9dcf21f448 Merge: dfd0f33f a556ce8f Author: Dimitry Kolyshev Date: Tue Feb 21 11:54:27 2023 +0700 Merge remote-tracking branch 'origin/master' into 1333-protection-pause-1 # Conflicts: # CHANGELOG.md commit dfd0f33fb474d497cbc9237ee466276728eea397 Author: Dimitry Kolyshev Date: Tue Feb 21 11:51:40 2023 +0700 all: docs commit d36df96fba8c6d923faef85c198b6bd0743b7ee8 Author: Dimitry Kolyshev Date: Mon Feb 20 12:41:49 2023 +0700 all: safesearch commit 60f2ceec563221337f34bb60baa96aa2b2429c40 Merge: 7c514427 6f6ced33 Author: Dimitry Kolyshev Date: Mon Feb 20 12:30:42 2023 +0700 Merge remote-tracking branch 'origin/master' into 1333-protection-pause-1 # Conflicts: # CHANGELOG.md commit 7c514427e77c5b09d8e148c78220a71046e68cd1 Merge: 0fa4ff99 4d295a38 Author: Dimitry Kolyshev Date: Thu Feb 16 11:55:21 2023 +0700 Merge remote-tracking branch 'origin/master' into 1333-protection-pause-1 # Conflicts: # CHANGELOG.md ... and 26 more commits --- AGHTechDoc.md | 1 + CHANGELOG.md | 6 ++ client/src/__locales/en.json | 17 +++- client/src/actions/index.js | 56 ++++++++++- client/src/api/Api.js | 9 ++ client/src/components/App/index.js | 2 + client/src/components/Dashboard/Dashboard.css | 15 ++- client/src/components/Dashboard/index.js | 98 ++++++++++++++++--- .../src/components/ProtectionTimer/index.js | 52 ++++++++++ client/src/components/ui/Dropdown.css | 56 +++++++++++ client/src/components/ui/Icons.js | 6 ++ client/src/components/ui/Tabler.css | 12 +++ client/src/helpers/constants.js | 11 +++ client/src/helpers/helpers.js | 6 ++ client/src/reducers/dashboard.js | 14 ++- internal/dnsforward/config.go | 34 +++++++ internal/dnsforward/dns.go | 2 +- internal/dnsforward/http.go | 61 +++++++++++- .../TestDNSForwardHTTP_handleGetConfig.json | 3 + .../TestDNSForwardHTTP_handleSetConfig.json | 19 ++++ internal/home/control.go | 36 ++++--- openapi/CHANGELOG.md | 24 ++++- openapi/openapi.yaml | 34 +++++++ 23 files changed, 528 insertions(+), 46 deletions(-) create mode 100644 client/src/components/ProtectionTimer/index.js diff --git a/AGHTechDoc.md b/AGHTechDoc.md index 8f69e5bd..c1b53300 100644 --- a/AGHTechDoc.md +++ b/AGHTechDoc.md @@ -399,6 +399,7 @@ Response: "protection_enabled":true, "running":true, "dhcp_available":true, + "protection_disabled_duration":0 "version":"undefined" } diff --git a/CHANGELOG.md b/CHANGELOG.md index a8f44577..d14a6f5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,11 @@ NOTE: Add new changes BELOW THIS COMMENT. ### Added +- The new HTTP API `POST /control/protection`, that updates protection state + and adds an optional pause duration ([#1333]). The format of request body + is described in `openapi/openapi.yaml`. The duration of this pause could + also be set with the config field `protection_disabled_until` in `dns` + section of the YAML configuration file. - Two new HTTP APIs, `PUT /control/stats/config/update` and `GET control/stats/config`, which can be used to set and receive the query log configuration. See openapi/openapi.yaml for the full description. @@ -122,6 +127,7 @@ In this release, the schema version has changed from 17 to 20. ([#5584]). [#1163]: https://github.com/AdguardTeam/AdGuardHome/issues/1163 +[#1333]: https://github.com/AdguardTeam/AdGuardHome/issues/1333 [#1472]: https://github.com/AdguardTeam/AdGuardHome/issues/1472 [#5567]: https://github.com/AdguardTeam/AdGuardHome/issues/5567 [#5584]: https://github.com/AdguardTeam/AdGuardHome/issues/5584 diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json index 08f3c08f..2448b80f 100644 --- a/client/src/__locales/en.json +++ b/client/src/__locales/en.json @@ -650,5 +650,20 @@ "confirm_dns_cache_clear": "Are you sure you want to clear DNS cache?", "cache_cleared": "DNS cache successfully cleared", "clear_cache": "Clear cache", - "make_static": "Make static" + "make_static": "Make static", + "disable_for_seconds": "For {{count}} second", + "disable_for_seconds_plural": "For {{count}} seconds", + "disable_for_minutes": "For {{count}} minute", + "disable_for_minutes_plural": "For {{count}} minutes", + "disable_for_hours": "For {{count}} hour", + "disable_for_hours_plural": "For {{count}} hours", + "disable_until_tomorrow": "Until tomorrow", + "disable_notify_for_seconds": "Disable protection for {{count}} second", + "disable_notify_for_seconds_plural": "Disable protection for {{count}} seconds", + "disable_notify_for_minutes": "Disable protection for {{count}} minute", + "disable_notify_for_minutes_plural": "Disable protection for {{count}} minutes", + "disable_notify_for_hours": "Disable protection for {{count}} hour", + "disable_notify_for_hours_plural": "Disable protection for {{count}} hours", + "disable_notify_until_tomorrow": "Disable protection until tomorrow", + "enable_protection_timer": "Protection will be enabled in {{time}}" } diff --git a/client/src/actions/index.js b/client/src/actions/index.js index a164f51a..5d96b045 100644 --- a/client/src/actions/index.js +++ b/client/src/actions/index.js @@ -6,7 +6,14 @@ import endsWith from 'lodash/endsWith'; import escapeRegExp from 'lodash/escapeRegExp'; import React from 'react'; import { compose } from 'redux'; -import { splitByNewLine, sortClients, filterOutComments } from '../helpers/helpers'; +import { + splitByNewLine, + sortClients, + filterOutComments, + msToSeconds, + msToMinutes, + msToHours, +} from '../helpers/helpers'; import { BLOCK_ACTIONS, CHECK_TIMEOUT, @@ -14,6 +21,7 @@ import { SETTINGS_NAMES, FORM_NAME, MANUAL_UPDATE_LINK, + DISABLE_PROTECTION_TIMINGS, } from '../helpers/constants'; import { areEqualVersions } from '../helpers/version'; import { getTlsStatus } from './encryption'; @@ -108,19 +116,54 @@ export const toggleProtectionRequest = createAction('TOGGLE_PROTECTION_REQUEST') export const toggleProtectionFailure = createAction('TOGGLE_PROTECTION_FAILURE'); export const toggleProtectionSuccess = createAction('TOGGLE_PROTECTION_SUCCESS'); -export const toggleProtection = (status) => async (dispatch) => { +const getDisabledMessage = (time) => { + switch (time) { + case DISABLE_PROTECTION_TIMINGS.HALF_MINUTE: + return i18next.t( + 'disable_notify_for_seconds', + { count: msToSeconds(DISABLE_PROTECTION_TIMINGS.HALF_MINUTE) }, + ); + case DISABLE_PROTECTION_TIMINGS.MINUTE: + return i18next.t( + 'disable_notify_for_minutes', + { count: msToMinutes(DISABLE_PROTECTION_TIMINGS.MINUTE) }, + ); + case DISABLE_PROTECTION_TIMINGS.TEN_MINUTES: + return i18next.t( + 'disable_notify_for_minutes', + { count: msToMinutes(DISABLE_PROTECTION_TIMINGS.TEN_MINUTES) }, + ); + case DISABLE_PROTECTION_TIMINGS.HOUR: + return i18next.t( + 'disable_notify_for_hours', + { count: msToHours(DISABLE_PROTECTION_TIMINGS.HOUR) }, + ); + case DISABLE_PROTECTION_TIMINGS.TOMORROW: + return i18next.t('disable_notify_until_tomorrow'); + default: + return 'disabled_protection'; + } +}; + +export const toggleProtection = (status, time = null) => async (dispatch) => { dispatch(toggleProtectionRequest()); try { - const successMessage = status ? 'disabled_protection' : 'enabled_protection'; - await apiClient.setDnsConfig({ protection_enabled: !status }); + const successMessage = status ? getDisabledMessage(time) : 'enabled_protection'; + await apiClient.setProtection({ enabled: !status, duration: time }); dispatch(addSuccessToast(successMessage)); - dispatch(toggleProtectionSuccess()); + dispatch(toggleProtectionSuccess({ disabledDuration: time })); } catch (error) { dispatch(addErrorToast({ error })); dispatch(toggleProtectionFailure()); } }; +export const setDisableDurationTime = createAction('SET_DISABLED_DURATION_TIME'); + +export const setProtectionTimerTime = (updatedTime) => async (dispatch) => { + dispatch(setDisableDurationTime({ timeToEnableProtection: updatedTime })); +}; + export const getVersionRequest = createAction('GET_VERSION_REQUEST'); export const getVersionFailure = createAction('GET_VERSION_FAILURE'); export const getVersionSuccess = createAction('GET_VERSION_SUCCESS'); @@ -273,6 +316,9 @@ export const getDnsStatus = () => async (dispatch) => { const handleRequestSuccess = (response) => { const dnsStatus = response.data; + if (dnsStatus.protection_disabled_duration === 0) { + dnsStatus.protection_disabled_duration = null; + } const { running } = dnsStatus; const runningStatus = dnsStatus && running; if (runningStatus === true) { diff --git a/client/src/api/Api.js b/client/src/api/Api.js index caf836b8..7ca33293 100644 --- a/client/src/api/Api.js +++ b/client/src/api/Api.js @@ -627,6 +627,15 @@ class Api { return this.makeRequest(path, method, config); } + SET_PROTECTION = { path: 'protection', method: 'POST' }; + + setProtection(data) { + const { enabled, duration } = data; + const { path, method } = this.SET_PROTECTION; + + return this.makeRequest(path, method, { data: { enabled, duration } }); + } + // Cache CLEAR_CACHE = { path: 'cache_clear', method: 'POST' }; diff --git a/client/src/components/App/index.js b/client/src/components/App/index.js index 819bb0c6..3d2db100 100644 --- a/client/src/components/App/index.js +++ b/client/src/components/App/index.js @@ -43,6 +43,7 @@ import DnsRewrites from '../../containers/DnsRewrites'; import CustomRules from '../../containers/CustomRules'; import Services from '../Filters/Services'; import Logs from '../Logs'; +import ProtectionTimer from '../ProtectionTimer'; const ROUTES = [ { @@ -191,6 +192,7 @@ const App = () => { {!processingEncryption && }
+
{processing && } {!isCoreRunning &&
diff --git a/client/src/components/Dashboard/Dashboard.css b/client/src/components/Dashboard/Dashboard.css index 415a3f6b..765c9ed1 100644 --- a/client/src/components/Dashboard/Dashboard.css +++ b/client/src/components/Dashboard/Dashboard.css @@ -1,3 +1,9 @@ +.dashboard-protection-button.btn-gray { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-right-color: #a4a4a4; +} + .stats__table .popover__body { left: -10px; min-width: 270px; @@ -34,20 +40,11 @@ align-items: center; } -.dashboard-title__button { - margin: 0 0.5rem; -} - @media (max-width: 767.98px) { .page-title--dashboard { flex-direction: column; align-items: flex-start; } - - .dashboard-title__button { - margin: 0.5rem 0; - display: block; - } } .counters__row { diff --git a/client/src/components/Dashboard/index.js b/client/src/components/Dashboard/index.js index cc19acf0..ab8d4efc 100644 --- a/client/src/components/Dashboard/index.js +++ b/client/src/components/Dashboard/index.js @@ -9,18 +9,20 @@ import Counters from './Counters'; import Clients from './Clients'; import QueriedDomains from './QueriedDomains'; import BlockedDomains from './BlockedDomains'; -import { SETTINGS_URLS } from '../../helpers/constants'; +import { DISABLE_PROTECTION_TIMINGS, ONE_SECOND_IN_MS, SETTINGS_URLS } from '../../helpers/constants'; +import { msToSeconds, msToMinutes, msToHours } from '../../helpers/helpers'; import PageTitle from '../ui/PageTitle'; import Loading from '../ui/Loading'; import './Dashboard.css'; +import Dropdown from '../ui/Dropdown'; const Dashboard = ({ getAccessList, getStats, getStatsConfig, dashboard, - dashboard: { protectionEnabled, processingProtection }, + dashboard: { protectionEnabled, processingProtection, protectionDisabledDuration }, toggleProtection, stats, access, @@ -36,7 +38,6 @@ const Dashboard = ({ useEffect(() => { getAllStats(); }, []); - const getSubtitle = () => { if (stats.interval === 0) { return t('stats_disabled_short'); @@ -47,9 +48,7 @@ const Dashboard = ({ : t('for_last_days', { count: stats.interval }); }; - const buttonText = protectionEnabled ? 'disable_protection' : 'enable_protection'; - - const buttonClass = classNames('btn btn-sm dashboard-title__button', { + const buttonClass = classNames('btn btn-sm dashboard-protection-button', { 'btn-gray': protectionEnabled, 'btn-success': !protectionEnabled, }); @@ -71,16 +70,87 @@ const Dashboard = ({ const subtitle = getSubtitle(); + const DISABLE_PROTECTION_ITEMS = [ + { + text: t('disable_for_seconds', { count: msToSeconds(DISABLE_PROTECTION_TIMINGS.HALF_MINUTE) }), + disableTime: DISABLE_PROTECTION_TIMINGS.HALF_MINUTE, + }, + { + text: t('disable_for_minutes', { count: msToMinutes(DISABLE_PROTECTION_TIMINGS.MINUTE) }), + disableTime: DISABLE_PROTECTION_TIMINGS.MINUTE, + }, + { + text: t('disable_for_minutes', { count: msToMinutes(DISABLE_PROTECTION_TIMINGS.TEN_MINUTES) }), + disableTime: DISABLE_PROTECTION_TIMINGS.TEN_MINUTES, + }, + { + text: t('disable_for_hours', { count: msToHours(DISABLE_PROTECTION_TIMINGS.HOUR) }), + disableTime: DISABLE_PROTECTION_TIMINGS.HOUR, + }, + { + text: t('disable_until_tomorrow'), + disableTime: DISABLE_PROTECTION_TIMINGS.TOMORROW, + }, + ]; + + const getDisableProtectionItems = () => ( + Object.values(DISABLE_PROTECTION_ITEMS) + .map((item, index) => ( +
{ + toggleProtection(protectionEnabled, item.disableTime - ONE_SECOND_IN_MS); + }} + > + {item.text} +
+ )) + ); + + const getRemaningTimeText = (milliseconds) => { + if (!milliseconds) { + return ''; + } + + const date = new Date(milliseconds); + const hh = date.getUTCHours(); + const mm = `0${date.getUTCMinutes()}`.slice(-2); + const ss = `0${date.getUTCSeconds()}`.slice(-2); + const formattedHH = `0${hh}`.slice(-2); + + return hh ? `${formattedHH}:${mm}:${ss}` : `${mm}:${ss}`; + }; + + const getProtectionBtnText = (status) => (status ? t('disable_protection') : t('enable_protection')); + return <> - +
+ + + {protectionEnabled && + {getDisableProtectionItems()} + } +