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 <dkolyshev@adguard.com>
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 <a.lisin@adguard.com>
Date:   Fri Mar 24 13:03:20 2023 +0200

    Fix protection timer bugs

commit 1cc61af1996bd803f3fa638cb9e2388470072bf0
Merge: 5a144ea3 235ce458
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
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 <dkolyshev@adguard.com>
Date:   Thu Mar 23 22:25:08 2023 +0700

    dnsforward: imp code

commit 235ce458a62b3152f36e32580ed0226a56580ec6
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Mar 23 17:35:06 2023 +0300

    dnsforward: imp locks

commit 0ea3a0a176b810a2b3f0b307aa406fe1670c9219
Merge: 52f66810 df61741f
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
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 <dkolyshev@adguard.com>
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 <a.lisin@adguard.com>
Date:   Wed Mar 22 06:56:55 2023 +0200

    Review fix

commit 5612d51252ba91842bd6811baec1c91136bb3bf2
Merge: c0a918a5 c3edab43
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
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 <a.lisin@adguard.com>
Date:   Tue Mar 21 12:13:18 2023 +0200

    Review fix

commit 34faa61cc1e6210a612e7a2f4895a1504df37680
Author: Arseny Lisin <a.lisin@adguard.com>
Date:   Tue Mar 21 10:43:37 2023 +0200

    Fix props to new api

commit 158e582373863495f0e0ca177d7b365cc66ad671
Author: Arseny Lisin <a.lisin@adguard.com>
Date:   Mon Mar 20 18:44:34 2023 +0200

    Review fix

commit 9e8b8c3778b8e1dfad0d39e44f70886dfd3aeb9a
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Mon Mar 20 22:31:28 2023 +0700

    all: docs

commit 761a203f53b535ca235cfe62f289bd0e02b90be2
Merge: d0b07231 48431f8b
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
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 <dkolyshev@adguard.com>
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 <dkolyshev@adguard.com>
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 <dkolyshev@adguard.com>
Date:   Tue Feb 21 11:51:40 2023 +0700

    all: docs

commit d36df96fba8c6d923faef85c198b6bd0743b7ee8
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Mon Feb 20 12:41:49 2023 +0700

    all: safesearch

commit 60f2ceec563221337f34bb60baa96aa2b2429c40
Merge: 7c514427 6f6ced33
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
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 <dkolyshev@adguard.com>
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
This commit is contained in:
Dimitry Kolyshev
2023-03-24 15:11:47 +03:00
parent 0bc3ef89ea
commit 9c48e96939
23 changed files with 528 additions and 46 deletions

View File

@@ -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}}"
}

View File

@@ -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) {

View File

@@ -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' };

View File

@@ -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 && <EncryptionTopline />}
<LoadingBar className="loading-bar" updateTime={1000} />
<Header />
<ProtectionTimer />
<div className="container container--wrap pb-5">
{processing && <Loading />}
{!isCoreRunning && <div className="row row-cards">

View File

@@ -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 {

View File

@@ -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) => (
<div
key={`disable_timings_${index}`}
className="dropdown-item"
onClick={() => {
toggleProtection(protectionEnabled, item.disableTime - ONE_SECOND_IN_MS);
}}
>
{item.text}
</div>
))
);
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 <>
<PageTitle title={t('dashboard')} containerClass="page-title--dashboard">
<button
type="button"
className={buttonClass}
onClick={() => toggleProtection(protectionEnabled)}
disabled={processingProtection}
>
<Trans>{buttonText}</Trans>
</button>
<div className="page-title__protection">
<button
type="button"
className={buttonClass}
onClick={() => {
toggleProtection(protectionEnabled);
}}
disabled={processingProtection}
>
{protectionDisabledDuration
? `${t('enable_protection_timer')} ${getRemaningTimeText(protectionDisabledDuration)}`
: getProtectionBtnText(protectionEnabled)
}
</button>
{protectionEnabled && <Dropdown
label=""
baseClassName="dropdown-protection"
icon="arrow-down"
controlClassName="dropdown-protection__toggle"
menuClassName="dropdown-menu dropdown-menu-arrow dropdown-menu--protection"
>
{getDisableProtectionItems()}
</Dropdown>}
</div>
<button
type="button"
className="btn btn-outline-primary btn-sm"

View File

@@ -0,0 +1,52 @@
import { useEffect } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { ONE_SECOND_IN_MS } from '../../helpers/constants';
import { setProtectionTimerTime, toggleProtectionSuccess } from '../../actions';
let interval = null;
const ProtectionTimer = ({
protectionDisabledDuration,
toggleProtectionSuccess,
setProtectionTimerTime,
}) => {
useEffect(() => {
if (protectionDisabledDuration !== null && protectionDisabledDuration < ONE_SECOND_IN_MS) {
toggleProtectionSuccess({ disabledDuration: null });
}
if (protectionDisabledDuration) {
interval = setInterval(() => {
setProtectionTimerTime(protectionDisabledDuration - ONE_SECOND_IN_MS);
}, ONE_SECOND_IN_MS);
}
return () => {
clearInterval(interval);
};
}, [protectionDisabledDuration]);
return null;
};
ProtectionTimer.propTypes = {
setProtectionTimerTime: PropTypes.func.isRequired,
};
const mapStateToProps = (state) => {
const { dashboard } = state;
const { protectionEnabled, protectionDisabledDuration } = dashboard;
return { protectionEnabled, protectionDisabledDuration };
};
const mapDispatchToProps = {
toggleProtectionSuccess,
setProtectionTimerTime,
};
export default connect(
mapStateToProps,
mapDispatchToProps,
)(ProtectionTimer);

View File

@@ -1,3 +1,7 @@
.dropdown-item {
cursor: pointer;
}
.dropdown-item.active,
.dropdown-item:active {
background-color: var(--btn-success-bgcolor);
@@ -6,3 +10,55 @@
.dropdown-menu {
cursor: default;
}
.dropdown-menu.dropdown-menu--protection {
top: calc(100% + 8px);
left: 50%;
transform: translateX(-50%);
}
.dropdown-menu.dropdown-menu-arrow.dropdown-menu--protection::before,
.dropdown-menu.dropdown-menu-arrow.dropdown-menu--protection::after {
left: 50%;
transform: translateX(-50%);
}
.dropdown-protection {
align-self: stretch;
width: 26px;
display: flex;
position: relative;
border: 1px solid #868e96;
border-top-right-radius: 3px;
border-bottom-right-radius: 3px;
border-left: none;
cursor: pointer;
}
.dropdown-protection__toggle {
width: 100%;
display: block;
position: relative;
background-color: #868e96;
transition: background-color 0.15s ease-in-out;
}
.dropdown-protection__toggle:hover {
background-color: #727b84;
}
.dropdown-protection__toggle .nav-icon {
width: 100%;
height: 100%;
display: block;
position: absolute;
top: 0;
left: 0;
color: var(--white);
transition: 0.15s ease-in-out transform;
transform-origin: center;
}
.dropdown-protection.show .nav-icon {
transform: rotate(180deg);
}

View File

@@ -181,6 +181,12 @@ const Icons = () => (
</svg>
</symbol>
<symbol id="arrow-down" viewBox="0 0 24 24" fill="currentColor">
<svg xmlns="http://www.w3.org/2000/svg">
<path fillRule="evenodd" d="M6.2 8.2a.64.64 0 0 1 .94 0L12 13.32l4.86-5.1a.64.64 0 0 1 .94 0c.27.27.27.71 0 .98l-5.33 5.6a.64.64 0 0 1-.94 0L6.2 9.2a.72.72 0 0 1 0-.98Z" clipRule="evenodd"/>
</svg>
</symbol>
<symbol id="arrow-right" viewBox="0 0 24 24" stroke="currentColor"
strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.5">
<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg">

View File

@@ -10216,6 +10216,18 @@ body.fixed-header .page {
line-height: 2.5rem;
}
.page-title__protection {
display: flex;
align-items: stretch;
margin: 0 0.5rem;
}
@media (max-width: 767.98px) {
.page-title__protection {
margin: 0.5rem 0;
}
}
.page-title-icon {
color: #9aa0ac;
font-size: 1.25rem;

View File

@@ -498,6 +498,8 @@ export const TOAST_TYPES = {
};
export const SUCCESS_TOAST_TIMEOUT = 5000;
export const ONE_SECOND_IN_MS = 1000;
export const FAILURE_TOAST_TIMEOUT = 30000;
export const TOAST_TIMEOUTS = {
@@ -526,3 +528,12 @@ export const MOBILE_CONFIG_LINKS = {
DOT: 'apple/dot.mobileconfig',
DOH: 'apple/doh.mobileconfig',
};
// Timings for disable protection in milliseconds
export const DISABLE_PROTECTION_TIMINGS = {
HALF_MINUTE: 30 * 1000,
MINUTE: 60 * 1000,
TEN_MINUTES: 10 * 60 * 1000,
HOUR: 60 * 60 * 1000,
TOMORROW: 24 * 60 * 60 * 1000,
};

View File

@@ -388,6 +388,12 @@ export const toggleAllServices = (services, change, isSelected) => {
services.forEach((service) => change(`blocked_services.${service.id}`, isSelected));
};
export const msToSeconds = (milliseconds) => Math.floor(milliseconds / 1000);
export const msToMinutes = (milliseconds) => Math.floor(milliseconds / 1000 / 60);
export const msToHours = (milliseconds) => Math.floor(milliseconds / 1000 / 60 / 60);
export const secondsToMilliseconds = (seconds) => {
if (seconds) {
return seconds * 1000;

View File

@@ -25,6 +25,7 @@ const dashboard = handleActions(
dns_port: dnsPort,
dns_addresses: dnsAddresses,
protection_enabled: protectionEnabled,
protection_disabled_duration: protectionDisabledDuration,
http_port: httpPort,
language,
} = payload;
@@ -36,9 +37,11 @@ const dashboard = handleActions(
dnsPort,
dnsAddresses,
protectionEnabled,
protectionDisabledDuration,
language,
httpPort,
};
return newState;
},
@@ -103,15 +106,22 @@ const dashboard = handleActions(
...state,
processingProtection: false,
}),
[actions.toggleProtectionSuccess]: (state) => {
[actions.toggleProtectionSuccess]: (state, { payload }) => {
const newState = {
...state,
protectionEnabled: !state.protectionEnabled,
processingProtection: false,
protectionDisabledDuration: payload.disabledDuration,
};
return newState;
},
[actions.setDisableDurationTime]: (state, { payload }) => ({
...state,
protectionDisabledDuration: payload.timeToEnableProtection,
}),
[actions.getClientsRequest]: (state) => ({
...state,
processingClients: true,
@@ -156,6 +166,8 @@ const dashboard = handleActions(
processingUpdate: false,
processingProfile: true,
protectionEnabled: false,
protectionDisabledDuration: null,
protectionCountdownActive: false,
processingProtection: false,
httpPort: STANDARD_WEB_PORT,
dnsPort: STANDARD_DNS_PORT,