Skip to content

Commit

Permalink
Merge branch 'master' into 3290-docker-healthcheck
Browse files Browse the repository at this point in the history
  • Loading branch information
EugeneOne1 committed Mar 24, 2023
2 parents e33b0c5 + fb7b8bb commit 8c3798c
Show file tree
Hide file tree
Showing 53 changed files with 1,296 additions and 340 deletions.
1 change: 1 addition & 0 deletions AGHTechDoc.md
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,7 @@ Response:
"protection_enabled":true,
"running":true,
"dhcp_available":true,
"protection_disabled_duration":0
"version":"undefined"
}

Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ NOTE: Add new changes BELOW THIS COMMENT.
### Added

- Docker container's healthcheck ([#3290]).
- 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.
Expand Down Expand Up @@ -119,14 +124,17 @@ In this release, the schema version has changed from 17 to 20.

### Fixed

- Panic in empty hostname in the filter's URL ([#5631]).
- Panic caused by empty top-level domain name label in `/etc/hosts` files
([#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
[#3290]: https://github.com/AdguardTeam/AdGuardHome/issues/3290
[#5567]: https://github.com/AdguardTeam/AdGuardHome/issues/5567
[#5584]: https://github.com/AdguardTeam/AdGuardHome/issues/5584
[#5631]: https://github.com/AdguardTeam/AdGuardHome/issues/5631

[rfc6761]: https://www.rfc-editor.org/rfc/rfc6761

Expand Down
20 changes: 19 additions & 1 deletion client/src/__locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -650,5 +650,23 @@
"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",
"theme_auto_desc": "Auto (based on the color scheme of your device)",
"theme_dark_desc": "Dark theme",
"theme_light_desc": "Light theme",
"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}}"
}
56 changes: 51 additions & 5 deletions client/src/actions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,22 @@ 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,
STATUS_RESPONSE,
SETTINGS_NAMES,
FORM_NAME,
MANUAL_UPDATE_LINK,
DISABLE_PROTECTION_TIMINGS,
} from '../helpers/constants';
import { areEqualVersions } from '../helpers/version';
import { getTlsStatus } from './encryption';
Expand Down Expand Up @@ -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');
Expand Down Expand Up @@ -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) {
Expand Down
9 changes: 9 additions & 0 deletions client/src/api/Api.js
Original file line number Diff line number Diff line change
Expand Up @@ -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' };

Expand Down
2 changes: 2 additions & 0 deletions client/src/components/App/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
{
Expand Down Expand Up @@ -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">
Expand Down
15 changes: 6 additions & 9 deletions client/src/components/Dashboard/Dashboard.css
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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 {
Expand Down
112 changes: 95 additions & 17 deletions client/src/components/Dashboard/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,25 @@ 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,
msToDays,
} 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,
Expand All @@ -36,20 +43,20 @@ const Dashboard = ({
useEffect(() => {
getAllStats();
}, []);

const getSubtitle = () => {
if (stats.interval === 0) {
const ONE_DAY = 1;
const intervalInDays = msToDays(stats.interval);

if (intervalInDays < ONE_DAY) {
return t('stats_disabled_short');
}

return stats.interval === 1
return intervalInDays === ONE_DAY
? t('for_last_24_hours')
: t('for_last_days', { count: stats.interval });
: t('for_last_days', { count: msToDays(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,
});
Expand All @@ -71,16 +78,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"
Expand Down
Loading

0 comments on commit 8c3798c

Please sign in to comment.