From e393acf5eb08251cd38073a181f5ddce08264af5 Mon Sep 17 00:00:00 2001 From: jvoisin Date: Mon, 21 Dec 2020 14:51:48 +0100 Subject: [PATCH 001/141] Remove a superfluous argument --- client/src/actions/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/actions/index.js b/client/src/actions/index.js index 04054091..58e2d018 100644 --- a/client/src/actions/index.js +++ b/client/src/actions/index.js @@ -287,7 +287,7 @@ export const getDnsStatus = () => async (dispatch) => { try { checkStatus(handleRequestSuccess, handleRequestError); } catch (error) { - handleRequestError(error); + handleRequestError(); } }; From 026cc2ecbfcb1d61c9c977ad0c515101c23abf1c Mon Sep 17 00:00:00 2001 From: jvoisin Date: Mon, 21 Dec 2020 17:00:27 +0100 Subject: [PATCH 002/141] Add a title to the refresh buttons This increases a bit the accessibility. --- client/src/components/Dashboard/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/client/src/components/Dashboard/index.js b/client/src/components/Dashboard/index.js index 4de5b8dd..fb9e3002 100644 --- a/client/src/components/Dashboard/index.js +++ b/client/src/components/Dashboard/index.js @@ -44,6 +44,7 @@ const Dashboard = ({ const refreshButton = ; }; -const getTitle = (reason) => { +const getTitle = () => { const { t } = useTranslation(); const filters = useSelector((state) => state.filtering.filters, shallowEqual); const whitelistFilters = useSelector((state) => state.filtering.whitelistFilters, shallowEqual); - const filter_id = useSelector((state) => state.filtering.check.filter_id); - - const filterName = getFilterName( - filters, - whitelistFilters, - filter_id, - 'filtered_custom_rules', - (filter) => (filter?.name ? t('query_log_filtered', { filter: filter.name }) : ''), - ); + const rules = useSelector((state) => state.filtering.check.rules, shallowEqual); + const reason = useSelector((state) => state.filtering.check.reason); const getReasonFiltered = (reason) => { const filterKey = reason.replace(FILTERED, ''); return i18next.t('query_log_filtered', { filter: filterKey }); }; + const ruleAndFilterNames = getRulesToFilterList(rules, filters, whitelistFilters); + const REASON_TO_TITLE_MAP = { [FILTERED_STATUS.NOT_FILTERED_NOT_FOUND]: t('check_not_found'), [FILTERED_STATUS.REWRITE]: t('rewrite_applied'), [FILTERED_STATUS.REWRITE_HOSTS]: t('rewrite_hosts_applied'), - [FILTERED_STATUS.FILTERED_BLACK_LIST]: filterName, - [FILTERED_STATUS.NOT_FILTERED_WHITE_LIST]: filterName, + [FILTERED_STATUS.FILTERED_BLACK_LIST]: ruleAndFilterNames, + [FILTERED_STATUS.NOT_FILTERED_WHITE_LIST]: ruleAndFilterNames, [FILTERED_STATUS.FILTERED_SAFE_SEARCH]: getReasonFiltered(reason), [FILTERED_STATUS.FILTERED_SAFE_BROWSING]: getReasonFiltered(reason), [FILTERED_STATUS.FILTERED_PARENTAL]: getReasonFiltered(reason), @@ -78,7 +73,11 @@ const getTitle = (reason) => { return <>
{t('check_reason', { reason })}
-
{filterName}
+
+ {t('rule_label')}: +   + {ruleAndFilterNames} +
; }; @@ -86,14 +85,13 @@ const Info = () => { const { hostname, reason, - rule, service_name, cname, ip_addrs, } = useSelector((state) => state.filtering.check, shallowEqual); const { t } = useTranslation(); - const title = getTitle(reason); + const title = getTitle(); const className = classNames('card mb-0 p-3', { 'logs__row--red': checkFiltered(reason), @@ -112,7 +110,6 @@ const Info = () => {
{title}
{!onlyFiltered && <> - {rule &&
{t('check_rule', { rule })}
} {service_name &&
{t('check_service', { service: service_name })}
} {cname &&
{t('check_cname', { cname })}
} {ip_addrs &&
{t('check_ip', { ip: ip_addrs.join(', ') })}
} diff --git a/client/src/components/Logs/Cells/ResponseCell.js b/client/src/components/Logs/Cells/ResponseCell.js index 816f35a3..026dbce1 100644 --- a/client/src/components/Logs/Cells/ResponseCell.js +++ b/client/src/components/Logs/Cells/ResponseCell.js @@ -4,8 +4,9 @@ import classNames from 'classnames'; import React from 'react'; import propTypes from 'prop-types'; import { + getRulesToFilterList, formatElapsedMs, - getFilterName, + getFilterNames, getServiceName, } from '../../../helpers/helpers'; import { FILTERED_STATUS, FILTERED_STATUS_TO_META_MAP } from '../../../helpers/constants'; @@ -18,8 +19,7 @@ const ResponseCell = ({ response, status, upstream, - rule, - filterId, + rules, service_name, }) => { const { t } = useTranslation(); @@ -36,7 +36,6 @@ const ResponseCell = ({ const statusLabel = t(isBlockedByResponse ? 'blocked_by_cname_or_ip' : FILTERED_STATUS_TO_META_MAP[reason]?.LABEL || reason); const boldStatusLabel = {statusLabel}; - const filter = getFilterName(filters, whitelistFilters, filterId); const renderResponses = (responseArr) => { if (!responseArr || responseArr.length === 0) { @@ -52,18 +51,23 @@ const ResponseCell = ({ })}; }; + const rulesList = getRulesToFilterList(rules, filters, whitelistFilters); + const COMMON_CONTENT = { encryption_status: boldStatusLabel, install_settings_dns: upstream, elapsed: formattedElapsedMs, response_code: status, - ...(service_name ? { service_name: getServiceName(service_name) } : { filter }), - rule_label: rule, + ...(service_name + ? { service_name: getServiceName(service_name) } + : { } + ), + rule_label: rulesList, response_table_header: renderResponses(response), original_response: renderResponses(originalResponse), }; - const content = rule + const content = rules.length > 0 ? Object.entries(COMMON_CONTENT) : Object.entries({ ...COMMON_CONTENT, @@ -78,7 +82,8 @@ const ResponseCell = ({ } return getServiceName(service_name); case FILTERED_STATUS.FILTERED_BLACK_LIST: - return filter; + case FILTERED_STATUS.NOT_FILTERED_WHITE_LIST: + return getFilterNames(rules, filters, whitelistFilters).join(', '); default: return formattedElapsedMs; } @@ -113,8 +118,10 @@ ResponseCell.propTypes = { response: propTypes.array.isRequired, status: propTypes.string.isRequired, upstream: propTypes.string.isRequired, - rule: propTypes.string, - filterId: propTypes.number, + rules: propTypes.arrayOf(propTypes.shape({ + text: propTypes.string.isRequired, + filter_list_id: propTypes.number.isRequired, + })), service_name: propTypes.string, }; diff --git a/client/src/components/Logs/Cells/index.js b/client/src/components/Logs/Cells/index.js index 2e2635d9..8435a617 100644 --- a/client/src/components/Logs/Cells/index.js +++ b/client/src/components/Logs/Cells/index.js @@ -6,11 +6,11 @@ import propTypes from 'prop-types'; import { captitalizeWords, checkFiltered, + getRulesToFilterList, formatDateTime, formatElapsedMs, formatTime, getBlockingClientName, - getFilterName, getServiceName, processContent, } from '../../../helpers/helpers'; @@ -70,8 +70,7 @@ const Row = memo(({ upstream, type, client_proto, - filterId, - rule, + rules, originalResponse, status, service_name, @@ -107,8 +106,6 @@ const Row = memo(({ const sourceData = getSourceData(tracker); - const filter = getFilterName(filters, whitelistFilters, filterId); - const { confirmMessage, buttonKey: blockingClientKey, @@ -172,8 +169,8 @@ const Row = memo(({ response_details: 'title', install_settings_dns: upstream, elapsed: formattedElapsedMs, - filter: rule ? filter : null, - rule_label: rule, + rule_label: rules.length > 0 + && getRulesToFilterList(rules, filters, whitelistFilters), response_table_header: response?.join('\n'), response_code: status, client_details: 'title', @@ -235,8 +232,10 @@ Row.propTypes = { upstream: propTypes.string.isRequired, type: propTypes.string.isRequired, client_proto: propTypes.string.isRequired, - filterId: propTypes.number, - rule: propTypes.string, + rules: propTypes.arrayOf(propTypes.shape({ + text: propTypes.string.isRequired, + filter_list_id: propTypes.number.isRequired, + })), originalResponse: propTypes.array, status: propTypes.string.isRequired, service_name: propTypes.string, diff --git a/client/src/components/Logs/Logs.css b/client/src/components/Logs/Logs.css index 64300bb9..a48a8d15 100644 --- a/client/src/components/Logs/Logs.css +++ b/client/src/components/Logs/Logs.css @@ -428,3 +428,13 @@ margin-right: 1px; opacity: 0.5; } + +.filteringRules__rule { + margin-bottom: 0; +} + +.filteringRules__filter { + font-style: italic; + font-weight: normal; + margin-bottom: 1rem; +} diff --git a/client/src/helpers/helpers.js b/client/src/helpers/helpers.js index 7d44b400..82f30245 100644 --- a/client/src/helpers/helpers.js +++ b/client/src/helpers/helpers.js @@ -7,6 +7,7 @@ import i18n from 'i18next'; import uniqBy from 'lodash/uniqBy'; import ipaddr from 'ipaddr.js'; import queryString from 'query-string'; +import React from 'react'; import { getTrackerData } from './trackers/trackers'; import { @@ -68,6 +69,7 @@ export const normalizeLogs = (logs) => logs.map((log) => { time, filterId, rule, + rules, service_name, original_answer, upstream, @@ -80,6 +82,15 @@ export const normalizeLogs = (logs) => logs.map((log) => { return `${type}: ${value} (ttl=${ttl})`; }) : []); + let newRules = rules; + /* TODO 'filterId' and 'rule' are deprecated, will be removed in 0.106 */ + if (rule !== undefined && filterId !== undefined && rules !== undefined && rules.length === 0) { + newRules = { + filter_list_id: filterId, + text: rule, + }; + } + return { time, domain, @@ -88,8 +99,10 @@ export const normalizeLogs = (logs) => logs.map((log) => { reason, client, client_proto, + /* TODO 'filterId' and 'rule' are deprecated, will be removed in 0.106 */ filterId, rule, + rules: newRules, status, service_name, originalAnswer: original_answer, @@ -726,6 +739,75 @@ export const getFilterName = ( return resolveFilterName(filter); }; +/** + * @param {array} rules + * @param {array} filters + * @param {array} whitelistFilters + * @returns {string[]} + */ +export const getFilterNames = (rules, filters, whitelistFilters) => rules.map( + ({ filter_list_id }) => getFilterName(filters, whitelistFilters, filter_list_id), +); + +/** + * @param {array} rules + * @returns {string[]} + */ +export const getRuleNames = (rules) => rules.map(({ text }) => text); + +/** + * @param {array} rules + * @param {array} filters + * @param {array} whitelistFilters + * @returns {object} + */ +export const getFilterNameToRulesMap = (rules, filters, whitelistFilters) => rules.reduce( + (acc, { text, filter_list_id }) => { + const filterName = getFilterName(filters, whitelistFilters, filter_list_id); + + acc[filterName] = (acc[filterName] || []).concat(text); + return acc; + }, {}, +); + +/** + * @param {array} rules + * @param {array} filters + * @param {array} whitelistFilters + * @param {object} classes + * @returns {JSXElement} + */ +export const getRulesToFilterList = (rules, filters, whitelistFilters, classes = { + list: 'filteringRules', + rule: 'filteringRules__rule font-monospace', + filter: 'filteringRules__filter', +}) => { + const filterNameToRulesMap = getFilterNameToRulesMap(rules, filters, whitelistFilters); + + return
+ {Object.entries(filterNameToRulesMap).reduce( + (acc, [filterName, rulesArr]) => acc + .concat(rulesArr.map((rule, i) =>
{rule}
)) + .concat(
{filterName}
), + [], + )} +
; +}; + +/** +* @param {array} rules +* @param {array} filters +* @param {array} whitelistFilters +* @returns {string} +*/ +export const getRulesAndFilterNames = (rules, filters, whitelistFilters) => { + const filterNameToRulesMap = getFilterNameToRulesMap(rules, filters, whitelistFilters); + + return Object.entries(filterNameToRulesMap).map( + ([filterName, filterRules]) => filterRules.concat(filterName).join('\n'), + ).join('\n\n'); +}; + /** * @param ip {string} * @param gateway_ip {string} diff --git a/client/src/helpers/renderFormattedClientCell.js b/client/src/helpers/renderFormattedClientCell.js index d677c4ca..f7e59a84 100644 --- a/client/src/helpers/renderFormattedClientCell.js +++ b/client/src/helpers/renderFormattedClientCell.js @@ -31,7 +31,7 @@ const getFormattedWhois = (whois) => { * @param {object} info.whois_info * @param {boolean} [isDetailed] * @param {boolean} [isLogs] - * @returns {JSX.Element} + * @returns {JSXElement} */ export const renderFormattedClientCell = (value, info, isDetailed = false, isLogs = false) => { let whoisContainer = null; From ace7c1c89285bcc0b44e3ea7138b61f31f426c9f Mon Sep 17 00:00:00 2001 From: Andrey Meshkov Date: Mon, 28 Dec 2020 12:28:47 +0300 Subject: [PATCH 023/141] fixed comparison --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index dceda824..69ba6c20 100644 --- a/README.md +++ b/README.md @@ -137,8 +137,7 @@ AdGuard Home provides a lot of features out-of-the-box with no need to install a | Force Safe search on search engines | ✅ | ❌ | | Per-client (device) configuration | ✅ | ✅ | | Access settings (choose who can use AGH DNS) | ✅ | ❌ | -| Written in a memory-safe language | ✅ | ❌ | -| Running without root privileges | ✅ | ❌ | +| Running [without root privileges](https://github.com/AdguardTeam/AdGuardHome/wiki/Getting-Started#running-without-superuser) | ✅ | ❌ | ### How does AdGuard Home compare to traditional ad blockers From 67ad07d69ad3d8805c3d42f836b0759bc0b95a4e Mon Sep 17 00:00:00 2001 From: Artem Baskal Date: Mon, 28 Dec 2020 16:39:15 +0300 Subject: [PATCH 024/141] client: 2451 Remove empty rules label --- client/src/components/Logs/Cells/ResponseCell.js | 9 ++++----- client/src/components/Logs/Cells/index.js | 5 +++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/client/src/components/Logs/Cells/ResponseCell.js b/client/src/components/Logs/Cells/ResponseCell.js index 026dbce1..3a3aeb60 100644 --- a/client/src/components/Logs/Cells/ResponseCell.js +++ b/client/src/components/Logs/Cells/ResponseCell.js @@ -51,18 +51,17 @@ const ResponseCell = ({ })}; }; - const rulesList = getRulesToFilterList(rules, filters, whitelistFilters); - const COMMON_CONTENT = { encryption_status: boldStatusLabel, install_settings_dns: upstream, elapsed: formattedElapsedMs, response_code: status, ...(service_name - ? { service_name: getServiceName(service_name) } - : { } + && { service_name: getServiceName(service_name) } + ), + ...(rules.length > 0 + && { rule_label: getRulesToFilterList(rules, filters, whitelistFilters) } ), - rule_label: rulesList, response_table_header: renderResponses(response), original_response: renderResponses(originalResponse), }; diff --git a/client/src/components/Logs/Cells/index.js b/client/src/components/Logs/Cells/index.js index 8435a617..bc1fc2ed 100644 --- a/client/src/components/Logs/Cells/index.js +++ b/client/src/components/Logs/Cells/index.js @@ -169,8 +169,9 @@ const Row = memo(({ response_details: 'title', install_settings_dns: upstream, elapsed: formattedElapsedMs, - rule_label: rules.length > 0 - && getRulesToFilterList(rules, filters, whitelistFilters), + ...(rules.length > 0 + && { rule_label: getRulesToFilterList(rules, filters, whitelistFilters) } + ), response_table_header: response?.join('\n'), response_code: status, client_details: 'title', From 338209f32bdb876da19e52c366e440ea3c457470 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Mon, 28 Dec 2020 18:41:50 +0300 Subject: [PATCH 025/141] Pull request: all: improve dnsrewrite handling Merge in DNS/adguard-home from 2491-dnsrewrite-log to master Closes #2491. Squashed commit of the following: commit bfe3cb599ed0a921285fb1a6ea27aaefdcc0d093 Merge: 95c5ffe43 15d8f979b Author: Ainar Garipov Date: Mon Dec 28 18:33:32 2020 +0300 Merge branch 'master' into 2491-dnsrewrite-log commit 95c5ffe4360b732556455f24b844dad27047e64b Author: Artem Baskal Date: Mon Dec 28 18:11:01 2020 +0300 Add RewriteRule for client commit b9096c8789009dac1838b542d3409fef54b59aa5 Author: Ainar Garipov Date: Mon Dec 28 17:22:44 2020 +0300 all: imp naming, docs commit 4e00de0d613e4740451e4c8eb5a1de35a70a5896 Author: Ainar Garipov Date: Mon Dec 28 17:16:35 2020 +0300 all: imp naming, add todo commit 67e4045f627a9569f382309705963640dcf3454a Author: Ainar Garipov Date: Mon Dec 28 16:53:00 2020 +0300 all: improve dnsrewrite handling --- client/src/helpers/constants.js | 5 ++++ internal/dnsfilter/dnsfilter.go | 46 +++++++++++++++-------------- internal/dnsfilter/dnsrewrite.go | 6 ++-- internal/dnsfilter/rewrites_test.go | 32 ++++++++++---------- internal/dnsforward/dns.go | 4 +-- internal/dnsforward/filter.go | 8 ++--- internal/home/controlfiltering.go | 2 +- internal/querylog/qlog_test.go | 2 +- internal/querylog/searchcriteria.go | 11 +++++-- openapi/CHANGELOG.md | 2 +- openapi/openapi.yaml | 4 +-- 11 files changed, 67 insertions(+), 55 deletions(-) diff --git a/client/src/helpers/constants.js b/client/src/helpers/constants.js index af10524f..76be76a6 100644 --- a/client/src/helpers/constants.js +++ b/client/src/helpers/constants.js @@ -339,6 +339,7 @@ export const FILTERED_STATUS = { FILTERED_BLOCKED_SERVICE: 'FilteredBlockedService', REWRITE: 'Rewrite', REWRITE_HOSTS: 'RewriteEtcHosts', + REWRITE_RULE: 'RewriteRule', FILTERED_SAFE_SEARCH: 'FilteredSafeSearch', FILTERED_SAFE_BROWSING: 'FilteredSafeBrowsing', FILTERED_PARENTAL: 'FilteredParental', @@ -430,6 +431,10 @@ export const FILTERED_STATUS_TO_META_MAP = { LABEL: RESPONSE_FILTER.REWRITTEN.LABEL, COLOR: QUERY_STATUS_COLORS.BLUE, }, + [FILTERED_STATUS.REWRITE_RULE]: { + LABEL: RESPONSE_FILTER.REWRITTEN.LABEL, + COLOR: QUERY_STATUS_COLORS.BLUE, + }, [FILTERED_STATUS.FILTERED_SAFE_BROWSING]: { LABEL: RESPONSE_FILTER.BLOCKED_THREATS.LABEL, COLOR: QUERY_STATUS_COLORS.YELLOW, diff --git a/internal/dnsfilter/dnsfilter.go b/internal/dnsfilter/dnsfilter.go index 4a1b255e..c5c28aff 100644 --- a/internal/dnsfilter/dnsfilter.go +++ b/internal/dnsfilter/dnsfilter.go @@ -148,17 +148,21 @@ const ( // FilteredBlockedService - the host is blocked by "blocked services" settings FilteredBlockedService - // ReasonRewrite is returned when there was a rewrite by - // a legacy DNS Rewrite rule. - ReasonRewrite + // Rewritten is returned when there was a rewrite by a legacy DNS + // rewrite rule. + Rewritten - // RewriteAutoHosts is returned when there was a rewrite by - // autohosts rules (/etc/hosts and so on). - RewriteAutoHosts + // RewrittenAutoHosts is returned when there was a rewrite by autohosts + // rules (/etc/hosts and so on). + RewrittenAutoHosts - // DNSRewriteRule is returned when a $dnsrewrite filter rule was - // applied. - DNSRewriteRule + // RewrittenRule is returned when a $dnsrewrite filter rule was applied. + // + // TODO(a.garipov): Remove Rewritten and RewrittenAutoHosts by merging + // their functionality into RewrittenRule. + // + // See https://github.com/AdguardTeam/AdGuardHome/issues/2499. + RewrittenRule ) // TODO(a.garipov): Resync with actual code names or replace completely @@ -175,11 +179,9 @@ var reasonNames = []string{ FilteredSafeSearch: "FilteredSafeSearch", FilteredBlockedService: "FilteredBlockedService", - ReasonRewrite: "Rewrite", - - RewriteAutoHosts: "RewriteEtcHosts", - - DNSRewriteRule: "DNSRewriteRule", + Rewritten: "Rewrite", + RewrittenAutoHosts: "RewriteEtcHosts", + RewrittenRule: "RewriteRule", } func (r Reason) String() string { @@ -331,15 +333,15 @@ type Result struct { Rules []*ResultRule `json:",omitempty"` // ReverseHosts is the reverse lookup rewrite result. It is - // empty unless Reason is set to RewriteAutoHosts. + // empty unless Reason is set to RewrittenAutoHosts. ReverseHosts []string `json:",omitempty"` // IPList is the lookup rewrite result. It is empty unless - // Reason is set to RewriteAutoHosts or ReasonRewrite. + // Reason is set to RewrittenAutoHosts or Rewritten. IPList []net.IP `json:",omitempty"` // CanonName is the CNAME value from the lookup rewrite result. - // It is empty unless Reason is set to ReasonRewrite. + // It is empty unless Reason is set to Rewritten or RewrittenRule. CanonName string `json:",omitempty"` // ServiceName is the name of the blocked service. It is empty @@ -379,7 +381,7 @@ func (d *DNSFilter) CheckHost(host string, qtype uint16, setts *RequestFiltering // first - check rewrites, they have the highest priority result = d.processRewrites(host, qtype) - if result.Reason == ReasonRewrite { + if result.Reason == Rewritten { return result, nil } @@ -453,7 +455,7 @@ func (d *DNSFilter) CheckHost(host string, qtype uint16, setts *RequestFiltering func (d *DNSFilter) checkAutoHosts(host string, qtype uint16, result *Result) (matched bool) { ips := d.Config.AutoHosts.Process(host, qtype) if ips != nil { - result.Reason = RewriteAutoHosts + result.Reason = RewrittenAutoHosts result.IPList = ips return true @@ -461,7 +463,7 @@ func (d *DNSFilter) checkAutoHosts(host string, qtype uint16, result *Result) (m revHosts := d.Config.AutoHosts.ProcessReverse(host, qtype) if len(revHosts) != 0 { - result.Reason = RewriteAutoHosts + result.Reason = RewrittenAutoHosts // TODO(a.garipov): Optimize this with a buffer. result.ReverseHosts = make([]string, len(revHosts)) @@ -488,7 +490,7 @@ func (d *DNSFilter) processRewrites(host string, qtype uint16) (res Result) { rr := findRewrites(d.Rewrites, host) if len(rr) != 0 { - res.Reason = ReasonRewrite + res.Reason = Rewritten } cnames := map[string]bool{} @@ -696,7 +698,7 @@ func (d *DNSFilter) matchHost(host string, qtype uint16, setts RequestFilteringS // awkward. if dnsr := dnsres.DNSRewrites(); len(dnsr) > 0 { res = d.processDNSRewrites(dnsr) - if res.Reason == DNSRewriteRule && res.CanonName == host { + if res.Reason == RewrittenRule && res.CanonName == host { // A rewrite of a host to itself. Go on and // try matching other things. } else { diff --git a/internal/dnsfilter/dnsrewrite.go b/internal/dnsfilter/dnsrewrite.go index 66cb5828..15e4e211 100644 --- a/internal/dnsfilter/dnsrewrite.go +++ b/internal/dnsfilter/dnsrewrite.go @@ -39,7 +39,7 @@ func (d *DNSFilter) processDNSRewrites(dnsr []*rules.NetworkRule) (res Result) { }} return Result{ - Reason: DNSRewriteRule, + Reason: RewrittenRule, Rules: rules, CanonName: dr.NewCNAME, } @@ -65,7 +65,7 @@ func (d *DNSFilter) processDNSRewrites(dnsr []*rules.NetworkRule) (res Result) { } return Result{ - Reason: DNSRewriteRule, + Reason: RewrittenRule, Rules: rules, DNSRewriteResult: dnsrr, } @@ -73,7 +73,7 @@ func (d *DNSFilter) processDNSRewrites(dnsr []*rules.NetworkRule) (res Result) { } return Result{ - Reason: DNSRewriteRule, + Reason: RewrittenRule, Rules: rules, DNSRewriteResult: dnsrr, } diff --git a/internal/dnsfilter/rewrites_test.go b/internal/dnsfilter/rewrites_test.go index 4304b6de..3a3284ec 100644 --- a/internal/dnsfilter/rewrites_test.go +++ b/internal/dnsfilter/rewrites_test.go @@ -25,14 +25,14 @@ func TestRewrites(t *testing.T) { assert.Equal(t, NotFilteredNotFound, r.Reason) r = d.processRewrites("www.host.com", dns.TypeA) - assert.Equal(t, ReasonRewrite, r.Reason) + assert.Equal(t, Rewritten, r.Reason) assert.Equal(t, "host.com", r.CanonName) assert.Equal(t, 2, len(r.IPList)) assert.True(t, r.IPList[0].Equal(net.ParseIP("1.2.3.4"))) assert.True(t, r.IPList[1].Equal(net.ParseIP("1.2.3.5"))) r = d.processRewrites("www.host.com", dns.TypeAAAA) - assert.Equal(t, ReasonRewrite, r.Reason) + assert.Equal(t, Rewritten, r.Reason) assert.Equal(t, "host.com", r.CanonName) assert.Equal(t, 1, len(r.IPList)) assert.True(t, r.IPList[0].Equal(net.ParseIP("1:2:3::4"))) @@ -44,11 +44,11 @@ func TestRewrites(t *testing.T) { } d.prepareRewrites() r = d.processRewrites("host.com", dns.TypeA) - assert.Equal(t, ReasonRewrite, r.Reason) + assert.Equal(t, Rewritten, r.Reason) assert.True(t, r.IPList[0].Equal(net.ParseIP("1.2.3.4"))) r = d.processRewrites("www.host.com", dns.TypeA) - assert.Equal(t, ReasonRewrite, r.Reason) + assert.Equal(t, Rewritten, r.Reason) assert.True(t, r.IPList[0].Equal(net.ParseIP("1.2.3.5"))) r = d.processRewrites("www.host2.com", dns.TypeA) @@ -61,7 +61,7 @@ func TestRewrites(t *testing.T) { } d.prepareRewrites() r = d.processRewrites("a.host.com", dns.TypeA) - assert.Equal(t, ReasonRewrite, r.Reason) + assert.Equal(t, Rewritten, r.Reason) assert.True(t, len(r.IPList) == 1) assert.True(t, r.IPList[0].Equal(net.ParseIP("1.2.3.4"))) @@ -72,7 +72,7 @@ func TestRewrites(t *testing.T) { } d.prepareRewrites() r = d.processRewrites("www.host.com", dns.TypeA) - assert.Equal(t, ReasonRewrite, r.Reason) + assert.Equal(t, Rewritten, r.Reason) assert.Equal(t, "host.com", r.CanonName) assert.True(t, r.IPList[0].Equal(net.ParseIP("1.2.3.4"))) @@ -84,7 +84,7 @@ func TestRewrites(t *testing.T) { } d.prepareRewrites() r = d.processRewrites("b.host.com", dns.TypeA) - assert.Equal(t, ReasonRewrite, r.Reason) + assert.Equal(t, Rewritten, r.Reason) assert.Equal(t, "host.com", r.CanonName) assert.True(t, len(r.IPList) == 1) assert.True(t, r.IPList[0].Equal(net.ParseIP("1.2.3.4"))) @@ -97,7 +97,7 @@ func TestRewrites(t *testing.T) { } d.prepareRewrites() r = d.processRewrites("b.host.com", dns.TypeA) - assert.Equal(t, ReasonRewrite, r.Reason) + assert.Equal(t, Rewritten, r.Reason) assert.Equal(t, "x.somehost.com", r.CanonName) assert.True(t, len(r.IPList) == 1) assert.True(t, r.IPList[0].Equal(net.ParseIP("1.2.3.4"))) @@ -115,19 +115,19 @@ func TestRewritesLevels(t *testing.T) { // match exact r := d.processRewrites("host.com", dns.TypeA) - assert.Equal(t, ReasonRewrite, r.Reason) + assert.Equal(t, Rewritten, r.Reason) assert.Equal(t, 1, len(r.IPList)) assert.Equal(t, "1.1.1.1", r.IPList[0].String()) // match L2 r = d.processRewrites("sub.host.com", dns.TypeA) - assert.Equal(t, ReasonRewrite, r.Reason) + assert.Equal(t, Rewritten, r.Reason) assert.Equal(t, 1, len(r.IPList)) assert.Equal(t, "2.2.2.2", r.IPList[0].String()) // match L3 r = d.processRewrites("my.sub.host.com", dns.TypeA) - assert.Equal(t, ReasonRewrite, r.Reason) + assert.Equal(t, Rewritten, r.Reason) assert.Equal(t, 1, len(r.IPList)) assert.Equal(t, "3.3.3.3", r.IPList[0].String()) } @@ -143,7 +143,7 @@ func TestRewritesExceptionCNAME(t *testing.T) { // match sub-domain r := d.processRewrites("my.host.com", dns.TypeA) - assert.Equal(t, ReasonRewrite, r.Reason) + assert.Equal(t, Rewritten, r.Reason) assert.Equal(t, 1, len(r.IPList)) assert.Equal(t, "2.2.2.2", r.IPList[0].String()) @@ -163,7 +163,7 @@ func TestRewritesExceptionWC(t *testing.T) { // match sub-domain r := d.processRewrites("my.host.com", dns.TypeA) - assert.Equal(t, ReasonRewrite, r.Reason) + assert.Equal(t, Rewritten, r.Reason) assert.Equal(t, 1, len(r.IPList)) assert.Equal(t, "2.2.2.2", r.IPList[0].String()) @@ -186,7 +186,7 @@ func TestRewritesExceptionIP(t *testing.T) { // match domain r := d.processRewrites("host.com", dns.TypeA) - assert.Equal(t, ReasonRewrite, r.Reason) + assert.Equal(t, Rewritten, r.Reason) assert.Equal(t, 1, len(r.IPList)) assert.Equal(t, "1.2.3.4", r.IPList[0].String()) @@ -200,7 +200,7 @@ func TestRewritesExceptionIP(t *testing.T) { // match domain r = d.processRewrites("host2.com", dns.TypeAAAA) - assert.Equal(t, ReasonRewrite, r.Reason) + assert.Equal(t, Rewritten, r.Reason) assert.Equal(t, 1, len(r.IPList)) assert.Equal(t, "::1", r.IPList[0].String()) @@ -210,6 +210,6 @@ func TestRewritesExceptionIP(t *testing.T) { // match domain r = d.processRewrites("host3.com", dns.TypeAAAA) - assert.Equal(t, ReasonRewrite, r.Reason) + assert.Equal(t, Rewritten, r.Reason) assert.Equal(t, 0, len(r.IPList)) } diff --git a/internal/dnsforward/dns.go b/internal/dnsforward/dns.go index 0e5eb51f..c6c5a6b6 100644 --- a/internal/dnsforward/dns.go +++ b/internal/dnsforward/dns.go @@ -366,8 +366,8 @@ func processFilteringAfterResponse(ctx *dnsContext) int { var err error switch res.Reason { - case dnsfilter.ReasonRewrite, - dnsfilter.DNSRewriteRule: + case dnsfilter.Rewritten, + dnsfilter.RewrittenRule: if len(ctx.origQuestion.Name) == 0 { // origQuestion is set in case we get only CNAME without IP from rewrites table diff --git a/internal/dnsforward/filter.go b/internal/dnsforward/filter.go index c6bfb160..80cf26dd 100644 --- a/internal/dnsforward/filter.go +++ b/internal/dnsforward/filter.go @@ -55,7 +55,7 @@ func (s *Server) filterDNSRequest(ctx *dnsContext) (*dnsfilter.Result, error) { } else if res.IsFiltered { log.Tracef("Host %s is filtered, reason - %q, matched rule: %q", host, res.Reason, res.Rules[0].Text) d.Res = s.genDNSFilterMessage(d, &res) - } else if res.Reason.In(dnsfilter.ReasonRewrite, dnsfilter.DNSRewriteRule) && + } else if res.Reason.In(dnsfilter.Rewritten, dnsfilter.RewrittenRule) && res.CanonName != "" && len(res.IPList) == 0 { // Resolve the new canonical name, not the original host @@ -63,7 +63,7 @@ func (s *Server) filterDNSRequest(ctx *dnsContext) (*dnsfilter.Result, error) { // processFilteringAfterResponse. ctx.origQuestion = d.Req.Question[0] d.Req.Question[0].Name = dns.Fqdn(res.CanonName) - } else if res.Reason == dnsfilter.RewriteAutoHosts && len(res.ReverseHosts) != 0 { + } else if res.Reason == dnsfilter.RewrittenAutoHosts && len(res.ReverseHosts) != 0 { resp := s.makeResponse(req) for _, h := range res.ReverseHosts { hdr := dns.RR_Header{ @@ -82,7 +82,7 @@ func (s *Server) filterDNSRequest(ctx *dnsContext) (*dnsfilter.Result, error) { } d.Res = resp - } else if res.Reason == dnsfilter.ReasonRewrite || res.Reason == dnsfilter.RewriteAutoHosts { + } else if res.Reason == dnsfilter.Rewritten || res.Reason == dnsfilter.RewrittenAutoHosts { resp := s.makeResponse(req) name := host @@ -104,7 +104,7 @@ func (s *Server) filterDNSRequest(ctx *dnsContext) (*dnsfilter.Result, error) { } d.Res = resp - } else if res.Reason == dnsfilter.DNSRewriteRule { + } else if res.Reason == dnsfilter.RewrittenRule { err = s.filterDNSRewrite(req, res, d) if err != nil { return nil, err diff --git a/internal/home/controlfiltering.go b/internal/home/controlfiltering.go index 1d0172e8..65e023b2 100644 --- a/internal/home/controlfiltering.go +++ b/internal/home/controlfiltering.go @@ -369,7 +369,7 @@ type checkHostResp struct { // for FilteredBlockedService: SvcName string `json:"service_name"` - // for ReasonRewrite: + // for Rewritten: CanonName string `json:"cname"` // CNAME value IPList []net.IP `json:"ip_addrs"` // list of IP addresses } diff --git a/internal/querylog/qlog_test.go b/internal/querylog/qlog_test.go index fd37a1de..dfd4e6ce 100644 --- a/internal/querylog/qlog_test.go +++ b/internal/querylog/qlog_test.go @@ -236,7 +236,7 @@ func addEntry(l *queryLog, host, answerStr, client string) { a.Answer = append(a.Answer, answer) res := dnsfilter.Result{ IsFiltered: true, - Reason: dnsfilter.ReasonRewrite, + Reason: dnsfilter.Rewritten, ServiceName: "SomeService", Rules: []*dnsfilter.ResultRule{{ FilterListID: 1, diff --git a/internal/querylog/searchcriteria.go b/internal/querylog/searchcriteria.go index b98e0838..f6b0ee98 100644 --- a/internal/querylog/searchcriteria.go +++ b/internal/querylog/searchcriteria.go @@ -116,8 +116,9 @@ func (c *searchCriteria) ctFilteringStatusCase(res dnsfilter.Result) bool { return res.IsFiltered || res.Reason.In( dnsfilter.NotFilteredAllowList, - dnsfilter.ReasonRewrite, - dnsfilter.RewriteAutoHosts, + dnsfilter.Rewritten, + dnsfilter.RewrittenAutoHosts, + dnsfilter.RewrittenRule, ) case filteringStatusBlocked: @@ -137,7 +138,11 @@ func (c *searchCriteria) ctFilteringStatusCase(res dnsfilter.Result) bool { return res.Reason == dnsfilter.NotFilteredAllowList case filteringStatusRewritten: - return res.Reason.In(dnsfilter.ReasonRewrite, dnsfilter.RewriteAutoHosts) + return res.Reason.In( + dnsfilter.Rewritten, + dnsfilter.RewrittenAutoHosts, + dnsfilter.RewrittenRule, + ) case filteringStatusSafeSearch: return res.IsFiltered && res.Reason == dnsfilter.FilteredSafeSearch diff --git a/openapi/CHANGELOG.md b/openapi/CHANGELOG.md index 076d9896..133664b1 100644 --- a/openapi/CHANGELOG.md +++ b/openapi/CHANGELOG.md @@ -6,7 +6,7 @@ ### New `"reason"` in `GET /filtering/check_host` and `GET /querylog` -* The new `DNSRewriteRule` reason is added to `GET /filtering/check_host` and +* The new `RewriteRule` reason is added to `GET /filtering/check_host` and `GET /querylog`. * Also, the reason which was incorrectly documented as `"ReasonRewrite"` is now diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index 61db836e..8b3ef126 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -1259,7 +1259,7 @@ - 'FilteredBlockedService' - 'Rewrite' - 'RewriteEtcHosts' - - 'DNSRewriteRule' + - 'RewriteRule' 'filter_id': 'deprecated': true 'description': > @@ -1663,7 +1663,7 @@ - 'FilteredBlockedService' - 'Rewrite' - 'RewriteEtcHosts' - - 'DNSRewriteRule' + - 'RewriteRule' 'service_name': 'type': 'string' 'description': 'Set if reason=FilteredBlockedService' From 93ffed7809c34ad74d406d6820ebb4712d331369 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Mon, 28 Dec 2020 18:49:36 +0300 Subject: [PATCH 026/141] Pull request: dnsfilter: improve cname rewrite validation Merge in DNS/adguard-home from 2492-dnsrw-host to master Closes #2492. Squashed commit of the following: commit 7128ed15de86c926ea4cff9fd8d3d3821657254f Merge: e678c64e6 338209f32 Author: Ainar Garipov Date: Mon Dec 28 18:42:25 2020 +0300 Merge branch 'master' into 2492-dnsrw-host commit e678c64e656ccf69c6818e9165ff1451f7c8fde6 Author: Ainar Garipov Date: Mon Dec 28 18:04:52 2020 +0300 dnsfilter: improve cname rewrite validation --- go.mod | 2 +- go.sum | 4 +- internal/dnsfilter/dnsrewrite_test.go | 67 ++++++++++++++------------- 3 files changed, 37 insertions(+), 36 deletions(-) diff --git a/go.mod b/go.mod index 61c563e5..f22141c2 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.14 require ( github.com/AdguardTeam/dnsproxy v0.33.7 github.com/AdguardTeam/golibs v0.4.4 - github.com/AdguardTeam/urlfilter v0.14.1 + github.com/AdguardTeam/urlfilter v0.14.2 github.com/NYTimes/gziphandler v1.1.1 github.com/ameshkov/dnscrypt/v2 v2.0.1 github.com/fsnotify/fsnotify v1.4.9 diff --git a/go.sum b/go.sum index d8d6ef4d..1c488f95 100644 --- a/go.sum +++ b/go.sum @@ -26,8 +26,8 @@ github.com/AdguardTeam/golibs v0.4.2/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKU github.com/AdguardTeam/golibs v0.4.4 h1:cM9UySQiYFW79zo5XRwnaIWVzfW4eNXmZktMrWbthpw= github.com/AdguardTeam/golibs v0.4.4/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4= github.com/AdguardTeam/gomitmproxy v0.2.0/go.mod h1:Qdv0Mktnzer5zpdpi5rAwixNJzW2FN91LjKJCkVbYGU= -github.com/AdguardTeam/urlfilter v0.14.1 h1:imYls0fit9ojA6pP1hWFUEIjyoXbDF85ZM+G67bI48c= -github.com/AdguardTeam/urlfilter v0.14.1/go.mod h1:klx4JbOfc4EaNb5lWLqOwfg+pVcyRukmoJRvO55lL5U= +github.com/AdguardTeam/urlfilter v0.14.2 h1:k26vEYz0mT/liDGZ0JGIBLYLMHaisIGX1UR0qaVnO4k= +github.com/AdguardTeam/urlfilter v0.14.2/go.mod h1:klx4JbOfc4EaNb5lWLqOwfg+pVcyRukmoJRvO55lL5U= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= diff --git a/internal/dnsfilter/dnsrewrite_test.go b/internal/dnsfilter/dnsrewrite_test.go index 4918ccc0..dadef406 100644 --- a/internal/dnsfilter/dnsrewrite_test.go +++ b/internal/dnsfilter/dnsrewrite_test.go @@ -11,39 +11,40 @@ import ( func TestDNSFilter_CheckHostRules_dnsrewrite(t *testing.T) { const text = ` -|cname^$dnsrewrite=new_cname +|cname^$dnsrewrite=new-cname -|a_record^$dnsrewrite=127.0.0.1 +|a-record^$dnsrewrite=127.0.0.1 -|aaaa_record^$dnsrewrite=::1 +|aaaa-record^$dnsrewrite=::1 -|txt_record^$dnsrewrite=NOERROR;TXT;hello_world +|txt-record^$dnsrewrite=NOERROR;TXT;hello-world |refused^$dnsrewrite=REFUSED -|a_records^$dnsrewrite=127.0.0.1 -|a_records^$dnsrewrite=127.0.0.2 +|a-records^$dnsrewrite=127.0.0.1 +|a-records^$dnsrewrite=127.0.0.2 -|aaaa_records^$dnsrewrite=::1 -|aaaa_records^$dnsrewrite=::2 +|aaaa-records^$dnsrewrite=::1 +|aaaa-records^$dnsrewrite=::2 -|disable_one^$dnsrewrite=127.0.0.1 -|disable_one^$dnsrewrite=127.0.0.2 -@@||disable_one^$dnsrewrite=127.0.0.1 +|disable-one^$dnsrewrite=127.0.0.1 +|disable-one^$dnsrewrite=127.0.0.2 +@@||disable-one^$dnsrewrite=127.0.0.1 -|disable_cname^$dnsrewrite=127.0.0.1 -|disable_cname^$dnsrewrite=new_cname -@@||disable_cname^$dnsrewrite=new_cname +|disable-cname^$dnsrewrite=127.0.0.1 +|disable-cname^$dnsrewrite=new-cname +@@||disable-cname^$dnsrewrite=new-cname -|disable_cname_many^$dnsrewrite=127.0.0.1 -|disable_cname_many^$dnsrewrite=new_cname_1 -|disable_cname_many^$dnsrewrite=new_cname_2 -@@||disable_cname_many^$dnsrewrite=new_cname_1 +|disable-cname-many^$dnsrewrite=127.0.0.1 +|disable-cname-many^$dnsrewrite=new-cname-1 +|disable-cname-many^$dnsrewrite=new-cname-2 +@@||disable-cname-many^$dnsrewrite=new-cname-1 -|disable_all^$dnsrewrite=127.0.0.1 -|disable_all^$dnsrewrite=127.0.0.2 -@@||disable_all^$dnsrewrite +|disable-all^$dnsrewrite=127.0.0.1 +|disable-all^$dnsrewrite=127.0.0.2 +@@||disable-all^$dnsrewrite ` + f := NewForTest(nil, []Filter{{ID: 0, Data: []byte(text)}}) setts := &RequestFilteringSettings{ FilteringEnabled: true, @@ -60,10 +61,10 @@ func TestDNSFilter_CheckHostRules_dnsrewrite(t *testing.T) { res, err := f.CheckHostRules(host, dtyp, setts) assert.Nil(t, err) - assert.Equal(t, "new_cname", res.CanonName) + assert.Equal(t, "new-cname", res.CanonName) }) - t.Run("a_record", func(t *testing.T) { + t.Run("a-record", func(t *testing.T) { dtyp := dns.TypeA host := path.Base(t.Name()) @@ -78,7 +79,7 @@ func TestDNSFilter_CheckHostRules_dnsrewrite(t *testing.T) { } }) - t.Run("aaaa_record", func(t *testing.T) { + t.Run("aaaa-record", func(t *testing.T) { dtyp := dns.TypeAAAA host := path.Base(t.Name()) @@ -93,7 +94,7 @@ func TestDNSFilter_CheckHostRules_dnsrewrite(t *testing.T) { } }) - t.Run("txt_record", func(t *testing.T) { + t.Run("txt-record", func(t *testing.T) { dtyp := dns.TypeTXT host := path.Base(t.Name()) res, err := f.CheckHostRules(host, dtyp, setts) @@ -102,7 +103,7 @@ func TestDNSFilter_CheckHostRules_dnsrewrite(t *testing.T) { if dnsrr := res.DNSRewriteResult; assert.NotNil(t, dnsrr) { assert.Equal(t, dns.RcodeSuccess, dnsrr.RCode) if strVals := dnsrr.Response[dtyp]; assert.Len(t, strVals, 1) { - assert.Equal(t, "hello_world", strVals[0]) + assert.Equal(t, "hello-world", strVals[0]) } } }) @@ -117,7 +118,7 @@ func TestDNSFilter_CheckHostRules_dnsrewrite(t *testing.T) { } }) - t.Run("a_records", func(t *testing.T) { + t.Run("a-records", func(t *testing.T) { dtyp := dns.TypeA host := path.Base(t.Name()) @@ -133,7 +134,7 @@ func TestDNSFilter_CheckHostRules_dnsrewrite(t *testing.T) { } }) - t.Run("aaaa_records", func(t *testing.T) { + t.Run("aaaa-records", func(t *testing.T) { dtyp := dns.TypeAAAA host := path.Base(t.Name()) @@ -149,7 +150,7 @@ func TestDNSFilter_CheckHostRules_dnsrewrite(t *testing.T) { } }) - t.Run("disable_one", func(t *testing.T) { + t.Run("disable-one", func(t *testing.T) { dtyp := dns.TypeA host := path.Base(t.Name()) @@ -164,7 +165,7 @@ func TestDNSFilter_CheckHostRules_dnsrewrite(t *testing.T) { } }) - t.Run("disable_cname", func(t *testing.T) { + t.Run("disable-cname", func(t *testing.T) { dtyp := dns.TypeA host := path.Base(t.Name()) @@ -180,17 +181,17 @@ func TestDNSFilter_CheckHostRules_dnsrewrite(t *testing.T) { } }) - t.Run("disable_cname_many", func(t *testing.T) { + t.Run("disable-cname-many", func(t *testing.T) { dtyp := dns.TypeA host := path.Base(t.Name()) res, err := f.CheckHostRules(host, dtyp, setts) assert.Nil(t, err) - assert.Equal(t, "new_cname_2", res.CanonName) + assert.Equal(t, "new-cname-2", res.CanonName) assert.Nil(t, res.DNSRewriteResult) }) - t.Run("disable_all", func(t *testing.T) { + t.Run("disable-all", func(t *testing.T) { dtyp := dns.TypeA host := path.Base(t.Name()) From 8a1d86aa7d8e9dd73f5d3f04c010adbd537ae817 Mon Sep 17 00:00:00 2001 From: Eugene Burkov Date: Mon, 28 Dec 2020 19:07:29 +0300 Subject: [PATCH 027/141] Pull request: data race Merge in DNS/adguard-home from 2489-data-race to master Closes #2489. Squashed commit of the following: commit 7745b79f0489970f3ba1bb11bb757b998fa6369c Merge: d070cfd53 93ffed780 Author: Eugene Burkov Date: Mon Dec 28 19:00:07 2020 +0300 Merge branch 'master' into 2489-data-race commit d070cfd53e72b609f305cd8d79d747fbf47dc5f4 Author: Eugene Burkov Date: Fri Dec 25 20:31:21 2020 +0300 util: fix ignoring write events bug commit 725850bdbd96eaf65fb3228fcaeba51a3ec95905 Author: Eugene Burkov Date: Fri Dec 25 17:38:29 2020 +0300 util: simpl hosts upd algo --- internal/util/autohosts.go | 55 +++++++++++++++++--------------------- 1 file changed, 25 insertions(+), 30 deletions(-) diff --git a/internal/util/autohosts.go b/internal/util/autohosts.go index ec2f3579..1bfdb44b 100644 --- a/internal/util/autohosts.go +++ b/internal/util/autohosts.go @@ -29,10 +29,12 @@ type AutoHosts struct { // TODO(a.garipov): Make better use of newtypes. Perhaps a custom map. tableReverse map[string][]string - hostsFn string // path to the main hosts-file - hostsDirs []string // paths to OS-specific directories with hosts-files - watcher *fsnotify.Watcher // file and directory watcher object - updateChan chan bool // signal for 'updateLoop' goroutine + hostsFn string // path to the main hosts-file + hostsDirs []string // paths to OS-specific directories with hosts-files + watcher *fsnotify.Watcher // file and directory watcher object + + // onlyWritesChan used to contain only writing events from watcher. + onlyWritesChan chan fsnotify.Event onChanged onChangedT // notification to other modules } @@ -54,7 +56,7 @@ func (a *AutoHosts) notify() { // hostsFn: Override default name for the hosts-file (optional) func (a *AutoHosts) Init(hostsFn string) { a.table = make(map[string][]net.IP) - a.updateChan = make(chan bool, 2) + a.onlyWritesChan = make(chan fsnotify.Event, 2) a.hostsFn = "/etc/hosts" if runtime.GOOS == "windows" { @@ -82,8 +84,7 @@ func (a *AutoHosts) Init(hostsFn string) { func (a *AutoHosts) Start() { log.Debug("Start AutoHosts module") - go a.updateLoop() - a.updateChan <- true + a.updateHosts() if a.watcher != nil { go a.watcherLoop() @@ -104,11 +105,10 @@ func (a *AutoHosts) Start() { // Close - close module func (a *AutoHosts) Close() { - a.updateChan <- false - close(a.updateChan) if a.watcher != nil { _ = a.watcher.Close() } + close(a.onlyWritesChan) } // Process returns the list of IP addresses for the hostname or nil if nothing @@ -273,20 +273,32 @@ func (a *AutoHosts) load(table map[string][]net.IP, tableRev map[string][]string } } +// onlyWrites is a filter for (*fsnotify.Watcher).Events. +func (a *AutoHosts) onlyWrites() { + for event := range a.watcher.Events { + if event.Op&fsnotify.Write == fsnotify.Write { + a.onlyWritesChan <- event + } + } +} + // Receive notifications from fsnotify package func (a *AutoHosts) watcherLoop() { + go a.onlyWrites() for { select { - case event, ok := <-a.watcher.Events: + case event, ok := <-a.onlyWritesChan: if !ok { return } + // Assume that we sometimes have the same event occurred + // several times. repeat := true for repeat { select { - case <-a.watcher.Events: - // Skip this duplicating event + case _, ok = <-a.onlyWritesChan: + repeat = ok default: repeat = false } @@ -294,12 +306,7 @@ func (a *AutoHosts) watcherLoop() { if event.Op&fsnotify.Write == fsnotify.Write { log.Debug("AutoHosts: modified: %s", event.Name) - select { - case a.updateChan <- true: - // sent a signal to 'updateLoop' goroutine - default: - // queue is full - } + a.updateHosts() } case err, ok := <-a.watcher.Errors: @@ -311,18 +318,6 @@ func (a *AutoHosts) watcherLoop() { } } -// updateLoop reads static hosts from system files. -func (a *AutoHosts) updateLoop() { - for ok := range a.updateChan { - if !ok { - log.Debug("Finished AutoHosts update loop") - return - } - - a.updateHosts() - } -} - // updateHosts - loads system hosts func (a *AutoHosts) updateHosts() { table := make(map[string][]net.IP) From 483f02c92a5cabe0e7086a1cf1985096023826ea Mon Sep 17 00:00:00 2001 From: Eugene Burkov Date: Mon, 28 Dec 2020 19:26:37 +0300 Subject: [PATCH 028/141] Pull request: doc the Question[0] inspection Merge in DNS/adguard-home from 2465-question-0 to master Closes #2465. Squashed commit of the following: commit 0dbcaf7eea4f582cedc31f37d1b20162fe1c38df Merge: 0d7c22cd0 8a1d86aa7 Author: Eugene Burkov Date: Mon Dec 28 19:09:25 2020 +0300 Merge branch 'master' into 2465-question-0 commit 0d7c22cd0eb930bd301a4bb7b8f23e9fd78a2ccd Author: Eugene Burkov Date: Wed Dec 23 14:17:13 2020 +0300 dnsforward: doc the Question[0] inspection --- internal/dnsforward/dns.go | 6 ++++++ internal/dnsforward/msg.go | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/internal/dnsforward/dns.go b/internal/dnsforward/dns.go index c6c5a6b6..4a47cdc1 100644 --- a/internal/dnsforward/dns.go +++ b/internal/dnsforward/dns.go @@ -41,6 +41,12 @@ func (s *Server) handleDNSRequest(_ *proxy.Proxy, d *proxy.DNSContext) error { ctx.startTime = time.Now() type modProcessFunc func(ctx *dnsContext) int + + // Since (*dnsforward.Server).handleDNSRequest(...) is used as + // proxy.(Config).RequestHandler, there is no need for additional index + // out of range checking in any of the following functions, because the + // (*proxy.Proxy).handleDNSRequest method performs it before calling the + // appropriate handler. mods := []modProcessFunc{ processInitial, processInternalHosts, diff --git a/internal/dnsforward/msg.go b/internal/dnsforward/msg.go index 28a8ac3a..71497a6c 100644 --- a/internal/dnsforward/msg.go +++ b/internal/dnsforward/msg.go @@ -11,7 +11,8 @@ import ( "github.com/miekg/dns" ) -// Create a DNS response by DNS request and set necessary flags +// makeResponse creates a DNS response by req and sets necessary flags. It also +// guarantees that req.Question will be not empty. func (s *Server) makeResponse(req *dns.Msg) (resp *dns.Msg) { resp = &dns.Msg{ MsgHdr: dns.MsgHdr{ From fa96d49dcb690131d4a559ab16b27453c147a4c9 Mon Sep 17 00:00:00 2001 From: jvoisin Date: Thu, 24 Dec 2020 17:59:11 +0100 Subject: [PATCH 029/141] Fix the remaining stylint issues --- client/src/components/Logs/Cells/IconTooltip.css | 4 ++-- client/src/components/Logs/Logs.css | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/client/src/components/Logs/Cells/IconTooltip.css b/client/src/components/Logs/Cells/IconTooltip.css index 6260c22f..811c7623 100644 --- a/client/src/components/Logs/Cells/IconTooltip.css +++ b/client/src/components/Logs/Cells/IconTooltip.css @@ -35,7 +35,7 @@ } .grid--title { - font-weight: bold; + font-weight: 600; } .grid--title:not(:first-child) { @@ -65,7 +65,7 @@ } .grid .key-colon, .grid .title--border { - font-weight: bold; + font-weight: 600; } } diff --git a/client/src/components/Logs/Logs.css b/client/src/components/Logs/Logs.css index a48a8d15..4a642cb5 100644 --- a/client/src/components/Logs/Logs.css +++ b/client/src/components/Logs/Logs.css @@ -37,7 +37,7 @@ } .logs__text--bold { - font-weight: bold; + font-weight: 600; } .logs__time { @@ -164,12 +164,13 @@ } .logs__refresh { - --size: 2.5rem; position: relative; top: 3px; display: inline-flex; align-items: center; justify-content: center; + + --size: 2.5rem; width: var(--size); height: var(--size); padding: 0; @@ -357,7 +358,7 @@ color: var(--gray-4d); background-color: var(--white80); pointer-events: none; - font-weight: bold; + font-weight: 600; text-align: center; padding-top: 21rem; display: block; From 5e20ac7ed5de861ea5c945d1775f75312d62b69f Mon Sep 17 00:00:00 2001 From: Eugene Burkov Date: Tue, 29 Dec 2020 19:53:56 +0300 Subject: [PATCH 030/141] Pull request: beta client squashed Merge in DNS/adguard-home from beta-client-2 to master Squashed commit of the following: commit b2640cc49a6c5484d730b534dcf5a8013d7fa478 Merge: 659def862 aef4659e9 Author: Eugene Burkov Date: Tue Dec 29 19:23:09 2020 +0300 Merge branch 'master' into beta-client-2 commit 659def8626467949c35b7a6a0c99ffafb07b4385 Author: Eugene Burkov Date: Tue Dec 29 17:25:14 2020 +0300 all: upgrade github actions node version commit b4b8cf8dd75672e9155da5d111ac66e8f5ba1535 Author: Vladislav Abdulmyanov Date: Tue Dec 29 16:57:14 2020 +0300 all: beta client squashed --- .dockerignore | 2 + .github/workflows/build.yml | 2 +- .gitignore | 3 + Dockerfile | 2 + Makefile | 5 + client2/.eslintignore | 6 + client2/.eslintrc | 5 + client2/declaration.d.ts | 18 + client2/package.json | 92 + client2/postcss.config.js | 17 + .../assets/apple-touch-icon-180x180.png | Bin 0 -> 2137 bytes client2/public/assets/favicon.png | Bin 0 -> 1296 bytes client2/public/assets/safari-pinned-tab.svg | 8 + client2/public/index.html | 23 + client2/public/install.html | 22 + client2/public/login.html | 22 + client2/scripts/consts.ts | 12 + client2/scripts/generator/index.ts | 18 + client2/scripts/generator/src/generateApis.ts | 317 + .../scripts/generator/src/generateEntities.ts | 519 + client2/scripts/generator/src/utils.ts | 74 + client2/scripts/helpers/checkTranslations.ts | 226 + client2/scripts/lint/common.js | 79 + client2/scripts/lint/dev.js | 10 + client2/scripts/lint/prod.js | 5 + client2/scripts/webpack/helpers.js | 40 + .../scripts/webpack/webpack.config.base.js | 74 + client2/scripts/webpack/webpack.config.dev.js | 113 + .../scripts/webpack/webpack.config.prod.js | 91 + client2/src/App.tsx | 18 + client2/src/Install.tsx | 18 + client2/src/assets/img/background_min.png | Bin 0 -> 7187 bytes client2/src/components/App.tsx | 16 + .../components/Install/Install.module.pcss | 14 + client2/src/components/Install/Install.tsx | 122 + .../AdminInterface/AdminInterface.module.pcss | 17 + .../AdminInterface/AdminInterface.tsx | 145 + .../components/AdminInterface/index.ts | 1 + .../Install/components/Auth/Auth.tsx | 55 + .../Install/components/Auth/index.ts | 1 + .../ConfigureDevices.module.pcss | 4 + .../ConfigureDevices/ConfigureDevices.tsx | 128 + .../components/ConfigureDevices/index.ts | 1 + .../DnsServer/DnsServer.module.pcss | 12 + .../components/DnsServer/DnsServer.tsx | 145 + .../Install/components/DnsServer/index.ts | 1 + .../StepButtons/StepButtons.module.pcss | 8 + .../components/StepButtons/StepButtons.tsx | 45 + .../Install/components/StepButtons/index.ts | 1 + .../components/Stepper/Stepper.module.pcss | 3 + .../Install/components/Stepper/Stepper.tsx | 24 + .../Install/components/Stepper/index.ts | 1 + .../components/Welcome/Welcome.module.pcss | 15 + .../Install/components/Welcome/Welcome.tsx | 40 + .../Install/components/Welcome/index.ts | 1 + client2/src/components/Install/index.ts | 1 + .../common/controls/Input/Input.tsx | 146 + .../components/common/controls/Input/index.ts | 1 + .../common/controls/Radio/Radio.module.pcss | 16 + .../common/controls/Radio/Radio.tsx | 57 + .../components/common/controls/Radio/index.ts | 1 + .../common/controls/Switch/Switch.tsx | 3 + .../common/controls/Switch/index.ts | 1 + .../src/components/common/controls/index.ts | 3 + .../components/common/formating/danger.tsx | 12 + .../src/components/common/formating/index.ts | 2 + client2/src/components/common/formating/p.tsx | 11 + client2/src/components/common/index.ts | 0 .../common/ui/Icon/Icon.module.pcss | 7 + .../src/components/common/ui/Icon/Icon.tsx | 25 + .../src/components/common/ui/Icon/index.ts | 1 + .../common/ui/Notifications/index.ts | 1 + .../common/ui/Notifications/notifications.tsx | 42 + client2/src/components/common/ui/index.ts | 2 + client2/src/lib/ant/Radio.pcss | 15 + client2/src/lib/ant/Step.pcss | 65 + client2/src/lib/ant/ant-overrides.less | 8 + client2/src/lib/ant/ant.less | 2 + client2/src/lib/ant/index.ts | 5 + client2/src/lib/apis/blockedServices.ts | 31 + client2/src/lib/apis/clients.ts | 103 + client2/src/lib/apis/dhcp.ts | 110 + client2/src/lib/apis/filtering.ts | 167 + client2/src/lib/apis/global.ts | 160 + client2/src/lib/apis/i18n.ts | 32 + client2/src/lib/apis/install.ts | 123 + client2/src/lib/apis/log.ts | 72 + client2/src/lib/apis/mobileconfig.ts | 35 + client2/src/lib/apis/parental.ts | 44 + client2/src/lib/apis/rewrite.ts | 61 + client2/src/lib/apis/safebrowsing.ts | 39 + client2/src/lib/apis/safesearch.ts | 39 + client2/src/lib/apis/stats.ts | 64 + client2/src/lib/apis/tls.ts | 61 + client2/src/lib/consts/common.ts | 1 + client2/src/lib/consts/forms.ts | 1 + client2/src/lib/consts/install.ts | 7 + client2/src/lib/entities/AddUrlRequest.ts | 78 + client2/src/lib/entities/AddressInfo.ts | 67 + client2/src/lib/entities/AddressInfoBeta.ts | 71 + client2/src/lib/entities/AddressesInfo.ts | 81 + client2/src/lib/entities/AddressesInfoBeta.ts | 80 + .../src/lib/entities/BlockedServicesArray.ts | 31 + .../src/lib/entities/CheckConfigRequest.ts | 75 + .../lib/entities/CheckConfigRequestBeta.ts | 75 + .../lib/entities/CheckConfigRequestInfo.ts | 81 + .../entities/CheckConfigRequestInfoBeta.ts | 85 + .../src/lib/entities/CheckConfigResponse.ts | 64 + .../lib/entities/CheckConfigResponseInfo.ts | 59 + .../lib/entities/CheckConfigStaticIpInfo.ts | 79 + .../entities/CheckConfigStaticIpInfoStatic.ts | 7 + client2/src/lib/entities/Client.ts | 176 + client2/src/lib/entities/ClientAuto.ts | 85 + client2/src/lib/entities/ClientDelete.ts | 45 + .../src/lib/entities/ClientFindSubEntry.ts | 222 + client2/src/lib/entities/ClientUpdate.ts | 61 + client2/src/lib/entities/Clients.ts | 62 + client2/src/lib/entities/ClientsArray.ts | 31 + client2/src/lib/entities/ClientsAutoArray.ts | 31 + client2/src/lib/entities/ClientsFindEntry.ts | 31 + .../src/lib/entities/ClientsFindResponse.ts | 31 + client2/src/lib/entities/DNSConfig.ts | 250 + client2/src/lib/entities/DhcpConfig.ts | 90 + client2/src/lib/entities/DhcpConfigV4.ts | 117 + client2/src/lib/entities/DhcpConfigV6.ts | 59 + client2/src/lib/entities/DhcpLease.ts | 103 + client2/src/lib/entities/DhcpSearchResult.ts | 62 + .../entities/DhcpSearchResultOtherServer.ts | 64 + .../lib/entities/DhcpSearchResultStaticIP.ts | 64 + client2/src/lib/entities/DhcpSearchV4.ts | 62 + client2/src/lib/entities/DhcpSearchV6.ts | 47 + client2/src/lib/entities/DhcpStaticLease.ts | 85 + client2/src/lib/entities/DhcpStatus.ts | 116 + client2/src/lib/entities/DnsAnswer.ts | 85 + client2/src/lib/entities/DnsQuestion.ts | 85 + client2/src/lib/entities/Error.ts | 46 + client2/src/lib/entities/Filter.ts | 136 + .../lib/entities/FilterCheckHostResponse.ts | 143 + client2/src/lib/entities/FilterConfig.ts | 59 + .../src/lib/entities/FilterRefreshRequest.ts | 45 + .../src/lib/entities/FilterRefreshResponse.ts | 45 + client2/src/lib/entities/FilterSetUrl.ts | 72 + client2/src/lib/entities/FilterStatus.ts | 89 + client2/src/lib/entities/GetVersionRequest.ts | 46 + .../src/lib/entities/InitialConfiguration.ts | 89 + .../lib/entities/InitialConfigurationBeta.ts | 89 + client2/src/lib/entities/Login.ts | 61 + client2/src/lib/entities/NetInterface.ts | 113 + client2/src/lib/entities/ProfileInfo.ts | 45 + client2/src/lib/entities/QueryLog.ts | 65 + client2/src/lib/entities/QueryLogConfig.ts | 76 + client2/src/lib/entities/QueryLogItem.ts | 277 + client2/src/lib/entities/RemoveUrlRequest.ts | 49 + client2/src/lib/entities/ResultRule.ts | 69 + client2/src/lib/entities/RewriteEntry.ts | 67 + client2/src/lib/entities/RewriteList.ts | 31 + client2/src/lib/entities/ServerStatus.ts | 167 + client2/src/lib/entities/Stats.ts | 257 + client2/src/lib/entities/StatsConfig.ts | 46 + client2/src/lib/entities/TlsConfig.ts | 404 + client2/src/lib/entities/TopArrayEntry.ts | 45 + client2/src/lib/entities/UpstreamsConfig.ts | 69 + .../lib/entities/UpstreamsConfigResponse.ts | 31 + client2/src/lib/entities/VersionInfo.ts | 100 + client2/src/lib/entities/WhoisInfo.ts | 45 + client2/src/lib/helpers/apiErrors.ts | 14 + client2/src/lib/helpers/installHelpers.ts | 17 + client2/src/lib/theme/Form.module.pcss | 78 + client2/src/lib/theme/Icons/Icon.pcss | 3 + client2/src/lib/theme/Icons/index.tsx | 54 + client2/src/lib/theme/Typography.module.pcss | 36 + client2/src/lib/theme/index.ts | 9 + .../src/localization/Translator/Translator.ts | 82 + client2/src/localization/Translator/index.ts | 2 + .../localization/Translator/lib/formatter.ts | 100 + .../src/localization/Translator/lib/nodes.ts | 50 + .../src/localization/Translator/lib/parser.ts | 335 + .../src/localization/Translator/lib/plural.ts | 394 + .../localization/Translator/lib/translator.ts | 9 + .../localization/Translator/lib/validator.ts | 60 + client2/src/localization/index.ts | 6 + client2/src/localization/locales/en.json | 48 + client2/src/localization/locales/index.ts | 43 + client2/src/localization/locales/ru.json | 4 + client2/src/main.pcss | 33 + client2/src/store/index.ts | 1 + client2/src/store/installStore.ts | 19 + client2/src/store/store.ts | 15 + client2/src/store/stores/Install.ts | 50 + client2/src/store/stores/ui.ts | 25 + client2/tsconfig.json | 39 + client2/yarn.lock | 8713 +++++++++++++++++ internal/home/config.go | 24 +- internal/home/control.go | 2 +- internal/home/controlfiltering.go | 2 +- internal/home/controlinstall.go | 203 +- internal/home/home.go | 27 +- internal/home/middlewares.go | 15 + internal/home/web.go | 52 +- openapi/openapi.yaml | 243 +- 200 files changed, 20843 insertions(+), 55 deletions(-) create mode 100644 client2/.eslintignore create mode 100644 client2/.eslintrc create mode 100644 client2/declaration.d.ts create mode 100644 client2/package.json create mode 100644 client2/postcss.config.js create mode 100644 client2/public/assets/apple-touch-icon-180x180.png create mode 100644 client2/public/assets/favicon.png create mode 100644 client2/public/assets/safari-pinned-tab.svg create mode 100644 client2/public/index.html create mode 100644 client2/public/install.html create mode 100644 client2/public/login.html create mode 100644 client2/scripts/consts.ts create mode 100644 client2/scripts/generator/index.ts create mode 100644 client2/scripts/generator/src/generateApis.ts create mode 100644 client2/scripts/generator/src/generateEntities.ts create mode 100644 client2/scripts/generator/src/utils.ts create mode 100644 client2/scripts/helpers/checkTranslations.ts create mode 100644 client2/scripts/lint/common.js create mode 100644 client2/scripts/lint/dev.js create mode 100644 client2/scripts/lint/prod.js create mode 100644 client2/scripts/webpack/helpers.js create mode 100644 client2/scripts/webpack/webpack.config.base.js create mode 100644 client2/scripts/webpack/webpack.config.dev.js create mode 100644 client2/scripts/webpack/webpack.config.prod.js create mode 100644 client2/src/App.tsx create mode 100644 client2/src/Install.tsx create mode 100644 client2/src/assets/img/background_min.png create mode 100644 client2/src/components/App.tsx create mode 100644 client2/src/components/Install/Install.module.pcss create mode 100644 client2/src/components/Install/Install.tsx create mode 100644 client2/src/components/Install/components/AdminInterface/AdminInterface.module.pcss create mode 100644 client2/src/components/Install/components/AdminInterface/AdminInterface.tsx create mode 100644 client2/src/components/Install/components/AdminInterface/index.ts create mode 100644 client2/src/components/Install/components/Auth/Auth.tsx create mode 100644 client2/src/components/Install/components/Auth/index.ts create mode 100644 client2/src/components/Install/components/ConfigureDevices/ConfigureDevices.module.pcss create mode 100644 client2/src/components/Install/components/ConfigureDevices/ConfigureDevices.tsx create mode 100644 client2/src/components/Install/components/ConfigureDevices/index.ts create mode 100644 client2/src/components/Install/components/DnsServer/DnsServer.module.pcss create mode 100644 client2/src/components/Install/components/DnsServer/DnsServer.tsx create mode 100644 client2/src/components/Install/components/DnsServer/index.ts create mode 100644 client2/src/components/Install/components/StepButtons/StepButtons.module.pcss create mode 100644 client2/src/components/Install/components/StepButtons/StepButtons.tsx create mode 100644 client2/src/components/Install/components/StepButtons/index.ts create mode 100644 client2/src/components/Install/components/Stepper/Stepper.module.pcss create mode 100644 client2/src/components/Install/components/Stepper/Stepper.tsx create mode 100644 client2/src/components/Install/components/Stepper/index.ts create mode 100644 client2/src/components/Install/components/Welcome/Welcome.module.pcss create mode 100644 client2/src/components/Install/components/Welcome/Welcome.tsx create mode 100644 client2/src/components/Install/components/Welcome/index.ts create mode 100644 client2/src/components/Install/index.ts create mode 100644 client2/src/components/common/controls/Input/Input.tsx create mode 100644 client2/src/components/common/controls/Input/index.ts create mode 100644 client2/src/components/common/controls/Radio/Radio.module.pcss create mode 100644 client2/src/components/common/controls/Radio/Radio.tsx create mode 100644 client2/src/components/common/controls/Radio/index.ts create mode 100644 client2/src/components/common/controls/Switch/Switch.tsx create mode 100644 client2/src/components/common/controls/Switch/index.ts create mode 100644 client2/src/components/common/controls/index.ts create mode 100644 client2/src/components/common/formating/danger.tsx create mode 100644 client2/src/components/common/formating/index.ts create mode 100644 client2/src/components/common/formating/p.tsx create mode 100644 client2/src/components/common/index.ts create mode 100644 client2/src/components/common/ui/Icon/Icon.module.pcss create mode 100644 client2/src/components/common/ui/Icon/Icon.tsx create mode 100644 client2/src/components/common/ui/Icon/index.ts create mode 100644 client2/src/components/common/ui/Notifications/index.ts create mode 100644 client2/src/components/common/ui/Notifications/notifications.tsx create mode 100644 client2/src/components/common/ui/index.ts create mode 100644 client2/src/lib/ant/Radio.pcss create mode 100644 client2/src/lib/ant/Step.pcss create mode 100644 client2/src/lib/ant/ant-overrides.less create mode 100644 client2/src/lib/ant/ant.less create mode 100644 client2/src/lib/ant/index.ts create mode 100644 client2/src/lib/apis/blockedServices.ts create mode 100644 client2/src/lib/apis/clients.ts create mode 100644 client2/src/lib/apis/dhcp.ts create mode 100644 client2/src/lib/apis/filtering.ts create mode 100644 client2/src/lib/apis/global.ts create mode 100644 client2/src/lib/apis/i18n.ts create mode 100644 client2/src/lib/apis/install.ts create mode 100644 client2/src/lib/apis/log.ts create mode 100644 client2/src/lib/apis/mobileconfig.ts create mode 100644 client2/src/lib/apis/parental.ts create mode 100644 client2/src/lib/apis/rewrite.ts create mode 100644 client2/src/lib/apis/safebrowsing.ts create mode 100644 client2/src/lib/apis/safesearch.ts create mode 100644 client2/src/lib/apis/stats.ts create mode 100644 client2/src/lib/apis/tls.ts create mode 100644 client2/src/lib/consts/common.ts create mode 100644 client2/src/lib/consts/forms.ts create mode 100644 client2/src/lib/consts/install.ts create mode 100644 client2/src/lib/entities/AddUrlRequest.ts create mode 100644 client2/src/lib/entities/AddressInfo.ts create mode 100644 client2/src/lib/entities/AddressInfoBeta.ts create mode 100644 client2/src/lib/entities/AddressesInfo.ts create mode 100644 client2/src/lib/entities/AddressesInfoBeta.ts create mode 100644 client2/src/lib/entities/BlockedServicesArray.ts create mode 100644 client2/src/lib/entities/CheckConfigRequest.ts create mode 100644 client2/src/lib/entities/CheckConfigRequestBeta.ts create mode 100644 client2/src/lib/entities/CheckConfigRequestInfo.ts create mode 100644 client2/src/lib/entities/CheckConfigRequestInfoBeta.ts create mode 100644 client2/src/lib/entities/CheckConfigResponse.ts create mode 100644 client2/src/lib/entities/CheckConfigResponseInfo.ts create mode 100644 client2/src/lib/entities/CheckConfigStaticIpInfo.ts create mode 100644 client2/src/lib/entities/CheckConfigStaticIpInfoStatic.ts create mode 100644 client2/src/lib/entities/Client.ts create mode 100644 client2/src/lib/entities/ClientAuto.ts create mode 100644 client2/src/lib/entities/ClientDelete.ts create mode 100644 client2/src/lib/entities/ClientFindSubEntry.ts create mode 100644 client2/src/lib/entities/ClientUpdate.ts create mode 100644 client2/src/lib/entities/Clients.ts create mode 100644 client2/src/lib/entities/ClientsArray.ts create mode 100644 client2/src/lib/entities/ClientsAutoArray.ts create mode 100644 client2/src/lib/entities/ClientsFindEntry.ts create mode 100644 client2/src/lib/entities/ClientsFindResponse.ts create mode 100644 client2/src/lib/entities/DNSConfig.ts create mode 100644 client2/src/lib/entities/DhcpConfig.ts create mode 100644 client2/src/lib/entities/DhcpConfigV4.ts create mode 100644 client2/src/lib/entities/DhcpConfigV6.ts create mode 100644 client2/src/lib/entities/DhcpLease.ts create mode 100644 client2/src/lib/entities/DhcpSearchResult.ts create mode 100644 client2/src/lib/entities/DhcpSearchResultOtherServer.ts create mode 100644 client2/src/lib/entities/DhcpSearchResultStaticIP.ts create mode 100644 client2/src/lib/entities/DhcpSearchV4.ts create mode 100644 client2/src/lib/entities/DhcpSearchV6.ts create mode 100644 client2/src/lib/entities/DhcpStaticLease.ts create mode 100644 client2/src/lib/entities/DhcpStatus.ts create mode 100644 client2/src/lib/entities/DnsAnswer.ts create mode 100644 client2/src/lib/entities/DnsQuestion.ts create mode 100644 client2/src/lib/entities/Error.ts create mode 100644 client2/src/lib/entities/Filter.ts create mode 100644 client2/src/lib/entities/FilterCheckHostResponse.ts create mode 100644 client2/src/lib/entities/FilterConfig.ts create mode 100644 client2/src/lib/entities/FilterRefreshRequest.ts create mode 100644 client2/src/lib/entities/FilterRefreshResponse.ts create mode 100644 client2/src/lib/entities/FilterSetUrl.ts create mode 100644 client2/src/lib/entities/FilterStatus.ts create mode 100644 client2/src/lib/entities/GetVersionRequest.ts create mode 100644 client2/src/lib/entities/InitialConfiguration.ts create mode 100644 client2/src/lib/entities/InitialConfigurationBeta.ts create mode 100644 client2/src/lib/entities/Login.ts create mode 100644 client2/src/lib/entities/NetInterface.ts create mode 100644 client2/src/lib/entities/ProfileInfo.ts create mode 100644 client2/src/lib/entities/QueryLog.ts create mode 100644 client2/src/lib/entities/QueryLogConfig.ts create mode 100644 client2/src/lib/entities/QueryLogItem.ts create mode 100644 client2/src/lib/entities/RemoveUrlRequest.ts create mode 100644 client2/src/lib/entities/ResultRule.ts create mode 100644 client2/src/lib/entities/RewriteEntry.ts create mode 100644 client2/src/lib/entities/RewriteList.ts create mode 100644 client2/src/lib/entities/ServerStatus.ts create mode 100644 client2/src/lib/entities/Stats.ts create mode 100644 client2/src/lib/entities/StatsConfig.ts create mode 100644 client2/src/lib/entities/TlsConfig.ts create mode 100644 client2/src/lib/entities/TopArrayEntry.ts create mode 100644 client2/src/lib/entities/UpstreamsConfig.ts create mode 100644 client2/src/lib/entities/UpstreamsConfigResponse.ts create mode 100644 client2/src/lib/entities/VersionInfo.ts create mode 100644 client2/src/lib/entities/WhoisInfo.ts create mode 100644 client2/src/lib/helpers/apiErrors.ts create mode 100644 client2/src/lib/helpers/installHelpers.ts create mode 100644 client2/src/lib/theme/Form.module.pcss create mode 100644 client2/src/lib/theme/Icons/Icon.pcss create mode 100644 client2/src/lib/theme/Icons/index.tsx create mode 100644 client2/src/lib/theme/Typography.module.pcss create mode 100644 client2/src/lib/theme/index.ts create mode 100644 client2/src/localization/Translator/Translator.ts create mode 100644 client2/src/localization/Translator/index.ts create mode 100644 client2/src/localization/Translator/lib/formatter.ts create mode 100644 client2/src/localization/Translator/lib/nodes.ts create mode 100644 client2/src/localization/Translator/lib/parser.ts create mode 100644 client2/src/localization/Translator/lib/plural.ts create mode 100644 client2/src/localization/Translator/lib/translator.ts create mode 100644 client2/src/localization/Translator/lib/validator.ts create mode 100644 client2/src/localization/index.ts create mode 100644 client2/src/localization/locales/en.json create mode 100644 client2/src/localization/locales/index.ts create mode 100644 client2/src/localization/locales/ru.json create mode 100644 client2/src/main.pcss create mode 100644 client2/src/store/index.ts create mode 100644 client2/src/store/installStore.ts create mode 100644 client2/src/store/store.ts create mode 100644 client2/src/store/stores/Install.ts create mode 100644 client2/src/store/stores/ui.ts create mode 100644 client2/tsconfig.json create mode 100644 client2/yarn.lock diff --git a/.dockerignore b/.dockerignore index c200f675..de2a4c61 100644 --- a/.dockerignore +++ b/.dockerignore @@ -9,8 +9,10 @@ /AdGuardHome.log /data /build +/build2 /dist /client/node_modules +/client2/node_modules /.gitattributes /.gitignore /.goreleaser.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b2851e53..bf4e0683 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,7 +2,7 @@ 'env': 'GO_VERSION': '1.14' - 'NODE_VERSION': '13' + 'NODE_VERSION': '14' 'on': 'push': diff --git a/.gitignore b/.gitignore index beba3ce1..6cbf3ecc 100644 --- a/.gitignore +++ b/.gitignore @@ -7,9 +7,11 @@ # Only build, run, and test outputs here. Sorted. *-packr.go *.db +*.log *.snap /bin/ /build/ +/build2/ /data/ /dist/ /dnsfilter/tests/dnsfilter.TestLotsOfRules*.pprof @@ -19,4 +21,5 @@ /snapcraft_login AdGuardHome* coverage.txt +leases.db node_modules/ diff --git a/Dockerfile b/Dockerfile index b0c54832..a218f2d4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,6 +18,7 @@ RUN apk --update --no-cache add \ gcc \ git \ npm \ + yarn \ && rm -rf /tmp/* /var/cache/apk/* WORKDIR /app @@ -26,6 +27,7 @@ COPY . ./ # Prepare the client code RUN npm --prefix client ci && npm --prefix client run build-prod +RUN yarn --cwd client2 build # Download go dependencies RUN go mod download diff --git a/Makefile b/Makefile index 27247fe6..68c7f9dc 100644 --- a/Makefile +++ b/Makefile @@ -137,13 +137,16 @@ build: client: npm --prefix client run build-prod + yarn --cwd client2 build client_with_deps: npm --prefix client ci npm --prefix client run build-prod + yarn --cwd client2 build client-watch: npm --prefix client run watch + yarn --cwd client2 start docker: DOCKER_CLI_EXPERIMENTAL=enabled \ @@ -163,6 +166,7 @@ lint: js-lint go-lint js-lint: dependencies npm --prefix client run lint + yarn --cwd client2 lint go-install-tools: env GO=$(GO) sh ./scripts/go-install-tools.sh @@ -184,6 +188,7 @@ ci: client_with_deps dependencies: npm --prefix client ci + yarn --cwd client2 install $(GO) mod download clean: diff --git a/client2/.eslintignore b/client2/.eslintignore new file mode 100644 index 00000000..d30c2950 --- /dev/null +++ b/client2/.eslintignore @@ -0,0 +1,6 @@ +scripts +node_modules +postcss.config.js +src/lib/entities +src/lib/apis +openApi \ No newline at end of file diff --git a/client2/.eslintrc b/client2/.eslintrc new file mode 100644 index 00000000..86f6ae47 --- /dev/null +++ b/client2/.eslintrc @@ -0,0 +1,5 @@ +{ + "extends": [ + "./scripts/lint/dev.js" + ] +} \ No newline at end of file diff --git a/client2/declaration.d.ts b/client2/declaration.d.ts new file mode 100644 index 00000000..87c53150 --- /dev/null +++ b/client2/declaration.d.ts @@ -0,0 +1,18 @@ +declare module '*.pcss' { + const content: {[className: string]: string}; + export default content; +} +declare module '*.css' { + const content: {[className: string]: string}; + export default content; +} +declare module '*.png' +declare module '*.jpg' +declare let AUTH_TOKEN: string; +declare let MAIN_TOKEN: string | undefined; +declare let NO_CAPTCHA: boolean | undefined; +declare module 'dygraphs'; +declare module '@novnc/novnc/core/rfb'; +// cp - CloudPayments script +declare let cp: any; +declare const DEV: any; diff --git a/client2/package.json b/client2/package.json new file mode 100644 index 00000000..d48f1ca6 --- /dev/null +++ b/client2/package.json @@ -0,0 +1,92 @@ +{ + "author": "Performix", + "private": true, + "name": "adguard-home", + "version": "0.1.0", + "scripts": { + "build": "rm -rf ../build2 && yarn install && webpack --config ./scripts/webpack/webpack.config.prod.js", + "start": "webpack serve --config ./scripts/webpack/webpack.config.dev.js", + "generate": "rm -rf ./src/lib/entities ./src/lib/apis && ts-node --compiler-options '{ \"module\": \"CommonJS\" }' ./scripts/generator/index.ts", + "translations:check": "ts-node --compiler-options '{ \"module\": \"CommonJS\" }' ./scripts/plugins/checkTranslations.ts", + "lint": "eslint -c ./scripts/lint/prod.js --ext .tsx --ext .ts ./", + "go:build": "cd .. && make REBUILD_CLIENT=0 build", + "go:run": "sudo ../AdguardHome" + }, + "husky": { + "hooks": { + "pre-commit": "yarn lint" + } + }, + "license": "ISC", + "dependencies": { + "@sentry/react": "^5.27.0", + "antd": "^4.7.2", + "classnames": "^2.2.6", + "dayjs": "^1.9.3", + "formik": "^2.2.0", + "mobx": "^6.0.1", + "mobx-react-lite": "^3.0.1", + "qs": "^6.9.4", + "react": "^17.0.0", + "react-dom": "^17.0.0", + "react-router-dom": "^5.2.0" + }, + "devDependencies": { + "@types/classnames": "^2.2.10", + "@types/qs": "^6.9.5", + "@types/react": "^16.9.53", + "@types/react-dom": "^16.9.8", + "@types/react-redux": "^7.1.9", + "@types/react-router-dom": "^5.1.6", + "@typescript-eslint/eslint-plugin": "^4.5.0", + "@typescript-eslint/parser": "^4.5.0", + "antd-dayjs-webpack-plugin": "^1.0.1", + "autoprefixer": "^10.0.1", + "connect-history-api-fallback": "^1.6.0", + "copy-webpack-plugin": "^6.2.1", + "css-loader": "^5.0.0", + "eslint": "^7.11.0", + "eslint-config-airbnb-base": "^14.2.0", + "eslint-config-airbnb-typescript": "^12.0.0", + "eslint-import-resolver-typescript": "^2.3.0", + "eslint-loader": "^4.0.2", + "eslint-plugin-import": "^2.22.1", + "eslint-plugin-react": "^7.21.5", + "eslint-plugin-react-hooks": "^4.2.0", + "file-loader": "^6.1.1", + "html-webpack-plugin": "^4.5.0", + "http-proxy-middleware": "^1.0.6", + "husky": "^4.3.0", + "less": "^3.12.2", + "less-loader": "^5.0.0", + "mini-css-extract-plugin": "^1.1.1", + "optimize-css-assets-webpack-plugin": "^5.0.4", + "postcss": "^8.1.2", + "postcss-calc": "^7.0.5", + "postcss-css-variables": "^0.17.0", + "postcss-custom-media": "^7.0.8", + "postcss-import": "^13.0.0", + "postcss-inline-svg": "^4.1.0", + "postcss-loader": "^4.0.4", + "postcss-mixins": "^7.0.1", + "postcss-modules": "^3.2.2", + "postcss-nested": "^5.0.1", + "postcss-preset-env": "^6.7.0", + "postcss-reporter": "^7.0.1", + "postcss-variables": "^1.1.1", + "style-loader": "^2.0.0", + "stylelint": "^13.7.2", + "stylelint-webpack-plugin": "^2.1.1", + "terser-webpack-plugin": "^5.0.0", + "ts-loader": "^8.0.6", + "ts-morph": "^8.1.2", + "ts-node": "^9.0.0", + "typescript": "^4.0.3", + "url-loader": "^4.1.1", + "webpack": "^5.10.0", + "webpack-cli": "^4.2.0", + "webpack-dev-server": "^3.11.0", + "webpack-merge": "^5.2.0", + "yaml": "^1.10.0" + } +} diff --git a/client2/postcss.config.js b/client2/postcss.config.js new file mode 100644 index 00000000..ee95a234 --- /dev/null +++ b/client2/postcss.config.js @@ -0,0 +1,17 @@ +module.exports = { + plugins: [ + ['postcss-import', {}], + ['postcss-nested', {}], + ['postcss-custom-media', {}], + ['postcss-variables', {}], + ['postcss-calc', {}], + ['postcss-mixins', {}], + ['postcss-preset-env', { stage: 3, features: { 'nesting-rules': true } }], + ['postcss-reporter', { clearMessages: true }], + ['postcss-inline-svg', { + paths: ['frontend/icons', 'vendor/adguard/utils-bundle/src/Resources/frontend/icons'], + svgo: { plugins: [{ cleanupAttrs: true }] } + }], + ['autoprefixer'], + ] +}; diff --git a/client2/public/assets/apple-touch-icon-180x180.png b/client2/public/assets/apple-touch-icon-180x180.png new file mode 100644 index 0000000000000000000000000000000000000000..ebc0be5067efcf4d8366a70cb7a4777327d9cfc4 GIT binary patch literal 2137 zcmV-f2&VUmP)crq!>P&7z9NmVd#Fb-aXcxPNW8d~3CPYP5Q2w0dZ>d1tbDYQ1xAzjt%N ze0Rftd&Pr*$B2Z<@WpcUc-0}JI{r~-|*0OWDf_1`ulh2vW>Cox+>i+-!vf8eHpys`dN!X}ogi_33W6e#hp^`TY5V$cuZwiN)o~qSmLO)uxZh zn!@D8w%@y{*0I0h!shho;qv3T;Jvioxw720n9ibgxrLw3s=VFA%;nO_eqR=-A=#DjK%w$)VEh)xzM(gTRt$ zvwLo}f8+7y-|ymVw0(EFhwu0B`~Lg2*}jp^my*ezkIt5_*|(+Au#3c*^ZN67!-3T7 z*Sg%o^Z4|N%aMM-i{|p_Z?=HB-oV}M;nnKe*X`MIxPknBVc?cf5ql=+5f_h~xkO1(HcbK~#7F?b!pDTTL9t@n7vs*7nry+TNr#*L=*{ zwr$(CZMUzdSl&f)Cz-jq$vN|R1>b|=AKH*4Ns=To#rM zweQfelfgiYZtfnQUfw>we*OW0LBSz#DxqQFts~kSNCUKi&Rx28>)xX$^wuk=ck4bi zwJOmy`u6khKOhqNXciUS&Oof7R>b(k#)ZJK;u8{+h#IQGm>e6I0tZQL-JY}M`ei#r#Ef@kGziJYl8;2i$ z`<+_y2YCFcMYQoR{PZ`qXaIQpqd~N>KYj|NmSuoTC*jfm2I8mx+^9t!;Pb!e=zk~U zrzumZWj^3DO?32hRGLApmTu{Dubz2xxax^2XWDhP-Qx`ZYp?96BfM~Rf5M->t=%25>e6Rs1lM-Rg40! z(L~+SWy@ErT(#P2bS$cMrE125*FQwfnzfOrv~IoS=;5d~o2oII!A)hXwP7P_MQ*Yb z{Wq$O7)#Ym1;5S2sx6UF*xGdTj7U@o*+x~xg5P#x-6kmP*x6L{;2o$|Le=d8zh*?m zZYYH8X&l|Pws{^^murTCR=3l=8==r_X~XCe6RM@>vJ4U-LJ?81Z$A_c9IPK*Slj%M zC;Rt-_B-{V!%$dJFS_4vsNqXl^neIQIuSK{LZEO|5q+$#Ie@YlWP%XWh^pf|ps=hq z`ot8}7;mI3W5ve?mHmJ zCBk3@6fR#Ogm$TG#zM-*529Qp3=Up{!u7o*`5)9OHBvS=Ac`rEFt~XO3cL55L9H_r zDVx4KAWR}*ar*)k?x0p=Kla~raqbc(r+-6}=1^AiAy7NQ_zpOecMQ>P@8M*Ic2tY@908{j-^Y6NrxZA$UdOFO z=PrzdbDG}KQuMaZa9U;4nvNc9hBIqvXdLYx2qzc%rD1eQPdLB124%GSSGa^HW9p*E zoq?-()3GW#KL{?wJc|(OHpc|l@<&_WW{@OFk|arzBuSDaNs=T%*+g7R25BU$p?<* zENJFmG`ug8-g|n_^zpx6=u(U_;VCkOizP*NE0N4yB1Eo%5czgO;GBe&_x)q#gM`%* zA|=`b)eKci%Jc>JU4c4CNz9@ci6%%X>Al!9{JnbZ&~(wflk@JNQn*BD}wuGR79zmm^Xg#t+ zs6(X8P}DL02~7a-!n@&eSWm)bpw&S98PGgK7a`0a^hx>-6+oP1d5;j;v#O)*~q`)>}ED;S$h>n(ma8|4)9OLl!0KQBZpFaK`z*o>jSfdRP)fwl!v$d4>A9e^qa4`1b};t%6fMCm)kKI~ z`Eb|+^Uf~?ZoeCZhftoT6?&_QNM_}s23T@wHSh+!$oR|6Igz9n6hZ10Q_C^p0 z29WV}_6Crs@Y4^V-El$^3O!F5BT(%pv~E# zg6MR2!R0hd1wcs*I8}eH-?bg?4!|BAuR9B66z0BuM@m#th+uP$ljnjik6i`PXm1J6 zQqOgl-2JVU%&@uoSBc4LMW;;F*?9U=wYVV7NMmxa8!2Gf&_5_h!e+y+|~ls z|JUbk#mj+YhEf3#lH~^aiX|=eD7MbhXjwYyz|mWv93RZ(p;p z1xqUqy%3u&?M2ab;Bfg}V8Y26M&$qJu3MQSMRpuc>aaUk=x02f6_jlnU_6LX=ub42 z7D<8c#DNfs zV+{geR!3Z&PB=E<5yEN-41=;teWEhqE?xZrN@Qmabyb3rm~XHe9H{#;L;&F~4sM4A zb!P~H>mGt7bP{!GjA$`Yg85BC-~)pQ&_GM{gGTkpkjPZ-dsIh_es#Kol19B^WRtH) zR7w*052|KI4Zsh3}-0D=pcmSkue;6$%;{ZCs{bId+VqK!r z+3z4kzyLQ|7i*(mHonoVZ8^OSAzZq>kW6PK9OE9ru>lL9r?)Kfg^_MzN~YILP$t}t zo`Vm7FH+=I5{LPahCSdQW%JMY?=%(wiOS$UCk4J=)E`0*xE zOxlHJqZgnH&P414FyTgY-4Fw4Pd`MyE+*4_K$R94sQ& + + + + + diff --git a/client2/public/index.html b/client2/public/index.html new file mode 100644 index 00000000..2e2d1e33 --- /dev/null +++ b/client2/public/index.html @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + AdGuard Home + + + +
+ + diff --git a/client2/public/install.html b/client2/public/install.html new file mode 100644 index 00000000..e0b70d14 --- /dev/null +++ b/client2/public/install.html @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + Setup AdGuard Home + + + +
+ + diff --git a/client2/public/login.html b/client2/public/login.html new file mode 100644 index 00000000..38145e15 --- /dev/null +++ b/client2/public/login.html @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + Login + + + +
+ + diff --git a/client2/scripts/consts.ts b/client2/scripts/consts.ts new file mode 100644 index 00000000..bbec9361 --- /dev/null +++ b/client2/scripts/consts.ts @@ -0,0 +1,12 @@ +export const OPEN_API_PATH = '../openapi/openapi.yaml'; +export const ENT_DIR = './src/lib/entities'; +export const API_DIR = './src/lib/apis'; +export const LOCALE_FOLDER_PATH = './src/lib/intl/__locales'; +export const TRANSLATOR_CLASS_NAME = 'Translator'; +export const USE_INTL_NAME = 'useIntl'; + +export const trimQuotes = (str: string) => { + return str.replace(/\'|\"/g, ''); +}; + +export const GENERATOR_ENTITY_ALLIAS = 'Entities/'; \ No newline at end of file diff --git a/client2/scripts/generator/index.ts b/client2/scripts/generator/index.ts new file mode 100644 index 00000000..19ac2256 --- /dev/null +++ b/client2/scripts/generator/index.ts @@ -0,0 +1,18 @@ +import * as fs from 'fs'; +import * as YAML from 'yaml'; +import { OPEN_API_PATH } from '../consts'; + +import EntitiesGenerator from './src/generateEntities'; +import ApisGenerator from './src/generateApis'; + + +const generateApi = (openApi: Record) => { + const ent = new EntitiesGenerator(openApi); + ent.save(); + + const api = new ApisGenerator(openApi); + api.save(); +} + +const openApiFile = fs.readFileSync(OPEN_API_PATH, 'utf8'); +generateApi(YAML.parse(openApiFile)); diff --git a/client2/scripts/generator/src/generateApis.ts b/client2/scripts/generator/src/generateApis.ts new file mode 100644 index 00000000..4201da2b --- /dev/null +++ b/client2/scripts/generator/src/generateApis.ts @@ -0,0 +1,317 @@ +/* eslint-disable no-template-curly-in-string */ +/* eslint-disable @typescript-eslint/no-unused-expressions */ +import * as fs from 'fs'; +import * as path from 'path'; +import { stringify } from 'qs'; +// eslint-disable-next-line import/no-extraneous-dependencies +import * as morph from 'ts-morph'; + +import { + API_DIR as API_DIR_CONST, + GENERATOR_ENTITY_ALLIAS, +} from '../../consts'; +import { toCamel, capitalize, schemaParamParser } from './utils'; + + +const API_DIR = path.resolve(API_DIR_CONST); +if (!fs.existsSync(API_DIR)) { + fs.mkdirSync(API_DIR); +} + +const { Project, QuoteKind } = morph; + + +class ApiGenerator { + project = new Project({ + tsConfigFilePath: './tsconfig.json', + addFilesFromTsConfig: false, + manipulationSettings: { + quoteKind: QuoteKind.Single, + usePrefixAndSuffixTextForRename: false, + useTrailingCommas: true, + }, + }); + + openapi: Record; + + serverUrl: string; + + paths: any; + + /* interface Controllers { + [controller: string]: { + [operationId: string]: { parameters - from opneApi, responses - from opneApi, method } + } + } */ + controllers: Record = {}; + + apis: morph.SourceFile[] = []; + + constructor(openapi: Record) { + this.openapi = openapi; + this.paths = openapi.paths; + this.serverUrl = openapi.servers[0].url; + + Object.keys(this.paths).forEach((pathKey) => { + Object.keys(this.paths[pathKey]).forEach((method) => { + const { + tags, operationId, parameters, responses, requestBody, security, + } = this.paths[pathKey][method]; + const controller = toCamel((tags ? tags[0] : pathKey.split('/')[1]).replace('-controller', '')); + + if (this.controllers[controller]) { + this.controllers[controller][operationId] = { + parameters, + responses, + method, + requestBody, + security, + pathKey: pathKey.replace(/{/g, '${'), + }; + } else { + this.controllers[controller] = { [operationId]: { + parameters, + responses, + method, + requestBody, + security, + pathKey: pathKey.replace(/{/g, '${'), + } }; + } + }); + }); + + this.generateApiFiles(); + } + + generateApiFiles = () => { + Object.keys(this.controllers).forEach(this.generateApiFile); + }; + + generateApiFile = (cName: string) => { + const apiFile = this.project.createSourceFile(`${API_DIR}/${cName}.ts`); + apiFile.addStatements([ + '// This file was autogenerated. Please do not change.', + '// All changes will be overwrited on commit.', + '', + ]); + + // const schemaProperties = schemas[schemaName].properties; + const importEntities: any[] = []; + + // add api class to file + const apiClass = apiFile.addClass({ + name: `${capitalize(cName)}Api`, + isDefaultExport: true, + }); + + // get operations of controller + const controllerOperations = this.controllers[cName]; + const operationList = Object.keys(controllerOperations).sort(); + // for each operation add fetcher + operationList.forEach((operation) => { + const { + requestBody, responses, parameters, method, pathKey, security, + } = controllerOperations[operation]; + + const queryParams: any[] = []; // { name, type } + const bodyParam: any[] = []; // { name, type } + + let hasResponseBodyType: /* boolean | ReturnType */ false | [string, boolean, boolean, boolean, boolean] = false; + let contentType = ''; + if (parameters) { + parameters.forEach((p: any) => { + const [ + pType, isArray, isClass, isImport, + ] = schemaParamParser(p.schema, this.openapi); + + if (isImport) { + importEntities.push({ type: pType, isClass }); + } + if (p.in === 'query') { + queryParams.push({ + name: p.name, type: `${pType}${isArray ? '[]' : ''}`, hasQuestionToken: !p.required }); + } + }); + } + if (queryParams.length > 0) { + const imp = apiFile.getImportDeclaration((i) => { + return i.getModuleSpecifierValue() === 'qs'; + }); if (!imp) { + apiFile.addImportDeclaration({ + moduleSpecifier: 'qs', + defaultImport: 'qs', + }); + } + } + if (requestBody) { + let content = requestBody.content; + const { $ref }: { $ref: string } = requestBody; + + if (!content && $ref) { + const name = $ref.split('/').pop() as string; + content = this.openapi.components.requestBodies[name].content; + } + + [contentType] = Object.keys(content); + const data = content[contentType]; + + const [ + pType, isArray, isClass, isImport, + ] = schemaParamParser(data.schema, this.openapi); + + if (isImport) { + importEntities.push({ type: pType, isClass }); + bodyParam.push({ name: pType.toLowerCase(), type: `${isClass ? 'I' : ''}${pType}${isArray ? '[]' : ''}`, isClass, pType }); + } else { + bodyParam.push({ name: 'data', type: `${pType}${isArray ? '[]' : ''}` }); + + } + } + if (responses['200']) { + const { content, headers } = responses['200']; + if (content && (content['*/*'] || content['application/json'])) { + const { schema, examples } = content['*/*'] || content['application/json']; + + if (!schema) { + process.exit(0); + } + + const propType = schemaParamParser(schema, this.openapi); + const [pType, , isClass, isImport] = propType; + + if (isImport) { + importEntities.push({ type: pType, isClass }); + } + hasResponseBodyType = propType; + } + } + let returnType = ''; + if (hasResponseBodyType) { + const [pType, isArray, isClass] = hasResponseBodyType as any; + let data = `Promise<${isClass ? 'I' : ''}${pType}${isArray ? '[]' : ''}`; + returnType = data; + } else { + returnType = 'Promise b.isClass); + if (shouldValidate.length > 0) { + returnType += ' | string[]'; + } + // append Error to default type return; + returnType += ' | Error>'; + + const fetcher = apiClass.addMethod({ + isAsync: true, + isStatic: true, + name: operation, + returnType, + }); + const params = [...queryParams, ...bodyParam].sort((a, b) => (Number(!!a.hasQuestionToken) - Number(!!b.hasQuestionToken))); + fetcher.addParameters(params); + + fetcher.setBodyText((w) => { + // Add data to URLSearchParams + if (contentType === 'text/plain') { + bodyParam.forEach((b) => { + w.writeLine(`const params = String(${b.name});`); + }); + } else { + if (shouldValidate.length > 0) { + w.writeLine(`const haveError: string[] = [];`); + shouldValidate.forEach((b) => { + w.writeLine(`const ${b.name}Valid = new ${b.pType}(${b.name});`); + w.writeLine(`haveError.push(...${b.name}Valid.validate());`); + }); + w.writeLine(`if (haveError.length > 0) {`); + w.writeLine(` return Promise.resolve(haveError);`) + w.writeLine(`}`); + } + } + // Switch return of fetch in case on queryParams + if (queryParams.length > 0) { + w.writeLine('const queryParams = {'); + queryParams.forEach((q) => { + w.writeLine(` ${q.name}: ${q.name},`); + }); + w.writeLine('}'); + w.writeLine(`return await fetch(\`${this.serverUrl}${pathKey}?\${qs.stringify(queryParams, { arrayFormat: 'comma' })}\`, {`); + } else { + w.writeLine(`return await fetch(\`${this.serverUrl}${pathKey}\`, {`); + } + // Add method + w.writeLine(` method: '${method.toUpperCase()}',`); + + // add Fetch options + if (contentType && contentType !== 'multipart/form-data') { + w.writeLine(' headers: {'); + w.writeLine(` 'Content-Type': '${contentType}',`); + w.writeLine(' },'); + } + if (contentType) { + switch (contentType) { + case 'text/plain': + w.writeLine(' body: params,'); + break; + default: + w.writeLine(` body: JSON.stringify(${bodyParam.map((b) => b.isClass ? `${b.name}Valid.serialize()` : b.name).join(', ')}),`); + break; + } + } + + // Handle response + if (hasResponseBodyType) { + w.writeLine('}).then(async (res) => {'); + w.writeLine(' if (res.status === 200) {'); + w.writeLine(' return res.json();'); + } else { + w.writeLine('}).then(async (res) => {'); + w.writeLine(' if (res.status === 200) {'); + w.writeLine(' return res.status;'); + } + + // Handle Error + w.writeLine(' } else {'); + w.writeLine(' return new Error(String(res.status));'); + w.writeLine(' }'); + w.writeLine('})'); + }); + }); + + const imports: any[] = []; + const types: string[] = []; + importEntities.forEach((i) => { + const { type } = i; + if (!types.includes(type)) { + imports.push(i); + types.push(type); + } + }); + imports.sort((a,b) => a.type > b.type ? 1 : -1).forEach((ie) => { + const { type: pType, isClass } = ie; + if (isClass) { + apiFile.addImportDeclaration({ + moduleSpecifier: `${GENERATOR_ENTITY_ALLIAS}${pType}`, + defaultImport: pType, + namedImports: [`I${pType}`], + }); + } else { + apiFile.addImportDeclaration({ + moduleSpecifier: `${GENERATOR_ENTITY_ALLIAS}${pType}`, + namedImports: [pType], + }); + } + }); + + this.apis.push(apiFile); + }; + + save = () => { + this.apis.forEach(async (e) => { + await e.saveSync(); + }); + }; +} + + +export default ApiGenerator; diff --git a/client2/scripts/generator/src/generateEntities.ts b/client2/scripts/generator/src/generateEntities.ts new file mode 100644 index 00000000..b12e02a7 --- /dev/null +++ b/client2/scripts/generator/src/generateEntities.ts @@ -0,0 +1,519 @@ +import * as fs from 'fs'; +import * as path from 'path'; +// eslint-disable-next-line import/no-extraneous-dependencies +import * as morph from 'ts-morph'; + +import { ENT_DIR } from '../../consts'; +import { TYPES, toCamel, schemaParamParser } from './utils'; + +const { Project, QuoteKind } = morph; + + +const EntDir = path.resolve(ENT_DIR); +if (!fs.existsSync(EntDir)) { + fs.mkdirSync(EntDir); +} + +class EntitiesGenerator { + project = new Project({ + tsConfigFilePath: './tsconfig.json', + addFilesFromTsConfig: false, + manipulationSettings: { + quoteKind: QuoteKind.Single, + usePrefixAndSuffixTextForRename: false, + useTrailingCommas: true, + }, + }); + + openapi: Record; + + schemas: Record; + + schemaNames: string[]; + + entities: morph.SourceFile[] = []; + + constructor(openapi: Record) { + this.openapi = openapi; + this.schemas = openapi.components.schemas; + this.schemaNames = Object.keys(this.schemas); + this.generateEntities(); + } + + generateEntities = () => { + this.schemaNames.forEach(this.generateEntity); + }; + + generateEntity = (sName: string) => { + const { properties, type, oneOf } = this.schemas[sName]; + const notAClass = !properties && TYPES[type as keyof typeof TYPES]; + + if (oneOf) { + this.generateOneOf(sName); + return; + } + + if (notAClass) { + this.generateEnum(sName); + } else { + this.generateClass(sName); + } + }; + + generateEnum = (sName: string) => { + const entityFile = this.project.createSourceFile(`${EntDir}/${sName}.ts`); + entityFile.addStatements([ + '// This file was autogenerated. Please do not change.', + '// All changes will be overwrited on commit.', + '', + ]); + + const { enum: enumMembers } = this.schemas[sName]; + entityFile.addEnum({ + name: sName, + members: enumMembers.map((e: string) => ({ name: e.toUpperCase(), value: e })), + isExported: true, + }); + + this.entities.push(entityFile); + }; + + generateOneOf = (sName: string) => { + const entityFile = this.project.createSourceFile(`${EntDir}/${sName}.ts`); + entityFile.addStatements([ + '// This file was autogenerated. Please do not change.', + '// All changes will be overwrited on commit.', + '', + ]); + const importEntities: { type: string, isClass: boolean }[] = []; + const entities = this.schemas[sName].oneOf.map((elem: any) => { + const [ + pType, isArray, isClass, isImport, + ] = schemaParamParser(elem, this.openapi); + importEntities.push({ type: pType, isClass }); + return { type: pType, isArray }; + }); + entityFile.addTypeAlias({ + name: sName, + isExported: true, + type: entities.map((e: any) => e.isArray ? `I${e.type}[]` : `I${e.type}`).join(' | '), + }) + + // add import + importEntities.sort((a, b) => a.type > b.type ? 1 : -1).forEach((ie) => { + const { type: pType, isClass } = ie; + if (isClass) { + entityFile.addImportDeclaration({ + moduleSpecifier: `./${pType}`, + namedImports: [`I${pType}`], + }); + } else { + entityFile.addImportDeclaration({ + moduleSpecifier: `./${pType}`, + namedImports: [pType], + }); + } + }); + this.entities.push(entityFile); + } + + generateClass = (sName: string) => { + const entityFile = this.project.createSourceFile(`${EntDir}/${sName}.ts`); + entityFile.addStatements([ + '// This file was autogenerated. Please do not change.', + '// All changes will be overwrited on commit.', + '', + ]); + + const { properties: sProps, required } = this.schemas[sName]; + + const importEntities: { type: string, isClass: boolean }[] = []; + const entityInterface = entityFile.addInterface({ + name: `I${sName}`, + isExported: true, + }); + const sortedSProps = Object.keys(sProps || {}).sort(); + // add server response interface to entityFile + sortedSProps.forEach((sPropName) => { + const [ + pType, isArray, isClass, isImport, isAdditional + ] = schemaParamParser(sProps[sPropName], this.openapi); + + if (isImport) { + importEntities.push({ type: pType, isClass }); + } + const propertyType = isAdditional + ? `{ [key: string]: ${isClass ? 'I' : ''}${pType}${isArray ? '[]' : ''} }` + : `${isClass ? 'I' : ''}${pType}${isArray ? '[]' : ''}`; + entityInterface.addProperty({ + name: sPropName, + type: propertyType, + hasQuestionToken: !( + (required && required.includes(sPropName)) || sProps[sPropName].required + ), + }); + }); + + // add import + const imports: { type: string, isClass: boolean }[] = []; + const types: string[] = []; + importEntities.forEach((i) => { + const { type } = i; + if (!types.includes(type)) { + imports.push(i); + types.push(type); + } + }); + imports.sort((a, b) => a.type > b.type ? 1 : -1).forEach((ie) => { + const { type: pType, isClass } = ie; + if (isClass) { + entityFile.addImportDeclaration({ + defaultImport: pType, + moduleSpecifier: `./${pType}`, + namedImports: [`I${pType}`], + }); + } else { + entityFile.addImportDeclaration({ + moduleSpecifier: `./${pType}`, + namedImports: [pType], + }); + } + }); + + const entityClass = entityFile.addClass({ + name: sName, + isDefaultExport: true, + }); + + // addProperties to class; + sortedSProps.forEach((sPropName) => { + const [pType, isArray, isClass, isImport, isAdditional] = schemaParamParser(sProps[sPropName], this.openapi); + + const isRequred = (required && required.includes(sPropName)) + || sProps[sPropName].required; + + const propertyType = isAdditional + ? `{ [key: string]: ${pType}${isArray ? '[]' : ''}${isRequred ? '' : ' | undefined'} }` + : `${pType}${isArray ? '[]' : ''}${isRequred ? '' : ' | undefined'}`; + + entityClass.addProperty({ + name: `_${sPropName}`, + isReadonly: true, + type: propertyType, + }); + const getter = entityClass.addGetAccessor({ + name: toCamel(sPropName), + returnType: propertyType, + statements: [`return this._${sPropName};`], + }); + const { description, example, minItems, maxItems, maxLength, minLength, maximum, minimum } = sProps[sPropName]; + if (description || example) { + getter.addJsDoc(`${example ? `Description: ${description}` : ''}${example ? `\nExample: ${example}` : ''}`); + } + if (minItems) { + entityClass.addGetAccessor({ + isStatic: true, + name: `${toCamel(sPropName)}MinItems`, + statements: [`return ${minItems};`], + }); + } + if (maxItems) { + entityClass.addGetAccessor({ + isStatic: true, + name: `${toCamel(sPropName)}MaxItems`, + statements: [`return ${maxItems};`], + }); + } + if (typeof minLength === 'number') { + entityClass.addGetAccessor({ + isStatic: true, + name: `${toCamel(sPropName)}MinLength`, + statements: [`return ${minLength};`], + }); + } + if (maxLength) { + entityClass.addGetAccessor({ + isStatic: true, + name: `${toCamel(sPropName)}MaxLength`, + statements: [`return ${maxLength};`], + }); + } + if (typeof minimum === 'number') { + entityClass.addGetAccessor({ + isStatic: true, + name: `${toCamel(sPropName)}MinValue`, + statements: [`return ${minimum};`], + }); + } + if (maximum) { + entityClass.addGetAccessor({ + isStatic: true, + name: `${toCamel(sPropName)}MaxValue`, + statements: [`return ${maximum};`], + }); + } + + if (!(isArray && isClass) && !isClass) { + const isEnum = !isClass && isImport; + const isRequired = (required && required.includes(sPropName)) || sProps[sPropName].required; + const { maxLength, minLength, maximum, minimum } = sProps[sPropName]; + const haveValidationFields = maxLength || typeof minLength === 'number' || maximum || typeof minimum === 'number'; + if (isRequired || haveValidationFields) { + const prop = toCamel(sPropName); + const validateField = entityClass.addMethod({ + isStatic: true, + name: `${prop}Validate`, + returnType: `boolean`, + parameters: [{ + name: prop, + type: `${pType}${isArray ? '[]' : ''}${isRequred ? '' : ' | undefined'}`, + }], + }) + + validateField.setBodyText((w) => { + w.write('return '); + const nonRequiredCall = isRequired ? prop : `!${prop} ? true : ${prop}`; + if (pType === 'string') { + if (isArray) { + w.write(`${nonRequiredCall}.reduce((result, p) => result && (typeof p === 'string' && !!p.trim()), true)`); + } else { + if (typeof minLength === 'number' && maxLength) { + w.write(`(${nonRequiredCall}.length >${minLength > 0 ? '=' : ''} ${minLength}) && (${nonRequiredCall}.length <= ${maxLength})`); + } + if (typeof minLength !== 'number' || !maxLength) { + w.write(`${isRequired ? `typeof ${prop} === 'string'` : `!${prop} ? true : typeof ${prop} === 'string'`} && !!${nonRequiredCall}.trim()`); + } + } + } else if (pType === 'number') { + if (isArray) { + w.write(`${nonRequiredCall}.reduce((result, p) => result && typeof p === 'number', true)`); + } else { + if (typeof minimum === 'number' && maximum) { + w.write(`${isRequired ? `${prop} >= ${minimum} && ${prop} <= ${maximum}` : `!${prop} ? true : ((${prop} >= ${minimum}) && (${prop} <= ${maximum}))`}`); + } + if (typeof minimum !== 'number' || !maximum) { + w.write(`${isRequired ? `typeof ${prop} === 'number'` : `!${prop} ? true : typeof ${prop} === 'number'`}`); + } + } + } else if (pType === 'boolean') { + w.write(`${isRequired ? `typeof ${prop} === 'boolean'` : `!${prop} ? true : typeof ${prop} === 'boolean'`}`); + } else if (isEnum) { + if (isArray){ + w.write(`${nonRequiredCall}.reduce((result, p) => result && Object.keys(${pType}).includes(${prop}), true)`); + } else { + w.write(`${isRequired ? `Object.keys(${pType}).includes(${prop})` : `!${prop} ? true : typeof ${prop} === 'boolean'`}`); + } + } + + w.write(';'); + }); + } + } + }); + + // add constructor; + const ctor = entityClass.addConstructor({ + parameters: [{ + name: 'props', + type: `I${sName}`, + }], + }); + ctor.setBodyText((w) => { + sortedSProps.forEach((sPropName) => { + const [ + pType, isArray, isClass, , isAdditional + ] = schemaParamParser(sProps[sPropName], this.openapi); + const req = (required && required.includes(sPropName)) + || sProps[sPropName].required; + if (!req) { + if ((pType === 'boolean' || pType === 'number' || pType ==='string') && !isClass && !isArray) { + w.writeLine(`if (typeof props.${sPropName} === '${pType}') {`); + } else { + w.writeLine(`if (props.${sPropName}) {`); + } + } + if (isAdditional) { + if (isArray && isClass) { + w.writeLine(`${!req ? ' ' : ''}this._${sPropName} = props.${sPropName}.map((p) => Object.keys(p).reduce((prev, key) => { + return { ...prev, [key]: new ${pType}(p[key])}; + },{}))`); + } else if (isClass) { + w.writeLine(`${!req ? ' ' : ''}this._${sPropName} = Object.keys(props.${sPropName}).reduce((prev, key) => { + return { ...prev, [key]: new ${pType}(props.${sPropName}[key])}; + },{})`); + } else { + if (pType === 'string' && !isArray) { + w.writeLine(`${!req ? ' ' : ''}this._${sPropName} = Object.keys(props.${sPropName}).reduce((prev, key) => { + return { ...prev, [key]: props.${sPropName}[key].trim()}; + },{})`); + } else { + w.writeLine(`${!req ? ' ' : ''}this._${sPropName} = Object.keys(props.${sPropName}).reduce((prev, key) => { + return { ...prev, [key]: props.${sPropName}[key]}; + },{})`); + } + } + } else { + if (isArray && isClass) { + w.writeLine(`${!req ? ' ' : ''}this._${sPropName} = props.${sPropName}.map((p) => new ${pType}(p));`); + } else if (isClass) { + w.writeLine(`${!req ? ' ' : ''}this._${sPropName} = new ${pType}(props.${sPropName});`); + } else { + if (pType === 'string' && !isArray) { + w.writeLine(`${!req ? ' ' : ''}this._${sPropName} = props.${sPropName}.trim();`); + } else { + w.writeLine(`${!req ? ' ' : ''}this._${sPropName} = props.${sPropName};`); + } + } + } + if (!req) { + w.writeLine('}'); + } + }); + }); + + // add serialize method; + const serialize = entityClass.addMethod({ + isStatic: false, + name: 'serialize', + returnType: `I${sName}`, + }); + serialize.setBodyText((w) => { + w.writeLine(`const data: I${sName} = {`); + const unReqFields: string[] = []; + sortedSProps.forEach((sPropName) => { + const req = (required && required.includes(sPropName)) + || sProps[sPropName].required; + const [, isArray, isClass, , isAdditional] = schemaParamParser(sProps[sPropName], this.openapi); + if (!req) { + unReqFields.push(sPropName); + return; + } + if (isAdditional) { + if (isArray && isClass) { + w.writeLine(` ${sPropName}: this._${sPropName}.map((p) => Object.keys(p).reduce((prev, key) => ({ ...prev, [key]: p[key].serialize() }))),`); + } else if (isClass) { + w.writeLine(` ${sPropName}: Object.keys(this._${sPropName}).reduce>((prev, key) => ({ ...prev, [key]: this._${sPropName}[key].serialize() }), {}),`); + } else { + w.writeLine(` ${sPropName}: Object.keys(this._${sPropName}).reduce((prev, key) => ({ ...prev, [key]: this._${sPropName}[key] })),`); + } + } else { + if (isArray && isClass) { + w.writeLine(` ${sPropName}: this._${sPropName}.map((p) => p.serialize()),`); + } else if (isClass) { + w.writeLine(` ${sPropName}: this._${sPropName}.serialize(),`); + } else { + w.writeLine(` ${sPropName}: this._${sPropName},`); + } + } + + }); + w.writeLine('};'); + unReqFields.forEach((sPropName) => { + const [, isArray, isClass, , isAdditional] = schemaParamParser(sProps[sPropName], this.openapi); + w.writeLine(`if (typeof this._${sPropName} !== 'undefined') {`); + if (isAdditional) { + if (isArray && isClass) { + w.writeLine(` data.${sPropName} = this._${sPropName}.map((p) => Object.keys(p).reduce((prev, key) => ({ ...prev, [key]: p[key].serialize() }), {}));`); + } else if (isClass) { + w.writeLine(` data.${sPropName} = Object.keys(this._${sPropName}).reduce((prev, key) => ({ ...prev, [key]: this._${sPropName}[key].serialize() }), {});`); + } else { + w.writeLine(` data.${sPropName} = Object.keys(this._${sPropName}).reduce((prev, key) => ({ ...prev, [key]: this._${sPropName}[key] }), {});`); + } + } else { + if (isArray && isClass) { + w.writeLine(` data.${sPropName} = this._${sPropName}.map((p) => p.serialize());`); + } else if (isClass) { + w.writeLine(` data.${sPropName} = this._${sPropName}.serialize();`); + } else { + w.writeLine(` data.${sPropName} = this._${sPropName};`); + + } + } + + w.writeLine(`}`); + }); + w.writeLine('return data;'); + }); + + // add validate method + const validate = entityClass.addMethod({ + isStatic: false, + name: 'validate', + returnType: `string[]`, + }) + validate.setBodyText((w) => { + w.writeLine('const validate = {'); + Object.keys(sProps || {}).forEach((sPropName) => { + const [pType, isArray, isClass, , isAdditional] = schemaParamParser(sProps[sPropName], this.openapi); + + const { maxLength, minLength, maximum, minimum } = sProps[sPropName]; + + const isRequired = (required && required.includes(sPropName)) || sProps[sPropName].required; + const nonRequiredCall = isRequired ? `this._${sPropName}` : `!this._${sPropName} ? true : this._${sPropName}`; + + if (isArray && isClass) { + w.writeLine(` ${sPropName}: ${nonRequiredCall}.reduce((result, p) => result && p.validate().length === 0, true),`); + } else if (isClass && !isAdditional) { + w.writeLine(` ${sPropName}: ${nonRequiredCall}.validate().length === 0,`); + } else { + if (pType === 'string') { + if (isArray) { + w.writeLine(` ${sPropName}: ${nonRequiredCall}.reduce((result, p) => result && typeof p === 'string', true),`); + } else { + if (typeof minLength === 'number' && maxLength) { + w.writeLine(` ${sPropName}: (${nonRequiredCall}.length >${minLength > 0 ? '=' : ''} ${minLength}) && (${nonRequiredCall}.length <= ${maxLength}),`); + } + if (typeof minLength !== 'number' || !maxLength) { + w.writeLine(` ${sPropName}: ${isRequired ? `typeof this._${sPropName} === 'string'` : `!this._${sPropName} ? true : typeof this._${sPropName} === 'string'`} && !this._${sPropName} ? true : this._${sPropName},`); + } + } + } else if (pType === 'number') { + if (isArray) { + w.writeLine(` ${sPropName}: ${nonRequiredCall}.reduce((result, p) => result && typeof p === 'number', true),`); + } else { + if (typeof minimum === 'number' && maximum) { + w.writeLine(` ${sPropName}: ${isRequired ? `this._${sPropName} >= ${minimum} && this._${sPropName} <= ${maximum}` : `!this._${sPropName} ? true : ((this._${sPropName} >= ${minimum}) && (this._${sPropName} <= ${maximum}))`},`); + } + if (typeof minimum !== 'number' || !maximum) { + w.writeLine(` ${sPropName}: ${isRequired ? `typeof this._${sPropName} === 'number'` : `!this._${sPropName} ? true : typeof this._${sPropName} === 'number'`},`); + } + } + } else if (pType === 'boolean') { + w.writeLine(` ${sPropName}: ${isRequired ? `typeof this._${sPropName} === 'boolean'` : `!this._${sPropName} ? true : typeof this._${sPropName} === 'boolean'`},`); + } + } + }); + w.writeLine('};'); + w.writeLine('const isError: string[] = [];') + w.writeLine('Object.keys(validate).forEach((key) => {'); + w.writeLine(' if (!(validate as any)[key]) {'); + w.writeLine(' isError.push(key);'); + w.writeLine(' }'); + w.writeLine('});'); + w.writeLine('return isError;'); + + }); + + // add update method; + const update = entityClass.addMethod({ + isStatic: false, + name: 'update', + returnType: `${sName}`, + }); + update.addParameter({ + name: 'props', + type: `Partial`, + }); + update.setBodyText((w) => { w.writeLine(`return new ${sName}({ ...this.serialize(), ...props });`); }); + + this.entities.push(entityFile); + }; + + save = () => { + this.entities.forEach(async (e) => { + await e.saveSync(); + }); + }; +} + +export default EntitiesGenerator; diff --git a/client2/scripts/generator/src/utils.ts b/client2/scripts/generator/src/utils.ts new file mode 100644 index 00000000..3ab2ef26 --- /dev/null +++ b/client2/scripts/generator/src/utils.ts @@ -0,0 +1,74 @@ +const toCamel = (s: string) => { + return s.replace(/([-_][a-z])/ig, ($1) => { + return $1.toUpperCase() + .replace('-', '') + .replace('_', ''); + }); +}; +const capitalize = (s: string) => { + return s[0].toUpperCase() + s.slice(1); +}; +const TYPES = { + integer: 'number', + float: 'number', + number: 'number', + string: 'string', + boolean: 'boolean', +}; + +/** + * @param schemaProp: valueof shema.properties[key] + * @param openApi: openapi object + * @returns [propType - basicType or import one, isArray, isClass, isImport] + */ +const schemaParamParser = (schemaProp: any, openApi: any): [string, boolean, boolean, boolean, boolean] => { + let type = ''; + let isImport = false; + let isClass = false; + let isArray = false; + let isAdditional = false; + + if (schemaProp.$ref || schemaProp.additionalProperties?.$ref) { + const temp = (schemaProp.$ref || schemaProp.additionalProperties?.$ref).split('/'); + + if (schemaProp.additionalProperties) { + isAdditional = true; + } + + type = `${temp[temp.length - 1]}`; + + const cl = openApi ? openApi.components.schemas[temp[temp.length - 1]] : {}; + + if (cl.type === 'string' && cl.enum) { + isImport = true; + } + + if (cl.type === 'object' && !cl.oneOf) { + isClass = true; + isImport = true; + } else if (cl.type === 'array') { + const temp: any = schemaParamParser(cl.items, openApi); + type = `${temp[0]}`; + isArray = true; + isClass = isClass || temp[2]; + isImport = isImport || temp[3]; + } + } else if (schemaProp.type === 'array') { + const temp: any = schemaParamParser(schemaProp.items, openApi); + type = `${temp[0]}`; + isArray = true; + isClass = isClass || temp[2]; + isImport = isImport || temp[3]; + } else { + type = (TYPES as Record)[schemaProp.type]; + } + if (!type) { + // TODO: Fix bug with Error fields. + type = 'any'; + // throw new Error('Failed to find entity type'); + } + + return [type, isArray, isClass, isImport, isAdditional]; +}; + +export { TYPES, toCamel, capitalize, schemaParamParser }; diff --git a/client2/scripts/helpers/checkTranslations.ts b/client2/scripts/helpers/checkTranslations.ts new file mode 100644 index 00000000..6634f65b --- /dev/null +++ b/client2/scripts/helpers/checkTranslations.ts @@ -0,0 +1,226 @@ +import * as fs from 'fs'; +import { + Project, + VariableStatement, + SyntaxKind, + Node, + Statement, + ts, + Identifier, + SourceFile, +} from 'ts-morph'; +import { + LOCALE_FOLDER_PATH, + TRANSLATOR_CLASS_NAME, + USE_INTL_NAME, + trimQuotes, +} from '../consts'; +import { checkForms, AvailableLocales } from '../../src/localization/Translator'; + +const project = new Project({ + tsConfigFilePath: './tsconfig.json', +}); + +let lang = 'ru'; +let option = ''; + +if (process.argv.length > 2) { + lang = process.argv[2]; + option = process.argv[3]; +} + +const usedTranslations: string[] = []; +const usedPluralTranslations: string[] = []; + +const problemFiles: string[] = []; +const sourceFiles = project.getSourceFiles(); +const sourceFilesWithIntl = sourceFiles.filter((sf) => { + return !!sf.getImportDeclarations().find((id) => { + return !!id.getNamedImports().find((ni) => ni.getName() === USE_INTL_NAME) + }) +}); +const getFileUsedIntl = (statements: Statement[]) => { + statements.forEach((s) => { + if (s instanceof VariableStatement) { + s.forEachDescendant((node) => { + let intVariableDeclaration: Identifier = null; + switch (node.getKind()) { + case SyntaxKind.VariableDeclaration: + if (node.getSymbol()) { + const name = node.getSymbol().getName(); + const callExp = node.getChildren().find((n) => n.getKind() === SyntaxKind.CallExpression); + if (callExp) { + const callExpIden = callExp.getChildren().find(n => n.getKind() === SyntaxKind.Identifier); + if (callExpIden && callExpIden.getSymbol().getName() === USE_INTL_NAME) { + intVariableDeclaration = node as Identifier; + } + } + } + break; + default: + break; + } + if (intVariableDeclaration) { + intVariableDeclaration.findReferencesAsNodes().forEach((fr) => { + if (fr instanceof Node) { + const parent = fr.getParentIfKind(SyntaxKind.PropertyAccessExpression); + if (parent && (parent.getName() === 'getMessage' || parent.getName() === 'getPlural')) { + const syntaxList = parent.getNextSiblings().find((n) => n.getKind() === SyntaxKind.SyntaxList); + if (syntaxList) { + const id = syntaxList.getChildren()[0]; + if (id && id.getKind() !== SyntaxKind.StringLiteral) { + problemFiles.push(fr.getSourceFile().getFilePath()); + } + if (id) { + usedTranslations.push(trimQuotes(id.getText())); + if (parent.getName() === 'getPlural') { + usedPluralTranslations.push(trimQuotes(id.getText())); + } + } + } + } + } + }) + } + }); + } + }) +} + +const getFileUsedTranslations = (file: SourceFile) => { + const namedImport = file.getImportDeclarations().find((id) => !!id.getNamedImports().find((ni) => ni.getName() === TRANSLATOR_CLASS_NAME)); + if (namedImport) { + const identifier = namedImport.getImportClause().getNamedImports().find((iden) => iden.getName() === TRANSLATOR_CLASS_NAME); + const translateReferences = identifier.getNodeProperty('name').findReferencesAsNodes(); + if (translateReferences.length > 0) { + translateReferences.forEach((identifierNode) => { + if (identifierNode.getParentIfKind(SyntaxKind.TypeReference)) { + const translatorVariable = identifierNode.getParent().getPreviousSibling().getPreviousSiblingIfKind(SyntaxKind.Identifier); + if (translatorVariable) { + translatorVariable.findReferencesAsNodes().forEach((node) => { + const parent = node.getParentIfKind(SyntaxKind.PropertyAccessExpression); + if (parent && (parent.getName() === 'getMessage' || parent.getName() === 'getPlural')) { + + const syntaxList = parent.getNextSiblings().find((n) => n.getKind() === SyntaxKind.SyntaxList); + if (syntaxList) { + const id = syntaxList.getChildren()[0]; + if (id && id.getKind() !== SyntaxKind.StringLiteral) { + problemFiles.push(parent.getSourceFile().getFilePath()); + } + if (id) { + usedTranslations.push(trimQuotes(id.getText())); + if (parent.getName() === 'getPlural') { + usedPluralTranslations.push(trimQuotes(id.getText())); + } + } + } + } + }) + } + } + }) + } + + } +} +sourceFilesWithIntl.forEach((file) => { + getFileUsedIntl(file.getStatements()); +}) + +const sourceFilesWithTranslator = project.getSourceFiles().filter((sf) => { + return !!sf.getImportDeclarations().find((id) => { + return !!id.getNamedImports().find((ni) => ni.getName() === TRANSLATOR_CLASS_NAME) + }) +}); +sourceFilesWithTranslator.forEach((file) => { + getFileUsedTranslations(file); +}) +const filteredUsedTranslations = Array.from(new Set(usedTranslations)); +const filteredUsedPluralTranslations = Array.from(new Set(usedPluralTranslations)); + +if (problemFiles.length) { + console.warn(`\n============== Files where translation id provided not as string ==============\n`); + console.log(problemFiles.join('\n')); + process.exit(255); +} + +const allFiles = fs.readdirSync(LOCALE_FOLDER_PATH); +// Use ru or needed language +const translationFile = allFiles.find((file) => file.includes(`${lang}.json`)); + +if (!translationFile) { + console.error('File not found'); + process.exit(255); +} + +const translationsObject = JSON.parse(fs.readFileSync(`./src/lib/intl/__locales/${translationFile}`, { flag: 'r+' }) as unknown as string); +const translations = { + locale: translationFile, + messages: Object.keys(translationsObject), +}; + +const someMessagesNotFound: string[] = []; +const notUsed: string[] = []; +const notFound: string[] = []; +const checkLocaleMessages = (locale: string, messages: string[]) => { + filteredUsedTranslations.forEach(f => { + if (!messages.includes(f)) { + notFound.push(f); + } + }); + messages.forEach(t => { + if (!filteredUsedTranslations.includes(t)) { + notUsed.push(t); + } + }); + if (notFound.length > 0) { + someMessagesNotFound.push(locale); + } +} + +const render = (data: string[], title: string) => { + console.log(`============ ${title} ============`); + console.table(data); + console.log(`============ ${title} ============`); +} + +checkLocaleMessages(translations.locale, translations.messages); + +const checkPluralForm = () => { + const pluralFormWrong: string[] = []; + filteredUsedPluralTranslations.forEach((id) => { + const message = translationsObject[id]; + if (!checkForms(message, lang as AvailableLocales, id)) { + pluralFormWrong.push(id) + } + }); + return pluralFormWrong; +} + +const plural = checkPluralForm(); +if (!option && (someMessagesNotFound.length || plural.length > 0 )) { + someMessagesNotFound.forEach(locale => console.error(`\nSome translatins for ${locale} was not found!\n`)); + plural.forEach(id => console.error(`\nTranslation with id: "${id}" - have wrong number of plural forms!\n`)); + process.exit(255); +} +if (option) { + switch (option) { + case '--show-missing': { + render(notFound, 'NotFound') + break; + } + case '--show-unused': { + render(notUsed, 'notUsed') + break; + } + case '--check-plurals': { + render(plural, 'Wrong Plural Form') + } + default: { + if (someMessagesNotFound.length) { + someMessagesNotFound.forEach(locale => console.error(`\nSome translatins for ${locale} was not found!\n\n`)); + process.exit(255); + } + } + } +} diff --git a/client2/scripts/lint/common.js b/client2/scripts/lint/common.js new file mode 100644 index 00000000..759fa44f --- /dev/null +++ b/client2/scripts/lint/common.js @@ -0,0 +1,79 @@ +module.exports = { + parser: '@typescript-eslint/parser', + parserOptions: { + project: './tsconfig.json', + ecmaFeatures: { + jsx: true + }, + extraFileExtensions: ['mjs', 'tsx', 'ts'], + ecmaVersion: 2020, + sourceType: 'module' + }, + plugins: ['react', '@typescript-eslint', 'import'], + env: { + browser: true, + commonjs: true, + es6: true, + es2020: true, + jest: true, + }, + settings: { + react: { + pragma: 'React', + version: 'detect', + }, + 'import/resolver': { + typescript: { + alwaysTryTypes: true + } + }, + 'import/parsers': { + '@typescript-eslint/parser': ['.ts', '.tsx'], + }, + }, + rules: { + '@typescript-eslint/explicit-module-boundary-types': 0, + '@typescript-eslint/explicit-function-return-type': [0, { allowExpressions: true }], + '@typescript-eslint/indent': ['error', 4], + '@typescript-eslint/interface-name-prefix': [0, { prefixWithI: 'never' }], + '@typescript-eslint/no-explicit-any': [0], + '@typescript-eslint/naming-convention': [2, { + selector: 'enum', format: ['UPPER_CASE', 'PascalCase'], + }], + '@typescript-eslint/no-non-null-assertion': 0, + 'arrow-body-style': 'off', + 'consistent-return': 0, + curly: [2, 'all'], + 'default-case': 0, + 'import/no-cycle': 0, + 'import/prefer-default-export': 'off', + 'import/no-named-as-default': 0, + indent: [0, 4], + 'no-alert': 2, + 'no-console': 2, + 'no-debugger': 2, + 'no-underscore-dangle': 'off', + 'no-useless-escape': 'off', + 'object-curly-newline': 'off', + 'react-hooks/exhaustive-deps': 0, + 'react/display-name': 0, + 'react/jsx-indent-props': ['error', 4], + 'react/jsx-indent': ['error', 4], + 'react/jsx-one-expression-per-line': 'off', + 'react/jsx-props-no-spreading': 0, + 'react/prop-types': 'off', + 'react/state-in-constructor': 'off', + }, + extends: [ + 'airbnb-base', + 'airbnb-typescript/base', + 'airbnb/hooks', + 'plugin:react/recommended', + 'plugin:@typescript-eslint/eslint-recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:import/errors', + 'plugin:import/warnings', + 'plugin:import/typescript', + ], + globals: {}, +}; diff --git a/client2/scripts/lint/dev.js b/client2/scripts/lint/dev.js new file mode 100644 index 00000000..33784298 --- /dev/null +++ b/client2/scripts/lint/dev.js @@ -0,0 +1,10 @@ +module.exports = { + rules: { + 'no-alert': 0, + 'no-debugger': 0, + 'no-console': 0, + }, + extends: [ + './common', + ], +}; diff --git a/client2/scripts/lint/prod.js b/client2/scripts/lint/prod.js new file mode 100644 index 00000000..f31bae1f --- /dev/null +++ b/client2/scripts/lint/prod.js @@ -0,0 +1,5 @@ +module.exports = { + extends: [ + './common.js', + ], +}; diff --git a/client2/scripts/webpack/helpers.js b/client2/scripts/webpack/helpers.js new file mode 100644 index 00000000..2a72e0b6 --- /dev/null +++ b/client2/scripts/webpack/helpers.js @@ -0,0 +1,40 @@ +const yaml = require('yaml'); +const fs = require('fs'); + +const ZERO_HOST = '0.0.0.0'; +const LOCALHOST = '127.0.0.1'; +const DEFAULT_PORT = 80; + +const importConfig = () => { + try { + const doc = yaml.parse(fs.readFileSync('../AdguardHome.yaml', 'utf8')); + const { bind_host, bind_port } = doc; + return { + bind_host, + bind_port, + }; + } catch (e) { + return { + bind_host: ZERO_HOST, + bind_port: DEFAULT_PORT, + }; + } +}; + +const getDevServerConfig = () => { + const { bind_host: host, bind_port: port } = importConfig(); + const { DEV_SERVER_PORT } = process.env; + + const devServerHost = host === ZERO_HOST ? LOCALHOST : host; + const devServerPort = 3000 || port + 8000; + + return { + host: devServerHost, + port: devServerPort + }; +}; + +module.exports = { + importConfig, + getDevServerConfig +}; diff --git a/client2/scripts/webpack/webpack.config.base.js b/client2/scripts/webpack/webpack.config.base.js new file mode 100644 index 00000000..babb4f54 --- /dev/null +++ b/client2/scripts/webpack/webpack.config.base.js @@ -0,0 +1,74 @@ +const path = require('path'); +const AntdDayjsWebpackPlugin = require('antd-dayjs-webpack-plugin'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); +const tsconfig = require('../../tsconfig.json'); + +const RESOURCES_PATH = path.resolve(__dirname, '../../'); +const HTML_PATH = path.resolve(RESOURCES_PATH, 'public/index.html'); +const HTML_INSTALL_PATH = path.resolve(RESOURCES_PATH, 'public/install.html'); + +module.exports = { + entry: { + install: './src/Install.tsx', + main: './src/App.tsx' + }, + resolve: { + extensions: ['.tsx', '.ts', '.js', '.pcss'], + alias: Object.keys(tsconfig.compilerOptions.paths).reduce((aliases, key) => { + // Reduce to load aliases from ./tsconfig.json in appropriate for webpack form + const paths = tsconfig.compilerOptions.paths[key].map(p => p.replace('/*', '')); + aliases[key.replace('/*', '')] = path.resolve( + __dirname, + '../../', + tsconfig.compilerOptions.baseUrl, + ...paths, + ); + return aliases; + }, {}), + }, + module: { + rules: [ + { + test: /\.tsx?$/, + use: 'ts-loader', + exclude: /node_modules/, + }, + { + test: /\.(woff|woff2)$/, + use: [{ + loader: 'file-loader', + options:{ + outputPath:'./', + } + }], + }, + { + test:/\.(png|jpe?g|gif)$/, + exclude: /(node_modules)/, + use:[{ + loader:'file-loader', + options:{ + outputPath:'./images', + } + }] + } + ], + }, + + plugins: [ + // new AntdDayjsWebpackPlugin() + new HtmlWebpackPlugin({ + inject: true, + cache: false, + chunks: ['main'], + template: HTML_PATH, + }), + new HtmlWebpackPlugin({ + inject: true, + cache: false, + chunks: ['install'], + filename: 'install.html', + template: HTML_INSTALL_PATH, + }), + ], +}; diff --git a/client2/scripts/webpack/webpack.config.dev.js b/client2/scripts/webpack/webpack.config.dev.js new file mode 100644 index 00000000..3a12f12e --- /dev/null +++ b/client2/scripts/webpack/webpack.config.dev.js @@ -0,0 +1,113 @@ +const history = require('connect-history-api-fallback'); +const { merge } = require('webpack-merge'); +const path = require('path'); +const proxy = require('http-proxy-middleware'); +const Webpack = require('webpack'); + +const { getDevServerConfig } = require('./helpers'); +const baseConfig = require('./webpack.config.base'); + +const target = getDevServerConfig(); + +const options = { + target: `http://${target.host}:${target.port}`, // target host + changeOrigin: true, // needed for virtual hosted sites +}; +const apiProxy = proxy.createProxyMiddleware(options); + +module.exports = merge(baseConfig, { + mode: 'development', + output: { + path: path.resolve(__dirname, '../../build2'), + filename: '[name].bundle.js', + }, + optimization: { + noEmitOnErrors: true, + }, + devServer: { + port: 4000, + historyApiFallback: true, + before: (app) => { + app.use('/control', apiProxy); + app.use(history({ + rewrites: [ + { + from: /\.(png|jpe?g|gif)$/, + to: (context) => { + const name = context.parsedUrl.pathname.split('/'); + return `/images/${name[name.length - 1]}` + } + }, { + from: /\.(woff|woff2)$/, + to: (context) => { + const name = context.parsedUrl.pathname.split('/'); + return `/${name[name.length - 1]}` + } + }, { + from: /\.(js|css)$/, + to: (context) => { + const name = context.parsedUrl.pathname.split('/'); + return `/${name[name.length - 1]}` + } + } + ], + })); + } + }, + devtool: 'eval-source-map', + module: { + rules: [ + { + enforce: 'pre', + test: /\.tsx?$/, + exclude: /node_modules/, + loader: 'eslint-loader', + options: { + configFile: path.resolve(__dirname, '../lint/dev.js'), + } + }, + { + test: (resource) => { + return ( + resource.indexOf('.pcss')+1 + || resource.indexOf('.css')+1 + || resource.indexOf('.less')+1 + ) && !(resource.indexOf('.module.')+1); + }, + use: ['style-loader', 'css-loader', 'postcss-loader', { + loader: 'less-loader', + options: { + javascriptEnabled: true, + }, + }], + }, + { + test: /\.module\.p?css$/, + use: [ + 'style-loader', + { + loader: 'css-loader', + options: { + modules: true, + sourceMap: true, + importLoaders: 1, + modules: { + localIdentName: "[name]__[local]___[hash:base64:5]", + } + }, + }, + 'postcss-loader', + ], + exclude: /node_modules/, + }, + ] + }, + plugins: [ + new Webpack.DefinePlugin({ + DEV: true, + 'process.env.DEV_SERVER_PORT': JSON.stringify(3000), + }), + new Webpack.HotModuleReplacementPlugin(), + new Webpack.ProgressPlugin(), + ], +}); diff --git a/client2/scripts/webpack/webpack.config.prod.js b/client2/scripts/webpack/webpack.config.prod.js new file mode 100644 index 00000000..958c7e68 --- /dev/null +++ b/client2/scripts/webpack/webpack.config.prod.js @@ -0,0 +1,91 @@ +const path = require('path'); +const { merge } = require('webpack-merge'); +const baseConfig = require('./webpack.config.base'); +const MiniCssExtractPlugin = require("mini-css-extract-plugin"); +const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin'); +const TerserJSPlugin = require('terser-webpack-plugin'); +const Webpack = require('webpack'); +const CopyPlugin = require('copy-webpack-plugin'); + +module.exports = merge(baseConfig, { + mode: 'production', + devtool: 'source-map', + output: { + path: path.resolve(__dirname, '../../../build2/static'), + filename: '[name].bundle.[hash:5].js', + publicPath: '/' + }, + optimization: { + minimizer: [new TerserJSPlugin({terserOptions: { + output: { + comments: false, + }, + }, + extractComments: false, + }), new OptimizeCSSAssetsPlugin({})], + splitChunks: { + cacheGroups: { + styles: { + name: 'styles', + test: /\.css$/, + chunks: 'all', + enforce: true, + }, + }, + }, + }, + module: { + rules: [ + { + test: (resource) => { + return ( + resource.indexOf('.pcss')+1 + || resource.indexOf('.css')+1 + || resource.indexOf('.less')+1 + ) && !(resource.indexOf('.module.')+1); + }, + use: [{ + loader: MiniCssExtractPlugin.loader, + options: { + esModules: true, + } + }, 'css-loader', 'postcss-loader', { + loader: 'less-loader', + options: { + javascriptEnabled: true, + }, + }], + exclude: /node_modules/, + }, + { + test: /\.module\.p?css$/, + use: [ + { + loader: MiniCssExtractPlugin.loader, + options: { + esModules: true, + } + }, + { + loader: 'css-loader', + options: { + modules: true, + sourceMap: true, + importLoaders: 1, + }, + }, + 'postcss-loader', + ], + exclude: /node_modules/, + } + ] + }, + plugins: [ + new Webpack.DefinePlugin({ + DEV: false, + }), + new MiniCssExtractPlugin({ + filename: '[name].[hash:5].css', + }), + ] +}); diff --git a/client2/src/App.tsx b/client2/src/App.tsx new file mode 100644 index 00000000..f01e3253 --- /dev/null +++ b/client2/src/App.tsx @@ -0,0 +1,18 @@ +import './main.pcss'; +import './lib/ant/ant.less'; +import React from 'react'; +import ReactDOM from 'react-dom'; +import Store, { storeValue } from 'Store'; +import './lib/ant'; + +import App from './components/App'; + +const Container = () => { + return ( + + + + ); +}; + +ReactDOM.render(, document.getElementById('app')); diff --git a/client2/src/Install.tsx b/client2/src/Install.tsx new file mode 100644 index 00000000..ccc61315 --- /dev/null +++ b/client2/src/Install.tsx @@ -0,0 +1,18 @@ +import './main.pcss'; +import './lib/ant/ant.less'; +import React from 'react'; +import ReactDOM from 'react-dom'; +import Store, { storeValue } from 'Store/installStore'; +import './lib/ant'; + +import Install from './components/Install'; + +const Container = () => { + return ( + + + + ); +}; + +ReactDOM.render(, document.getElementById('app')); diff --git a/client2/src/assets/img/background_min.png b/client2/src/assets/img/background_min.png new file mode 100644 index 0000000000000000000000000000000000000000..528fe2d19087348caa6a65a5448308bd81277e49 GIT binary patch literal 7187 zcmb_hXH-*7x1JCJ5>OO~L_yG(n$UeIqEe&@p@)Pj5D~wId)K$_{5UhS_nv1z`^=ej_C6=X$l&Hl_<1-0;G~YW z1{MG&8~_ZZS#+F*N*$3Zii76KkZ3X`u z$3{aXPZL1(R_~0dD1(4359eECVK9+TYsJ?yfdqVXmE}nU=v-W=LT4;OL8rwGCAr1L zLRZKPGLi1^)jc#X>W{+-;Po;Soo6Bq#?ozH>T82BhkLpP5U>*A<`M%)m9d9B6!9KG z56DF9p)F!EQ%qiLW*As3}KQGXqFuT;z%GLDiq8n$!I#EW7FBNm`hF?*uc$N zUdROut(atlm$QN^l7NH!Dd*Rx~%|r*e z@djqFA9+f$Bd%ieDfhw7kb5%HZYk0I)wR6K;BvLk;J!INM#NAZ_7|RFw2MJ7vR*Vk z6Zdf-_dMS)HCR%63Z94_+=ptL{PS}^Pj9t+?ZfQ;&NEemdGAnjh{n0g)A?=~xv(tN z)OuPtYRppgwJ_JYi&kmt;a<-ns9OXc?tgg1Y#S`rT{`8J$Yh)a=&&@ z7YjQbtQY8YWRD?!m56_fCSA0Le{$bNSI#6k>mXF&+=K5JevFKwgdC1dZ+A^y|0K4a zEf-$qla3sLnU-$Z!_)7tx5c%J|3On1HkG@;(5*<+A90v8!NIqyh3iGu^5tpo+=m;C z=9-$Bs>(6zCP`a(a|?KWie0IIyz2Z7Vv^W=AGG6D+y}^;R0SilK{;fdWISI58?hWLL1KxfE40O1Np2b)-Nh3ydKr$ z{q^Nlf~iswlWrz~6RuQ5%zV6Nhq)_m%-_K7U45BGpkUNs<|KFc7?y0> z=%*0M=_;(a)kw-}#0la&OW$*xKoD5eU@OjSh}!Eo z(Ex}_>VA-dRyHW{ms=qA-1(bkv{w0^)?)$z`>jh-J=V=3@66R;pvcQ$dQg961}E;0 z2Bc=rjUURmT_luq1CVBMHJfX(2{<2DVIlg{by)I*`I^f|RU|2dMV=8Fy~(w4FkU(u zjjg%#$TFjkuVIA)#WCBx&leszTghcRro2o<)mGL#Kru}utxYJAcbvu0_meNrF2<20 zI_DwT%Esf%Uu-?O@xS*E3il@!u3{k*l%D8-rRmD@>|46E=mEE#4Xqdf1mG(}=Nwo+ zEfkIMKZ-1Lk9rDuIDXo6w!*2?p#W_H1(hvDmcwye!`E-VYy{aA=u7zfm=ljWbhaw>FD@o$VsJy`l zc?c&1p0^Z*bTr93#P5d+eWrqJ965gIXW6HsLR4M@IS}$No7Hz#$Qd!s`_WiGA`oI! zh>l09v++MxFXh%PVPqp&`DgREDm&>u0273g?c$!^e0u`u4eP2vU6uy9!4z61ZyZ*h znTc9JydDI#9>0FMv-zhTIJM5d(4MmxpBroS@Bb zLD^;A86-%-L%(~ovZxAV#6h7{IuR>-ek9Q6F-JpytTd<)?!4xD334yJx1NcKRQU!x zFiIHz5(xRGokORa5(p{M9MwJfz+wyv_U{qot`imc!VM50PPV9L$H;;K?8iafO&}8i zJcDj-#*kzDn2K&HAY=qWRC$`>>6lDR)U$p5<(&NBBpYcd#GN2Bt9(mTW?BYNeV~h!!(@t$Zc9QI z0AjmhC-XnaQv7Z9=#%zj#UfO5kyE=hATiHUgJpxQYV1$3ky_6WMzuV&`T_x_WZ3|@ zqMXeiNwK4NP<4CQU%rU9$-jF+n#8`j?A+>nI`re}zZilC3AVk8+RncZU59kyi9Pmy z+34o5^?;a7sCco0@3RbxHq#cTpsCnrjNpsAdXGrKnM43$5zjj@selP=2gq!fUt9B@ zWd+h*6E$((&jl}ZwoX2(!}xHOEDLq03Ic-LrXVUYeC2(K zty3;r7ALzG1Y}yR*oK?gw&p$KLl*jG^@MEI((|aKMYGvjqoKt>g__x#zW{Ji>m8@9 zWp!-7<@0LchooR~LK8&{-@C=0Bu8Is21aS`d7P?}uNfcK1hprhoX=( zK@um~sOFGrY-q~|7C3c%ddS#d+CSMR^)f#F*cZVK_Ar3t8{ab&o{ectdcGQ95?-*F zT>HAy^eoh;1q)KHzJIKbnSSZ=#bv;WQ)ce7*C7J^PEG_GI)U-^kyLMWF5_K)OB0F*4zR{h=FbC9%mh&>R%=O#Ag5?J`2^P>re(3 zcZ%fwkyr>Rw`$BwzoSWVZaTmFd~}{IFp7yB{k1|_JYR3QoEGgJN|$i=c%>1mYiS+c zF=gov>Z}0Q0{g0C{bcwr+>~{HopvTrb&i(zld~S!7$(`ag|#``foy|r29xv7sY_P+ zf9&A^CxVcWsjoQmOvj}0LCPi!^jVxiR}5%HvMgy3!)iYPqxW@x;S%II3;xWn#XpLs zaRVGN`zkK|LBg8xN=ap>$qobQs{hgEQtgbKRy?MFrM|oc$>Zbb70I{#VeG=B{3)n0 zxIjdOMv7vBF5C%}a62-g1aj%z7BP6E#x%A#SS)b&@HF)1#_r03oN}?jpEExC8Ej41 z*AF>?G78rqipLTmpwT!dY9yTAN93&Qa@v8_l$<@zEwU{A9EHc#RyktsmooB^TFDhH z7rX~60I4-Y#$|Djq7MP%(yt#i#qCjc&oNLHUU}4zyW{s6CYUR{L&7nia|B)CKt-mk zu7Q8kaxde8tM$q>2e=;SasNAq>!yzf)#7Kz{5oCo4AteVr}(70zp8L0RI&&yyUF2fT8sB#&eN%UPE!flop_yXymn@i#{ z-}-;svVyzcoJ5!%LvfJUHsh`eq~q60IS@-#20!=JtMqq$ zVqcq>1EOT3+qV1^nhR|KIgC%rDwXaOQ!i>ZEBR(I9=$Qt*B-TopfWTy3fjOUW%b zhX#T}p7gX{aL*+hNIT*vedBJB-trYXgo#$O5kyo%0ljnrEDTp_mq|iYt#gbm*bpR| zmi`6=Xdhcpfx=KrZ%d8#7wGCg~QUmZ44sr2N?H7Q?E^VqI&>6c1ptQvpB zgpFf};L%`hOh(o1i(wxNbUPwz{&})`=z9LHc4lxarby<_=LA>X;^|&jR#s|w#D)!j zK(L2hHlV%;ZRlwo;2z~O{#+ie#z19{#P7wUYsJZma4_*c^sqS{DldZr1Xa!QrdeBG z4WY|8Fws41krsJhL}Um|Fk5J4JU`+tqZ&a-5RcvMJi4Bsk-6efu(nz>)lQe5kqGCq zg3HC_cU$A=oFBfh<4+K+K&oA~cwd?=NWYD|sS}ZLad2 z+?g7A7XoP+r>YoyzTmZrQqpO zW$iZU97qYLlpXswseSCQsn7Sm_pf*a-CI~n)eh0q;%rT5Xfh*^{^#e+RexVIjB|-T z>N5W!`5d5T$tesBA*u&%;zD<#uCq4+g7{0y<1^%+G%`1!nwK1PQXYhc>NYAuK$!4e zPN&ZjhTZt{eDL>q2tgx;qG49^B2-tQE|-~fW5}bht-&d}&(EjX3IULb897=KCCOQJ z#GF+u3>bXCRz03>%SfGXA)W!$Hu2z5N+r9nSp9W+J=`XVl3Iii9!9GTW{CCm3s3K8 zir0r&2-Z%u(Szu;JJHQoiF48MCi~a4xpm)qJkkdPO{Iml>bwt}v19i2tU5hYpW%S5 zaE`l{vCQs3^F19wD^g__mC!OWU$Z~)+64S>5-$v&h#>w z47LoVON1?d`3HjmHzUOTkJujRyXnUX2z`EnOGLt;q#HP;Dc7lXp9q?u2OkAcMRS+U zK9d!(Q;uF$3;;@We4+3vN*FQugC1Ynn49Ef;t67(HZfEN&IF(#6#>=Q!rqj)qoMD) z5j2fETx>v#!#P=M7SlH*+2vW|lr9^Qqy)mIW5wJu=kolOK=h8mg zqwX~Ir|xeQHp278P-z4p9s4^@tl;StcJc!^1yn48zMJWZFZG7i9L`KX#om@v25ec| zof?93wJ0g!>b&fhQ+EJTpF^i+bwsebFq@Oz3=pmyu^`CFx1Y;^{kxjn(L6DGd3s1F zl@Kalt=M*UVRq|{j21nIn8SN%WXqzentR^7lW}JQiRj^9@@(YHSZ;5`exh*!Pz_I& z->Z`zGLm7}(KulIdKSzX(PrLoYsw>>;R{)M031{!(btA7WshDoz`((~tP*J_l zuE%TBVU$8QqgZ)Ztt>&xHJw6(MW;76LuPIon%{%~$q>rCPyI#qx1lw)uw4}R7PRZ} zY%KITmIJ8r|LcCF6xZ%KKcXDMUC+zXQ@C&mw8*KYs43wLtbQ$ZKMwLTC<{wOF0Gs8v32i%@*Pygsex^%c6KN zm^Z*XZ>C273iQrceqY@_hk?pm+%G4;|G>M2TLd!rb7fp08V&qz#d8qf8rOe-Q3EW$ zPVSS@fVWuQq4w_a3$77mlH_i|4GgEPGh=f>k-Dv0I;#l+cp|II%52}F!IV&z7=6BJ z^W?f#9t$a?+0yr_UIZF&-WR%g)j%o}4N7bB^%afgSxBgjxXIPuIzaE<)G)0()w~}j z{k~&3D$OH@K|0ZNnaqWS5#$#mdlK`_uAhonMxCWw zKi}TrE#_W@4El_&dEG9QrZToD%)ZdagY6*m7~c6aTJ#tSm!2=arn?^zACRM^g*CHo zgTlN~zt@8{w?V^cZ;NQY>dkedGz@}d)-z_{=&EpX?ifHTK@#IrCX5F*ddk+X*6%gr zf&zYx2D%JOa|IiJ54HBCbnG=Q4Z94fp3j5lv_`tyKv=f7jU|huI(pF0!*vkg@aOtx zb_AU-ewyeJDyL^UieMTf@0aBkr;mNavB78 z%AA|p5XSqb`J0T4Sq&aWm~~2u7Aw$3kVY4azkDW(KLX{ zC}xTPF4#%(tu;nJ;L<_9^3jagaRTtR?bXmep38%StpYItzaZmuIQ7S^{A26oPBO>M z6Id8YqSw5_)-3goHDK=Z8Qwi3dFPQCa-zuTmR%)%vtLE`L@#0_u&{}5PU1PiuLSep z)DH*dW+MnDLX$@ZJyt%HSw0%0sH{rqB8|Fr8HTw;7Mp*jq8{+AX z556O?WPIDz4!J*4peyP@-=O_j79LCTKnC+pn)MGN#4F0JRxS7U^1Aw>C>kW8hVFK6 zH09h*{asRW7&Cge#RCSCT69Y%l%;N`ZFacrHdjp67=SLWv*}_X0gY05G|v1(hE;nT zd9n!JyFz+zM-rjboXtV)ArB)A^M0G7nnitOcFVk&jZK|r3%g>ULcCZ83i&D8c)m8P zosT{y(KY!Cbuy+Z^K76l_;lV3ZSjjj8CtV4CTiND`D)*YQ0b)Afym#PjH?*QN->U; ze3vv7J2KUxB31glA1U5E9Z^mE1$HQvhh=SVqp9I9JnFY~y(4!x&$J_6sJ~V^3Eu?9UR7qzW18P# zHLd+8Xi`p?>Rj!-xOeIdUWRLnA#WopEp+n3?S~SIMH4kpR?(hNFI!t0Sv42?*G6}X z_(fJm_lZO&PuZ7X%<4to%^Eu*9-iwXb-!M4^F@iO>^0BIe_{*NKtJ{SomyI0le8X_ zS*tCD^4+J$_iuxlxQj0Z@8m`ceFFVcZzy^PV}yqxd5+7rMk=rGo`=7}yj#MR3u7Qf z=o@jFD>-P`p)>2uEB&;0yPx$wd`YL~ zIDKymGk{Ug8{?Kgi2TiV_q@9I^7IsmC_qQq6L_6e(C4QO1fqV;OHY!{a-KUJnv42j-Y1f~;!of2h6ysj8Cpyi@!%wd)=5W7~M?#F~ ze{r6OqA6r(i!C7Wuex2hp#&x>QfbGE#?w=^b!7L!vy4Er=(oPm?88lNkn)K$2LL{W zBbt`Mqa;Bv@#JM_?M{;AhLm6a0{Z9E^0kzTM&k4$NK2 z?@~;>-p2)eJgHAh!uUusUoq*=zGZ@M8GY(y9C#f_ipj9mEA4>4aw8d38;|zW%N>Xd zEC4)Y|055fBH6z;(W|i0FG<@Bgf}&Y^pZ@0lM{p)_p^YOhidEqjC3x<@sW&ajPxac z=iT=Kl-vlcPk$Dj*?r5Xm|y;OKr*MgPUs05tmnD!*dMTxTnc!nSio+&n>~b$q^ygw zM1YnTZg*j9B+2T89b*{Dc60e6Oclw*a8cWm?cgXt$!p!zY*qSCh>%YFgB;osgK9Qd z{o7i>77x4(v%zV1uLGEBd#=c0BN(LB*aAPZT)iMTs0|K*1DU3)I>4?Ioy8A1F)ybH z2a2Oi>&K<#|D~DvKD>Brcd>2UCkTT6zbyG5HWMcdvstjE=gUTqRXUmm8Wc6czy1pd Co2}LW literal 0 HcmV?d00001 diff --git a/client2/src/components/App.tsx b/client2/src/components/App.tsx new file mode 100644 index 00000000..f27d5c17 --- /dev/null +++ b/client2/src/components/App.tsx @@ -0,0 +1,16 @@ +import { observer } from 'mobx-react-lite'; +import React, { FC, useContext } from 'react'; +import Store from 'Store'; +import Icons from 'Lib/theme/Icons'; + +const App: FC = observer(() => { + const store = useContext(Store); + return ( +
+ {store.ui.currentLang} + +
+ ); +}); + +export default App; diff --git a/client2/src/components/Install/Install.module.pcss b/client2/src/components/Install/Install.module.pcss new file mode 100644 index 00000000..8beec8bc --- /dev/null +++ b/client2/src/components/Install/Install.module.pcss @@ -0,0 +1,14 @@ +.layout { + background-image: url('../../assets/img/background_min.png'); + background-repeat: no-repeat; + min-height: 100vh; + background-color: #f1f3f7; +} +.container { + display: flex; + justify-content: center; + padding: 40px; +} +.content { + max-width: 404px; +} diff --git a/client2/src/components/Install/Install.tsx b/client2/src/components/Install/Install.tsx new file mode 100644 index 00000000..ce5dda59 --- /dev/null +++ b/client2/src/components/Install/Install.tsx @@ -0,0 +1,122 @@ +import React, { FC } from 'react'; +import { Layout } from 'antd'; +import { Formik, FormikHelpers } from 'formik'; +import { observer } from 'mobx-react-lite'; + +import { IInitialConfigurationBeta } from 'Entities/InitialConfigurationBeta'; +import Icons from 'Lib/theme/Icons'; +import { + DEFAULT_DNS_ADDRESS, + DEFAULT_DNS_PORT, + DEFAULT_IP_ADDRESS, + DEFAULT_IP_PORT, +} from 'Consts/install'; +import { notifyError } from 'Common/ui'; +import InstallStore from 'Store/stores/Install'; + +import AdminInterface from './components/AdminInterface'; +import Auth from './components/Auth'; +import DnsServer from './components/DnsServer'; +import Stepper from './components/Stepper'; +import Welcome from './components/Welcome'; +import ConfigureDevices from './components/ConfigureDevices'; + +import s from './Install.module.pcss'; + +const { Content } = Layout; + +export type FormValues = IInitialConfigurationBeta & { step: number }; + +const InstallForm: FC = observer(() => { + const initialValues: FormValues = { + step: 0, + web: { + ip: [DEFAULT_IP_ADDRESS], + port: DEFAULT_IP_PORT, + }, + dns: { + ip: [DEFAULT_DNS_ADDRESS], + port: DEFAULT_DNS_PORT, + }, + password: '', + username: '', + }; + + const onNext = async (values: FormValues, { setFieldValue }: FormikHelpers) => { + const currentStep = values.step; + const checker = (condition: boolean, message: string) => { + if (condition) { + setFieldValue('step', currentStep + 1); + } else { + notifyError(message); + } + }; + switch (currentStep) { + case 1: { + // web + const check = await InstallStore.checkConfig(values); + checker(check?.web?.status === '', check?.web?.status || ''); + break; + } + case 3: { + // dns + const check = await InstallStore.checkConfig(values); + checker(check?.dns?.status === '', check?.dns?.status || ''); + break; + } + case 4: { + // configure + const config = await InstallStore.configure(values); + if (config) { + const { web } = values; + window.location.href = `http://${web.ip[0]}:${web.port}`; + } + break; + } + default: + setFieldValue('step', currentStep + 1); + break; + } + }; + + return ( + + {({ values, handleSubmit, setFieldValue }) => ( +
+ + {values.step === 0 && ( + setFieldValue('step', 1)}/> + )} + {values.step === 1 && ( + + )} + {values.step === 2 && ( + + )} + {values.step === 3 && ( + + )} + {values.step === 4 && ( + + )} + + )} +
+ ); +}); + +const Install: FC = () => { + return ( + + + + + + + ); +}; + +export default Install; diff --git a/client2/src/components/Install/components/AdminInterface/AdminInterface.module.pcss b/client2/src/components/Install/components/AdminInterface/AdminInterface.module.pcss new file mode 100644 index 00000000..815f2470 --- /dev/null +++ b/client2/src/components/Install/components/AdminInterface/AdminInterface.module.pcss @@ -0,0 +1,17 @@ +.manualOptions { + margin-bottom: 48px; +} + +.name { + padding-bottom: 5px; + border-bottom: 1px solid var(--gray300); + margin-bottom: 16px; + margin-top: 20px; +} + +.manualOption { + display: flex; + justify-content: space-between; + align-items: baseline; + +} \ No newline at end of file diff --git a/client2/src/components/Install/components/AdminInterface/AdminInterface.tsx b/client2/src/components/Install/components/AdminInterface/AdminInterface.tsx new file mode 100644 index 00000000..711427a9 --- /dev/null +++ b/client2/src/components/Install/components/AdminInterface/AdminInterface.tsx @@ -0,0 +1,145 @@ +import React, { FC, useContext } from 'react'; +import cn from 'classnames'; +import { observer } from 'mobx-react-lite'; +import { FormikHelpers } from 'formik'; + +import { Input, Radio, Switch } from 'Common/controls'; +import { DEFAULT_IP_ADDRESS } from 'Consts/install'; +import { chechNetworkType, NETWORK_TYPE } from 'Helpers/installHelpers'; +import theme from 'Lib/theme'; +import Store from 'Store/installStore'; + +import s from './AdminInterface.module.pcss'; +import { FormValues } from '../../Install'; +import StepButtons from '../StepButtons'; + +enum NETWORK_OPTIONS { + ALL = 'all', + CUSTOM = 'custom', +} + +interface AdminInterfaceProps { + values: FormValues; + setFieldValue: FormikHelpers['setFieldValue']; +} + +const AdminInterface: FC = observer(({ + values, + setFieldValue, +}) => { + const { ui: { intl }, install: { addresses } } = useContext(Store); + const { web: { ip } } = values; + const radioValue = ip.length === 1 && ip[0] === DEFAULT_IP_ADDRESS + ? NETWORK_OPTIONS.ALL : NETWORK_OPTIONS.CUSTOM; + + const onSelectRadio = (v: string | number) => { + const value = v === NETWORK_OPTIONS.ALL + ? [DEFAULT_IP_ADDRESS] : []; + setFieldValue('web.ip', value); + }; + + const getManualBlock = () => ( +
+ {addresses?.interfaces.map((a) => { + let name = ''; + const type = chechNetworkType(a.name); + switch (type) { + case NETWORK_TYPE.ETHERNET: + name = `${intl.getMessage('ethernet')} (${a.name}) `; + break; + case NETWORK_TYPE.LOCAL: + name = `${intl.getMessage('localhost')} (${a.name}) `; + break; + default: + name = a.name || ''; + break; + } + return ( +
+
+
+ {name} +
+ {a.ipAddresses?.map((addrIp) => ( +
+
+ http://{addrIp} +
+ { + const temp = new Set(ip); + if (temp.has(addrIp)) { + temp.delete(addrIp); + } else { + temp.add(addrIp); + } + setFieldValue('web.ip', Array.from(temp.values())); + }}/> +
+ ))} +
+
+ ); + })} +
+ ); + + return ( +
+
+ {intl.getMessage('install_admin_interface_title')} +
+
+ {intl.getMessage('install_admin_interface_title_decs')} +
+
+ {intl.getMessage('install_admin_interface_where_interface')} +
+
+ {intl.getMessage('install_admin_interface_where_interface_desc')} +
+ + { radioValue !== NETWORK_OPTIONS.ALL && getManualBlock()} +
+ {intl.getMessage('install_admin_interface_port')} +
+
+ {intl.getMessage('install_admin_interface_port_desc')} +
+ { + const port = v === '' ? '' : parseInt(v, 10); + setFieldValue('web.port', port); + }} + /> + +
+ ); +}); + +export default AdminInterface; diff --git a/client2/src/components/Install/components/AdminInterface/index.ts b/client2/src/components/Install/components/AdminInterface/index.ts new file mode 100644 index 00000000..e0f7b851 --- /dev/null +++ b/client2/src/components/Install/components/AdminInterface/index.ts @@ -0,0 +1 @@ +export { default } from './AdminInterface'; diff --git a/client2/src/components/Install/components/Auth/Auth.tsx b/client2/src/components/Install/components/Auth/Auth.tsx new file mode 100644 index 00000000..a0f5797f --- /dev/null +++ b/client2/src/components/Install/components/Auth/Auth.tsx @@ -0,0 +1,55 @@ +import React, { FC, useContext } from 'react'; +import cn from 'classnames'; +import { observer } from 'mobx-react-lite'; +import { FormikHelpers } from 'formik'; + +import { Input } from 'Common/controls'; +import theme from 'Lib/theme'; +import Store from 'Store/installStore'; + +import StepButtons from '../StepButtons'; +import { FormValues } from '../../Install'; + +interface AuthProps { + values: FormValues; + setFieldValue: FormikHelpers['setFieldValue']; +} + +const Auth: FC = observer(({ + values, + setFieldValue, +}) => { + const { ui: { intl } } = useContext(Store); + + return ( +
+
+ {intl.getMessage('install_auth_title')} +
+
+ {intl.getMessage('install_auth_description')} +
+ setFieldValue('username', v)} + /> + setFieldValue('password', v)} + /> + +
+ ); +}); + +export default Auth; diff --git a/client2/src/components/Install/components/Auth/index.ts b/client2/src/components/Install/components/Auth/index.ts new file mode 100644 index 00000000..b1dea29b --- /dev/null +++ b/client2/src/components/Install/components/Auth/index.ts @@ -0,0 +1 @@ +export { default } from './Auth'; diff --git a/client2/src/components/Install/components/ConfigureDevices/ConfigureDevices.module.pcss b/client2/src/components/Install/components/ConfigureDevices/ConfigureDevices.module.pcss new file mode 100644 index 00000000..a3b2e199 --- /dev/null +++ b/client2/src/components/Install/components/ConfigureDevices/ConfigureDevices.module.pcss @@ -0,0 +1,4 @@ +.tabs { + width: 505px; + margin-left: -131px; +} \ No newline at end of file diff --git a/client2/src/components/Install/components/ConfigureDevices/ConfigureDevices.tsx b/client2/src/components/Install/components/ConfigureDevices/ConfigureDevices.tsx new file mode 100644 index 00000000..57c49cd4 --- /dev/null +++ b/client2/src/components/Install/components/ConfigureDevices/ConfigureDevices.tsx @@ -0,0 +1,128 @@ +import React, { FC, useContext } from 'react'; +import { Tabs } from 'antd'; +import cn from 'classnames'; +import { FormikHelpers } from 'formik'; + +import Store from 'Store/installStore'; +import theme from 'Lib/theme'; +import { danger, p } from 'Common/formating'; +import { DEFAULT_DNS_PORT, DEFAULT_IP_ADDRESS, DEFAULT_IP_PORT } from 'Consts/install'; + +import { FormValues } from '../../Install'; +import StepButtons from '../StepButtons'; +import s from './ConfigureDevices.module.pcss'; + +const { TabPane } = Tabs; + +interface ConfigureDevicesProps { + values: FormValues; + setFieldValue: FormikHelpers['setFieldValue']; +} + +const ConfigureDevices: FC = ({ + values, setFieldValue, +}) => { + const { ui: { intl }, install: { addresses } } = useContext(Store); + + const dhcp = (e: string) => ( + // TODO: link to dhcp + {e} + ); + + const allIps = addresses?.interfaces.reduce((all, data) => { + const { ipAddresses } = data; + if (ipAddresses) { + all.push(...ipAddresses); + } + return all; + }, [] as string[]); + + const { web: { ip: webIp }, dns: { ip: dnsIp } } = values; + const selectedWebIps = webIp.length === 1 && webIp[0] === DEFAULT_IP_ADDRESS + ? allIps : webIp; + const selectedDnsIps = dnsIp.length === 1 && dnsIp[0] === DEFAULT_IP_ADDRESS + ? allIps : dnsIp; + + return ( +
+
+ {intl.getMessage('install_configure_title')} +
+
+ {intl.getMessage('install_configure_danger_notice', { danger })} +
+
+ {intl.getMessage('install_configure_how_to_title')} +
+ + +
+ {intl.getMessage('install_configure_router', { p })} +
+
+ +
+ {intl.getMessage('install_configure_windows', { p })} +
+
+ +
+ {intl.getMessage('install_configure_macos', { p })} +
+
+ +
+ {/* TODO: add linux setup */} + {intl.getMessage('install_configure_router', { p })} +
+
+ +
+ {intl.getMessage('install_configure_android', { p })} +
+
+ +
+ {intl.getMessage('install_configure_ios', { p })} +
+
+
+ +
+ {intl.getMessage('install_configure_adresses')} +
+
+

+ {intl.getMessage('install_admin_interface_title')} +

+

+ {selectedWebIps?.map((ip) => ( +

+ {ip}{values.web.port !== DEFAULT_IP_PORT && `:${values.web.port}`} +
+ ))} +

+

+ {intl.getMessage('install_dns_server_title')} +

+
+ {selectedDnsIps?.map((ip) => ( +
+ {ip}{values.dns.port !== DEFAULT_DNS_PORT && `:${values.dns.port}`} +
+ ))} +
+
+
+ {intl.getMessage('install_configure_dhcp', { dhcp })} +
+ +
+ ); +}; + +export default ConfigureDevices; diff --git a/client2/src/components/Install/components/ConfigureDevices/index.ts b/client2/src/components/Install/components/ConfigureDevices/index.ts new file mode 100644 index 00000000..928cdfa0 --- /dev/null +++ b/client2/src/components/Install/components/ConfigureDevices/index.ts @@ -0,0 +1 @@ +export { default } from './ConfigureDevices'; diff --git a/client2/src/components/Install/components/DnsServer/DnsServer.module.pcss b/client2/src/components/Install/components/DnsServer/DnsServer.module.pcss new file mode 100644 index 00000000..5a90e4d5 --- /dev/null +++ b/client2/src/components/Install/components/DnsServer/DnsServer.module.pcss @@ -0,0 +1,12 @@ +.manualOptions { + margin-bottom: 48px; +} + +.manualOption { + display: flex; + justify-content: space-between; + align-items: baseline; + padding-bottom: 16px; + border-bottom: 1px solid var(--gray300); + margin-bottom: 16px; +} \ No newline at end of file diff --git a/client2/src/components/Install/components/DnsServer/DnsServer.tsx b/client2/src/components/Install/components/DnsServer/DnsServer.tsx new file mode 100644 index 00000000..d3e73494 --- /dev/null +++ b/client2/src/components/Install/components/DnsServer/DnsServer.tsx @@ -0,0 +1,145 @@ +import React, { FC, useContext } from 'react'; +import cn from 'classnames'; +import { observer } from 'mobx-react-lite'; +import { FormikHelpers } from 'formik'; + +import { Input, Radio, Switch } from 'Common/controls'; +import { DEFAULT_IP_ADDRESS } from 'Consts/install'; +import { chechNetworkType, NETWORK_TYPE } from 'Helpers/installHelpers'; +import theme from 'Lib/theme'; +import Store from 'Store/installStore'; + +import s from './DnsServer.module.pcss'; +import { FormValues } from '../../Install'; +import StepButtons from '../StepButtons'; + +enum NETWORK_OPTIONS { + ALL = 'all', + CUSTOM = 'custom', +} + +interface DnsServerProps { + values: FormValues; + setFieldValue: FormikHelpers['setFieldValue']; +} + +const DnsServer: FC = observer(({ + values, + setFieldValue, +}) => { + const { ui: { intl }, install: { addresses } } = useContext(Store); + const { dns: { ip } } = values; + const radioValue = ip.length === 1 && ip[0] === DEFAULT_IP_ADDRESS + ? NETWORK_OPTIONS.ALL : NETWORK_OPTIONS.CUSTOM; + + const onSelectRadio = (v: string | number) => { + const value = v === NETWORK_OPTIONS.ALL + ? [DEFAULT_IP_ADDRESS] : []; + setFieldValue('dns.ip', value); + }; + + const getManualBlock = () => ( +
+ {addresses?.interfaces.map((a) => { + let name = ''; + const type = chechNetworkType(a.name); + switch (type) { + case NETWORK_TYPE.ETHERNET: + name = `${intl.getMessage('ethernet')} (${a.name}) `; + break; + case NETWORK_TYPE.LOCAL: + name = `${intl.getMessage('localhost')} (${a.name}) `; + break; + default: + name = a.name || ''; + break; + } + return ( +
+
+
+ {name} +
+ {a.ipAddresses?.map((addrIp) => ( +
+
+ {addrIp} +
+ { + const temp = new Set(ip); + if (temp.has(addrIp)) { + temp.delete(addrIp); + } else { + temp.add(addrIp); + } + setFieldValue('dns.ip', Array.from(temp.values())); + }}/> +
+ ))} +
+
+ ); + })} +
+ ); + + return ( +
+
+ {intl.getMessage('install_dns_server_title')} +
+
+ {intl.getMessage('install_dns_server_desc')} +
+
+ {intl.getMessage('install_dns_server_network_interfaces')} +
+
+ {intl.getMessage('install_dns_server_network_interfaces_desc')} +
+ + { radioValue !== NETWORK_OPTIONS.ALL && getManualBlock()} +
+ {intl.getMessage('install_dns_server_port')} +
+
+ {intl.getMessage('install_dns_server_port_desc')} +
+ { + const port = v === '' ? '' : parseInt(v, 10); + setFieldValue('dns.port', port); + }} + /> + +
+ ); +}); + +export default DnsServer; diff --git a/client2/src/components/Install/components/DnsServer/index.ts b/client2/src/components/Install/components/DnsServer/index.ts new file mode 100644 index 00000000..95d67c54 --- /dev/null +++ b/client2/src/components/Install/components/DnsServer/index.ts @@ -0,0 +1 @@ +export { default } from './DnsServer'; diff --git a/client2/src/components/Install/components/StepButtons/StepButtons.module.pcss b/client2/src/components/Install/components/StepButtons/StepButtons.module.pcss new file mode 100644 index 00000000..f443fd2b --- /dev/null +++ b/client2/src/components/Install/components/StepButtons/StepButtons.module.pcss @@ -0,0 +1,8 @@ +.button { + margin-top: 48px; + width: 190px; + + &.inGroup { + margin-right: 24px; + } +} \ No newline at end of file diff --git a/client2/src/components/Install/components/StepButtons/StepButtons.tsx b/client2/src/components/Install/components/StepButtons/StepButtons.tsx new file mode 100644 index 00000000..ada548a6 --- /dev/null +++ b/client2/src/components/Install/components/StepButtons/StepButtons.tsx @@ -0,0 +1,45 @@ +import React, { FC, useContext } from 'react'; +import { Button } from 'antd'; +import cn from 'classnames'; +import { observer } from 'mobx-react-lite'; +import { FormikHelpers } from 'formik'; + +import Store from 'Store/installStore'; + +import { FormValues } from '../../Install'; +import s from './StepButtons.module.pcss'; + +interface StepButtonsProps { + setFieldValue: FormikHelpers['setFieldValue']; + currentStep: number; + values: FormValues; +} + +const StepButtons: FC = observer(({ + setFieldValue, + currentStep, +}) => { + const { ui: { intl } } = useContext(Store); + return ( +
+ + +
+ ); +}); + +export default StepButtons; diff --git a/client2/src/components/Install/components/StepButtons/index.ts b/client2/src/components/Install/components/StepButtons/index.ts new file mode 100644 index 00000000..a4875a80 --- /dev/null +++ b/client2/src/components/Install/components/StepButtons/index.ts @@ -0,0 +1 @@ +export { default } from './StepButtons'; diff --git a/client2/src/components/Install/components/Stepper/Stepper.module.pcss b/client2/src/components/Install/components/Stepper/Stepper.module.pcss new file mode 100644 index 00000000..c50ba055 --- /dev/null +++ b/client2/src/components/Install/components/Stepper/Stepper.module.pcss @@ -0,0 +1,3 @@ +.stepper { + margin-bottom: 48px; +} \ No newline at end of file diff --git a/client2/src/components/Install/components/Stepper/Stepper.tsx b/client2/src/components/Install/components/Stepper/Stepper.tsx new file mode 100644 index 00000000..060aa4e3 --- /dev/null +++ b/client2/src/components/Install/components/Stepper/Stepper.tsx @@ -0,0 +1,24 @@ +import React, { FC } from 'react'; +import { Steps } from 'antd'; + +import s from './Stepper.module.pcss'; + +interface StepperProps { + currentStep: number; +} + +const { Step } = Steps; + +const Stepper: FC = ({ currentStep }) => { + return ( + + + + + + + + ); +}; + +export default Stepper; diff --git a/client2/src/components/Install/components/Stepper/index.ts b/client2/src/components/Install/components/Stepper/index.ts new file mode 100644 index 00000000..2fb2a1bd --- /dev/null +++ b/client2/src/components/Install/components/Stepper/index.ts @@ -0,0 +1 @@ +export { default } from './Stepper'; diff --git a/client2/src/components/Install/components/Welcome/Welcome.module.pcss b/client2/src/components/Install/components/Welcome/Welcome.module.pcss new file mode 100644 index 00000000..37a5824f --- /dev/null +++ b/client2/src/components/Install/components/Welcome/Welcome.module.pcss @@ -0,0 +1,15 @@ +.iconContainer{ + margin-bottom: 48px; +} +.icon { + width: 185px; + height: 57px; +} +.button { + margin-top: 48px; + width: 190px; + + &.inGroup { + margin-right: 24px; + } +} \ No newline at end of file diff --git a/client2/src/components/Install/components/Welcome/Welcome.tsx b/client2/src/components/Install/components/Welcome/Welcome.tsx new file mode 100644 index 00000000..cd876465 --- /dev/null +++ b/client2/src/components/Install/components/Welcome/Welcome.tsx @@ -0,0 +1,40 @@ +import React, { FC, useContext } from 'react'; +import { Button } from 'antd'; +import { observer } from 'mobx-react-lite'; + +import Store from 'Store/installStore'; +import Icon from 'Common/ui/Icon'; +import theme from 'Lib/theme'; + +import s from './Welcome.module.pcss'; + +interface WelcomeProps { + onNext: () => void; +} + +const Welcome: FC = observer(({ onNext }) => { + const { ui: { intl } } = useContext(Store); + return ( +
+
+ +
+
+ {intl.getMessage('install_wellcome_title')} +
+
+ {intl.getMessage('install_wellcome_desc')} +
+ +
+ ); +}); + +export default Welcome; diff --git a/client2/src/components/Install/components/Welcome/index.ts b/client2/src/components/Install/components/Welcome/index.ts new file mode 100644 index 00000000..7cc4be19 --- /dev/null +++ b/client2/src/components/Install/components/Welcome/index.ts @@ -0,0 +1 @@ +export { default } from './Welcome'; diff --git a/client2/src/components/Install/index.ts b/client2/src/components/Install/index.ts new file mode 100644 index 00000000..6808555d --- /dev/null +++ b/client2/src/components/Install/index.ts @@ -0,0 +1 @@ +export { default } from './Install'; diff --git a/client2/src/components/common/controls/Input/Input.tsx b/client2/src/components/common/controls/Input/Input.tsx new file mode 100644 index 00000000..2b7503bb --- /dev/null +++ b/client2/src/components/common/controls/Input/Input.tsx @@ -0,0 +1,146 @@ +import React, { FC, FocusEvent, KeyboardEvent, ClipboardEvent, ChangeEvent, useState } from 'react'; +import { Input as InputControl } from 'antd'; +import { InputProps as InputControlProps } from 'antd/lib/input'; +import cn from 'classnames'; + +import { Icon } from 'Common/ui'; +import theme from 'Lib/theme'; + +interface AdminInterfaceProps { + autoComplete?: InputControlProps['autoComplete']; + autoFocus?: InputControlProps['autoFocus']; + className?: string; + description?: string; + disabled?: boolean; + error?: boolean; + id?: string; + inputMode?: InputControlProps['inputMode']; + label?: string; + wrapperClassName?: string; + name: string; + onBlur?: (e: FocusEvent) => void; + onChange?: (data: string, e?: ChangeEvent) => void; + onFocus?: (e: FocusEvent) => void; + onKeyDown?: (e: KeyboardEvent) => void; + onPaste?: (e: ClipboardEvent) => void; + pattern?: InputControlProps['pattern']; + placeholder: string; + prefix?: InputControlProps['prefix']; + size?: InputControlProps['size']; + suffix?: InputControlProps['suffix']; + type: InputControlProps['type']; + value: string | number; +} + +const InputComponent: FC = ({ + autoComplete, + autoFocus, + className, + description, + disabled, + error, + id, + inputMode, + label, + wrapperClassName, + name, + onBlur, + onChange, + onFocus, + onKeyDown, + onPaste, + pattern, + placeholder, + prefix, + size = 'middle', + suffix, + type, + value, +}) => { + const [inputType, setInputType] = useState(type); + + const inputClass = cn( + 'input', + { input_big: size === 'large' }, + { input_medium: size === 'middle' }, + { input_small: size === 'small' }, + className, + ); + + const handleBlur = (e: FocusEvent) => { + if (onBlur) { + onBlur(e); + } + }; + + const showPassword = () => { + if (inputType === 'password') { + setInputType('text'); + } else { + setInputType('password'); + } + }; + + const showPasswordIcon = () => { + const icon = inputType === 'password' ? 'visibility_disable' : 'visibility_enable'; + return ( + + ); + }; + + const validSuffix = ( + <> + {!!suffix && suffix} + {(type === 'password') && showPasswordIcon()} + + ); + + let descriptionView = null; + if (description) { + descriptionView = ( +
+ {description} +
+ ); + } + + return ( + + ); +}; + +export default InputComponent; diff --git a/client2/src/components/common/controls/Input/index.ts b/client2/src/components/common/controls/Input/index.ts new file mode 100644 index 00000000..b4d38647 --- /dev/null +++ b/client2/src/components/common/controls/Input/index.ts @@ -0,0 +1 @@ +export { default as Input } from './Input'; diff --git a/client2/src/components/common/controls/Radio/Radio.module.pcss b/client2/src/components/common/controls/Radio/Radio.module.pcss new file mode 100644 index 00000000..50c4a8a9 --- /dev/null +++ b/client2/src/components/common/controls/Radio/Radio.module.pcss @@ -0,0 +1,16 @@ +.group { + width: 100%; +} + +.radio { + display: flex; + align-items: center; + margin-bottom: 16px; + padding-bottom: 16px; + width: 100%; + border-bottom: 1px solid var(--gray300); + + &:last-child { + border-bottom: 0; + } +} \ No newline at end of file diff --git a/client2/src/components/common/controls/Radio/Radio.tsx b/client2/src/components/common/controls/Radio/Radio.tsx new file mode 100644 index 00000000..dfb1586b --- /dev/null +++ b/client2/src/components/common/controls/Radio/Radio.tsx @@ -0,0 +1,57 @@ +import React, { FC } from 'react'; +import { Radio } from 'antd'; +import { observer } from 'mobx-react-lite'; + +import theme from 'Lib/theme'; + +import s from './Radio.module.pcss'; + +const { Group } = Radio; + +interface AdminInterfaceProps { + options: { + label: string; + desc?: string; + value: string | number; + }[]; + onSelect: (value: string | number) => void; + value: string | number; +} + +const RadioComponent: FC = observer(({ + options, onSelect, value, +}) => { + if (options.length === 0) { + return null; + } + + return ( + { + onSelect(e.target.value); + }} + className={s.group} + > + {options.map((o) => ( + +
+ {o.label} +
+ {o.desc && ( +
+ {o.desc} +
+ )} +
+ ))} +
+ + ); +}); + +export default RadioComponent; diff --git a/client2/src/components/common/controls/Radio/index.ts b/client2/src/components/common/controls/Radio/index.ts new file mode 100644 index 00000000..61c95671 --- /dev/null +++ b/client2/src/components/common/controls/Radio/index.ts @@ -0,0 +1 @@ +export { default } from './Radio'; diff --git a/client2/src/components/common/controls/Switch/Switch.tsx b/client2/src/components/common/controls/Switch/Switch.tsx new file mode 100644 index 00000000..bb762eec --- /dev/null +++ b/client2/src/components/common/controls/Switch/Switch.tsx @@ -0,0 +1,3 @@ +import { Switch as SwitchE } from 'antd'; + +export default SwitchE; diff --git a/client2/src/components/common/controls/Switch/index.ts b/client2/src/components/common/controls/Switch/index.ts new file mode 100644 index 00000000..4f1eea55 --- /dev/null +++ b/client2/src/components/common/controls/Switch/index.ts @@ -0,0 +1 @@ +export { default as Switch } from './Switch'; diff --git a/client2/src/components/common/controls/index.ts b/client2/src/components/common/controls/index.ts new file mode 100644 index 00000000..6ff1fe13 --- /dev/null +++ b/client2/src/components/common/controls/index.ts @@ -0,0 +1,3 @@ +export { default as Radio } from './Radio'; +export { Input } from './Input'; +export { Switch } from './Switch'; diff --git a/client2/src/components/common/formating/danger.tsx b/client2/src/components/common/formating/danger.tsx new file mode 100644 index 00000000..60e9cc7e --- /dev/null +++ b/client2/src/components/common/formating/danger.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import theme from 'Lib/theme'; + +const danger = (e: string) => { + return ( + + {e} + + ); +}; + +export default danger; diff --git a/client2/src/components/common/formating/index.ts b/client2/src/components/common/formating/index.ts new file mode 100644 index 00000000..6bed9429 --- /dev/null +++ b/client2/src/components/common/formating/index.ts @@ -0,0 +1,2 @@ +export { default as danger } from './danger'; +export { default as p } from './p'; diff --git a/client2/src/components/common/formating/p.tsx b/client2/src/components/common/formating/p.tsx new file mode 100644 index 00000000..68b39915 --- /dev/null +++ b/client2/src/components/common/formating/p.tsx @@ -0,0 +1,11 @@ +import React from 'react'; + +const danger = (e: string) => { + return ( +

+ {e} +

+ ); +}; + +export default danger; diff --git a/client2/src/components/common/index.ts b/client2/src/components/common/index.ts new file mode 100644 index 00000000..e69de29b diff --git a/client2/src/components/common/ui/Icon/Icon.module.pcss b/client2/src/components/common/ui/Icon/Icon.module.pcss new file mode 100644 index 00000000..a935fe21 --- /dev/null +++ b/client2/src/components/common/ui/Icon/Icon.module.pcss @@ -0,0 +1,7 @@ +.icon { + display: inline-block; + vertical-align: middle; + width: 24px; + height: 24px; + flex-shrink: 0; +} diff --git a/client2/src/components/common/ui/Icon/Icon.tsx b/client2/src/components/common/ui/Icon/Icon.tsx new file mode 100644 index 00000000..55f5b027 --- /dev/null +++ b/client2/src/components/common/ui/Icon/Icon.tsx @@ -0,0 +1,25 @@ +import React, { FC } from 'react'; +import cn from 'classnames'; +import { IconType } from 'Lib/theme/Icons'; + +import s from './Icon.module.pcss'; + +interface IconProps { + icon: IconType; + color?: string; + className?: string; + onClick?: () => void; +} + +const Icon: FC = ({ icon, color, className, onClick }) => { + const iconClass = cn(s.icon, color, className); + + return ( + + + + ); +}; + +export default Icon; +export { IconType } from 'Lib/theme/Icons'; diff --git a/client2/src/components/common/ui/Icon/index.ts b/client2/src/components/common/ui/Icon/index.ts new file mode 100644 index 00000000..c1c8457b --- /dev/null +++ b/client2/src/components/common/ui/Icon/index.ts @@ -0,0 +1 @@ +export { default, IconType } from './Icon'; diff --git a/client2/src/components/common/ui/Notifications/index.ts b/client2/src/components/common/ui/Notifications/index.ts new file mode 100644 index 00000000..69c284eb --- /dev/null +++ b/client2/src/components/common/ui/Notifications/index.ts @@ -0,0 +1 @@ +export { notifyError, notifySuccess } from './notifications'; diff --git a/client2/src/components/common/ui/Notifications/notifications.tsx b/client2/src/components/common/ui/Notifications/notifications.tsx new file mode 100644 index 00000000..6a5da09b --- /dev/null +++ b/client2/src/components/common/ui/Notifications/notifications.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import { notification } from 'antd'; + +import { DEFAULT_NOTIFICATION_DURATION } from 'Consts/common'; + +export const notifySuccess = (title: string, code?: string) => { + notification.success({ + message: ( +
+ {title} +
+ ), + placement: 'bottomRight', + duration: DEFAULT_NOTIFICATION_DURATION, + className: 'notification', + }); +}; + +export const notifyError = ( + title: string, + options?: { + btn?: React.ReactNode; + duration?: number; + onClose?: () => void; + }, +) => { + const { btn, duration, onClose } = options || {}; + notification.error({ + onClose, + message: ( +
+ {title} +
+ ), + placement: 'bottomRight', + duration: typeof duration === 'number' ? duration : DEFAULT_NOTIFICATION_DURATION, + className: 'notification', + btn, + }); +}; diff --git a/client2/src/components/common/ui/index.ts b/client2/src/components/common/ui/index.ts new file mode 100644 index 00000000..f725111d --- /dev/null +++ b/client2/src/components/common/ui/index.ts @@ -0,0 +1,2 @@ +export { default as Icon } from './Icon'; +export { notifyError, notifySuccess } from './Notifications'; diff --git a/client2/src/lib/ant/Radio.pcss b/client2/src/lib/ant/Radio.pcss new file mode 100644 index 00000000..b3937d8d --- /dev/null +++ b/client2/src/lib/ant/Radio.pcss @@ -0,0 +1,15 @@ +.ant-radio { + margin-right: 18px; +} + +.ant-radio-inner { + width: 20px; + height: 20px; + background-color: transparent; + border-color: var(--gray400); + + &::after { + width: 12px; + height: 12px; + } +} \ No newline at end of file diff --git a/client2/src/lib/ant/Step.pcss b/client2/src/lib/ant/Step.pcss new file mode 100644 index 00000000..9f6e2044 --- /dev/null +++ b/client2/src/lib/ant/Step.pcss @@ -0,0 +1,65 @@ +.ant-steps { + display: flex; + margin-left: -67px; + .ant-steps-item-process { + .ant-steps-item-icon { + top: -4px; + box-sizing: content-box; + width: 16px; + height: 16px; + .ant-steps-icon { + background: var(--green400); + .ant-steps-icon-dot { + background: var(--green400); + border: 0; + } + } + } + } + .ant-steps-item-content { + width: 99px; + } + .ant-steps-item-finish > .ant-steps-item-container > .ant-steps-item-tail, + .ant-steps-item-wait > .ant-steps-item-container > .ant-steps-item-tail, + .ant-steps-item-process > .ant-steps-item-container > .ant-steps-item-tail { + top: 2px; + width: 100%; + margin: 0px 0px 0px 70px; + padding: 0; + &::after { + width: calc(100% - 8px); + height: 2px; + margin-left: 5px; + } + } + .ant-steps-item-wait > .ant-steps-item-container > .ant-steps-item-tail::after, + .ant-steps-item-process > .ant-steps-item-container > .ant-steps-item-tail::after { + background-color: var(--gray400); + } + .ant-steps-item-finish > .ant-steps-item-container > .ant-steps-item-tail::after { + background-color: var(--green400); + } + + .ant-steps-item-finish { + .ant-steps-item-icon { + .ant-steps-icon { + background: var(--green400); + .ant-steps-icon-dot { + background: var(--green400); + border: 0; + } + } + } + } + .ant-steps-item-icon { + width: 8px; + height: 8px; + .ant-steps-icon { + background: transparent; + .ant-steps-icon-dot { + background: transparent; + border: 2px solid var(--gray400); + } + } + } +} \ No newline at end of file diff --git a/client2/src/lib/ant/ant-overrides.less b/client2/src/lib/ant/ant-overrides.less new file mode 100644 index 00000000..a07601d9 --- /dev/null +++ b/client2/src/lib/ant/ant-overrides.less @@ -0,0 +1,8 @@ +@primary-color: #67b279; +@success-color: #53d4b1; +@text-color: #000; +@link-hover-color: #1332BB; +@link-active-color: #246FFF; + +@font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Arial, sans-serif; +@font-size-base: 14px; \ No newline at end of file diff --git a/client2/src/lib/ant/ant.less b/client2/src/lib/ant/ant.less new file mode 100644 index 00000000..bc24c3bc --- /dev/null +++ b/client2/src/lib/ant/ant.less @@ -0,0 +1,2 @@ +@import '~antd/dist/antd.less'; +@import './ant-overrides.less'; diff --git a/client2/src/lib/ant/index.ts b/client2/src/lib/ant/index.ts new file mode 100644 index 00000000..9c8a364d --- /dev/null +++ b/client2/src/lib/ant/index.ts @@ -0,0 +1,5 @@ +import './Step.pcss'; +import './Radio.pcss'; + +const insertStyles = true; +export default insertStyles; diff --git a/client2/src/lib/apis/blockedServices.ts b/client2/src/lib/apis/blockedServices.ts new file mode 100644 index 00000000..381a236d --- /dev/null +++ b/client2/src/lib/apis/blockedServices.ts @@ -0,0 +1,31 @@ +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export default class BlockedServicesApi { + static async blockedServicesList(): Promise { + return await fetch(`/control/blocked_services/list`, { + method: 'GET', + }).then(async (res) => { + if (res.status === 200) { + return res.json(); + } else { + return new Error(String(res.status)); + } + }) + } + + static async blockedServicesSet(data: string[]): Promise { + return await fetch(`/control/blocked_services/set`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(data), + }).then(async (res) => { + if (res.status === 200) { + return res.status; + } else { + return new Error(String(res.status)); + } + }) + } +} diff --git a/client2/src/lib/apis/clients.ts b/client2/src/lib/apis/clients.ts new file mode 100644 index 00000000..faaf22fb --- /dev/null +++ b/client2/src/lib/apis/clients.ts @@ -0,0 +1,103 @@ +import qs from 'qs'; +import Client, { IClient } from 'Entities/Client'; +import ClientDelete, { IClientDelete } from 'Entities/ClientDelete'; +import ClientUpdate, { IClientUpdate } from 'Entities/ClientUpdate'; +import Clients, { IClients } from 'Entities/Clients'; +import ClientsFindEntry, { IClientsFindEntry } from 'Entities/ClientsFindEntry'; + +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export default class ClientsApi { + static async clientsAdd(client: IClient): Promise { + const haveError: string[] = []; + const clientValid = new Client(client); + haveError.push(...clientValid.validate()); + if (haveError.length > 0) { + return Promise.resolve(haveError); + } + return await fetch(`/control/clients/add`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(clientValid.serialize()), + }).then(async (res) => { + if (res.status === 200) { + return res.status; + } else { + return new Error(String(res.status)); + } + }) + } + + static async clientsDelete(clientdelete: IClientDelete): Promise { + const haveError: string[] = []; + const clientdeleteValid = new ClientDelete(clientdelete); + haveError.push(...clientdeleteValid.validate()); + if (haveError.length > 0) { + return Promise.resolve(haveError); + } + return await fetch(`/control/clients/delete`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(clientdeleteValid.serialize()), + }).then(async (res) => { + if (res.status === 200) { + return res.status; + } else { + return new Error(String(res.status)); + } + }) + } + + static async clientsFind(ip0?: string): Promise { + const queryParams = { + ip0: ip0, + } + return await fetch(`/control/clients/find?${qs.stringify(queryParams, { arrayFormat: 'comma' })}`, { + method: 'GET', + }).then(async (res) => { + if (res.status === 200) { + return res.json(); + } else { + return new Error(String(res.status)); + } + }) + } + + static async clientsStatus(): Promise { + return await fetch(`/control/clients`, { + method: 'GET', + }).then(async (res) => { + if (res.status === 200) { + return res.json(); + } else { + return new Error(String(res.status)); + } + }) + } + + static async clientsUpdate(clientupdate: IClientUpdate): Promise { + const haveError: string[] = []; + const clientupdateValid = new ClientUpdate(clientupdate); + haveError.push(...clientupdateValid.validate()); + if (haveError.length > 0) { + return Promise.resolve(haveError); + } + return await fetch(`/control/clients/update`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(clientupdateValid.serialize()), + }).then(async (res) => { + if (res.status === 200) { + return res.status; + } else { + return new Error(String(res.status)); + } + }) + } +} diff --git a/client2/src/lib/apis/dhcp.ts b/client2/src/lib/apis/dhcp.ts new file mode 100644 index 00000000..99eef065 --- /dev/null +++ b/client2/src/lib/apis/dhcp.ts @@ -0,0 +1,110 @@ +import DhcpConfig, { IDhcpConfig } from 'Entities/DhcpConfig'; +import DhcpSearchResult, { IDhcpSearchResult } from 'Entities/DhcpSearchResult'; +import DhcpStaticLease, { IDhcpStaticLease } from 'Entities/DhcpStaticLease'; +import DhcpStatus, { IDhcpStatus } from 'Entities/DhcpStatus'; + +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export default class DhcpApi { + static async checkActiveDhcp(): Promise { + return await fetch(`/control/dhcp/find_active_dhcp`, { + method: 'POST', + }).then(async (res) => { + if (res.status === 200) { + return res.json(); + } else { + return new Error(String(res.status)); + } + }) + } + + static async dhcpAddStaticLease(dhcpstaticlease: IDhcpStaticLease): Promise { + const haveError: string[] = []; + const dhcpstaticleaseValid = new DhcpStaticLease(dhcpstaticlease); + haveError.push(...dhcpstaticleaseValid.validate()); + if (haveError.length > 0) { + return Promise.resolve(haveError); + } + return await fetch(`/control/dhcp/add_static_lease`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(dhcpstaticleaseValid.serialize()), + }).then(async (res) => { + if (res.status === 200) { + return res.status; + } else { + return new Error(String(res.status)); + } + }) + } + + static async dhcpRemoveStaticLease(dhcpstaticlease: IDhcpStaticLease): Promise { + const haveError: string[] = []; + const dhcpstaticleaseValid = new DhcpStaticLease(dhcpstaticlease); + haveError.push(...dhcpstaticleaseValid.validate()); + if (haveError.length > 0) { + return Promise.resolve(haveError); + } + return await fetch(`/control/dhcp/remove_static_lease`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(dhcpstaticleaseValid.serialize()), + }).then(async (res) => { + if (res.status === 200) { + return res.status; + } else { + return new Error(String(res.status)); + } + }) + } + + static async dhcpReset(): Promise { + return await fetch(`/control/dhcp/reset`, { + method: 'POST', + }).then(async (res) => { + if (res.status === 200) { + return res.status; + } else { + return new Error(String(res.status)); + } + }) + } + + static async dhcpSetConfig(dhcpconfig: IDhcpConfig): Promise { + const haveError: string[] = []; + const dhcpconfigValid = new DhcpConfig(dhcpconfig); + haveError.push(...dhcpconfigValid.validate()); + if (haveError.length > 0) { + return Promise.resolve(haveError); + } + return await fetch(`/control/dhcp/set_config`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(dhcpconfigValid.serialize()), + }).then(async (res) => { + if (res.status === 200) { + return res.status; + } else { + return new Error(String(res.status)); + } + }) + } + + static async dhcpStatus(): Promise { + return await fetch(`/control/dhcp/status`, { + method: 'GET', + }).then(async (res) => { + if (res.status === 200) { + return res.json(); + } else { + return new Error(String(res.status)); + } + }) + } +} diff --git a/client2/src/lib/apis/filtering.ts b/client2/src/lib/apis/filtering.ts new file mode 100644 index 00000000..cdaf1e91 --- /dev/null +++ b/client2/src/lib/apis/filtering.ts @@ -0,0 +1,167 @@ +import qs from 'qs'; +import AddUrlRequest, { IAddUrlRequest } from 'Entities/AddUrlRequest'; +import FilterCheckHostResponse, { IFilterCheckHostResponse } from 'Entities/FilterCheckHostResponse'; +import FilterConfig, { IFilterConfig } from 'Entities/FilterConfig'; +import FilterRefreshRequest, { IFilterRefreshRequest } from 'Entities/FilterRefreshRequest'; +import FilterRefreshResponse, { IFilterRefreshResponse } from 'Entities/FilterRefreshResponse'; +import FilterSetUrl, { IFilterSetUrl } from 'Entities/FilterSetUrl'; +import FilterStatus, { IFilterStatus } from 'Entities/FilterStatus'; +import RemoveUrlRequest, { IRemoveUrlRequest } from 'Entities/RemoveUrlRequest'; + +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export default class FilteringApi { + static async filteringAddURL(addurlrequest: IAddUrlRequest): Promise { + const haveError: string[] = []; + const addurlrequestValid = new AddUrlRequest(addurlrequest); + haveError.push(...addurlrequestValid.validate()); + if (haveError.length > 0) { + return Promise.resolve(haveError); + } + return await fetch(`/control/filtering/add_url`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(addurlrequestValid.serialize()), + }).then(async (res) => { + if (res.status === 200) { + return res.status; + } else { + return new Error(String(res.status)); + } + }) + } + + static async filteringCheckHost(name?: string): Promise { + const queryParams = { + name: name, + } + return await fetch(`/control/filtering/check_host?${qs.stringify(queryParams, { arrayFormat: 'comma' })}`, { + method: 'GET', + }).then(async (res) => { + if (res.status === 200) { + return res.json(); + } else { + return new Error(String(res.status)); + } + }) + } + + static async filteringConfig(filterconfig: IFilterConfig): Promise { + const haveError: string[] = []; + const filterconfigValid = new FilterConfig(filterconfig); + haveError.push(...filterconfigValid.validate()); + if (haveError.length > 0) { + return Promise.resolve(haveError); + } + return await fetch(`/control/filtering/config`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(filterconfigValid.serialize()), + }).then(async (res) => { + if (res.status === 200) { + return res.status; + } else { + return new Error(String(res.status)); + } + }) + } + + static async filteringRefresh(filterrefreshrequest: IFilterRefreshRequest): Promise { + const haveError: string[] = []; + const filterrefreshrequestValid = new FilterRefreshRequest(filterrefreshrequest); + haveError.push(...filterrefreshrequestValid.validate()); + if (haveError.length > 0) { + return Promise.resolve(haveError); + } + return await fetch(`/control/filtering/refresh`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(filterrefreshrequestValid.serialize()), + }).then(async (res) => { + if (res.status === 200) { + return res.json(); + } else { + return new Error(String(res.status)); + } + }) + } + + static async filteringRemoveURL(removeurlrequest: IRemoveUrlRequest): Promise { + const haveError: string[] = []; + const removeurlrequestValid = new RemoveUrlRequest(removeurlrequest); + haveError.push(...removeurlrequestValid.validate()); + if (haveError.length > 0) { + return Promise.resolve(haveError); + } + return await fetch(`/control/filtering/remove_url`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(removeurlrequestValid.serialize()), + }).then(async (res) => { + if (res.status === 200) { + return res.status; + } else { + return new Error(String(res.status)); + } + }) + } + + static async filteringSetRules(data: string): Promise { + const params = String(data); + return await fetch(`/control/filtering/set_rules`, { + method: 'POST', + headers: { + 'Content-Type': 'text/plain', + }, + body: params, + }).then(async (res) => { + if (res.status === 200) { + return res.status; + } else { + return new Error(String(res.status)); + } + }) + } + + static async filteringSetURL(filterseturl: IFilterSetUrl): Promise { + const haveError: string[] = []; + const filterseturlValid = new FilterSetUrl(filterseturl); + haveError.push(...filterseturlValid.validate()); + if (haveError.length > 0) { + return Promise.resolve(haveError); + } + return await fetch(`/control/filtering/set_url`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(filterseturlValid.serialize()), + }).then(async (res) => { + if (res.status === 200) { + return res.status; + } else { + return new Error(String(res.status)); + } + }) + } + + static async filteringStatus(): Promise { + return await fetch(`/control/filtering/status`, { + method: 'GET', + }).then(async (res) => { + if (res.status === 200) { + return res.json(); + } else { + return new Error(String(res.status)); + } + }) + } +} diff --git a/client2/src/lib/apis/global.ts b/client2/src/lib/apis/global.ts new file mode 100644 index 00000000..d66e7139 --- /dev/null +++ b/client2/src/lib/apis/global.ts @@ -0,0 +1,160 @@ +import DNSConfig, { IDNSConfig } from 'Entities/DNSConfig'; +import GetVersionRequest, { IGetVersionRequest } from 'Entities/GetVersionRequest'; +import Login, { ILogin } from 'Entities/Login'; +import ProfileInfo, { IProfileInfo } from 'Entities/ProfileInfo'; +import ServerStatus, { IServerStatus } from 'Entities/ServerStatus'; +import UpstreamsConfig, { IUpstreamsConfig } from 'Entities/UpstreamsConfig'; +import UpstreamsConfigResponse, { IUpstreamsConfigResponse } from 'Entities/UpstreamsConfigResponse'; +import VersionInfo, { IVersionInfo } from 'Entities/VersionInfo'; + +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export default class GlobalApi { + static async beginUpdate(): Promise { + return await fetch(`/control/update`, { + method: 'POST', + }).then(async (res) => { + if (res.status === 200) { + return res.status; + } else { + return new Error(String(res.status)); + } + }) + } + + static async dnsConfig(dnsconfig: IDNSConfig): Promise { + const haveError: string[] = []; + const dnsconfigValid = new DNSConfig(dnsconfig); + haveError.push(...dnsconfigValid.validate()); + if (haveError.length > 0) { + return Promise.resolve(haveError); + } + return await fetch(`/control/dns_config`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(dnsconfigValid.serialize()), + }).then(async (res) => { + if (res.status === 200) { + return res.status; + } else { + return new Error(String(res.status)); + } + }) + } + + static async dnsInfo(): Promise { + return await fetch(`/control/dns_info`, { + method: 'GET', + }).then(async (res) => { + if (res.status === 200) { + return res.json(); + } else { + return new Error(String(res.status)); + } + }) + } + + static async getProfile(): Promise { + return await fetch(`/control/profile`, { + method: 'GET', + }).then(async (res) => { + if (res.status === 200) { + return res.json(); + } else { + return new Error(String(res.status)); + } + }) + } + + static async getVersionJson(getversionrequest: IGetVersionRequest): Promise { + const haveError: string[] = []; + const getversionrequestValid = new GetVersionRequest(getversionrequest); + haveError.push(...getversionrequestValid.validate()); + if (haveError.length > 0) { + return Promise.resolve(haveError); + } + return await fetch(`/control/version.json`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(getversionrequestValid.serialize()), + }).then(async (res) => { + if (res.status === 200) { + return res.json(); + } else { + return new Error(String(res.status)); + } + }) + } + + static async login(login: ILogin): Promise { + const haveError: string[] = []; + const loginValid = new Login(login); + haveError.push(...loginValid.validate()); + if (haveError.length > 0) { + return Promise.resolve(haveError); + } + return await fetch(`/control/login`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(loginValid.serialize()), + }).then(async (res) => { + if (res.status === 200) { + return res.status; + } else { + return new Error(String(res.status)); + } + }) + } + + static async logout(): Promise { + return await fetch(`/control/logout`, { + method: 'GET', + }).then(async (res) => { + if (res.status === 200) { + return res.status; + } else { + return new Error(String(res.status)); + } + }) + } + + static async status(): Promise { + return await fetch(`/control/status`, { + method: 'GET', + }).then(async (res) => { + if (res.status === 200) { + return res.json(); + } else { + return new Error(String(res.status)); + } + }) + } + + static async testUpstreamDNS(upstreamsconfig: IUpstreamsConfig): Promise { + const haveError: string[] = []; + const upstreamsconfigValid = new UpstreamsConfig(upstreamsconfig); + haveError.push(...upstreamsconfigValid.validate()); + if (haveError.length > 0) { + return Promise.resolve(haveError); + } + return await fetch(`/control/test_upstream_dns`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(upstreamsconfigValid.serialize()), + }).then(async (res) => { + if (res.status === 200) { + return res.json(); + } else { + return new Error(String(res.status)); + } + }) + } +} diff --git a/client2/src/lib/apis/i18n.ts b/client2/src/lib/apis/i18n.ts new file mode 100644 index 00000000..045ea104 --- /dev/null +++ b/client2/src/lib/apis/i18n.ts @@ -0,0 +1,32 @@ +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export default class I18nApi { + static async changeLanguage(data: string): Promise { + const params = String(data); + return await fetch(`/control/i18n/change_language`, { + method: 'POST', + headers: { + 'Content-Type': 'text/plain', + }, + body: params, + }).then(async (res) => { + if (res.status === 200) { + return res.status; + } else { + return new Error(String(res.status)); + } + }) + } + + static async currentLanguage(): Promise { + return await fetch(`/control/i18n/current_language`, { + method: 'GET', + }).then(async (res) => { + if (res.status === 200) { + return res.status; + } else { + return new Error(String(res.status)); + } + }) + } +} diff --git a/client2/src/lib/apis/install.ts b/client2/src/lib/apis/install.ts new file mode 100644 index 00000000..6de0d230 --- /dev/null +++ b/client2/src/lib/apis/install.ts @@ -0,0 +1,123 @@ +import AddressesInfo, { IAddressesInfo } from 'Entities/AddressesInfo'; +import AddressesInfoBeta, { IAddressesInfoBeta } from 'Entities/AddressesInfoBeta'; +import CheckConfigRequest, { ICheckConfigRequest } from 'Entities/CheckConfigRequest'; +import CheckConfigRequestBeta, { ICheckConfigRequestBeta } from 'Entities/CheckConfigRequestBeta'; +import CheckConfigResponse, { ICheckConfigResponse } from 'Entities/CheckConfigResponse'; +import InitialConfiguration, { IInitialConfiguration } from 'Entities/InitialConfiguration'; +import InitialConfigurationBeta, { IInitialConfigurationBeta } from 'Entities/InitialConfigurationBeta'; + +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export default class InstallApi { + static async installCheckConfig(checkconfigrequest: ICheckConfigRequest): Promise { + const haveError: string[] = []; + const checkconfigrequestValid = new CheckConfigRequest(checkconfigrequest); + haveError.push(...checkconfigrequestValid.validate()); + if (haveError.length > 0) { + return Promise.resolve(haveError); + } + return await fetch(`/control/install/check_config`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(checkconfigrequestValid.serialize()), + }).then(async (res) => { + if (res.status === 200) { + return res.json(); + } else { + return new Error(String(res.status)); + } + }) + } + + static async installCheckConfigBeta(checkconfigrequestbeta: ICheckConfigRequestBeta): Promise { + const haveError: string[] = []; + const checkconfigrequestbetaValid = new CheckConfigRequestBeta(checkconfigrequestbeta); + haveError.push(...checkconfigrequestbetaValid.validate()); + if (haveError.length > 0) { + return Promise.resolve(haveError); + } + return await fetch(`/control/install/check_config_beta`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(checkconfigrequestbetaValid.serialize()), + }).then(async (res) => { + if (res.status === 200) { + return res.json(); + } else { + return new Error(String(res.status)); + } + }) + } + + static async installConfigure(initialconfiguration: IInitialConfiguration): Promise { + const haveError: string[] = []; + const initialconfigurationValid = new InitialConfiguration(initialconfiguration); + haveError.push(...initialconfigurationValid.validate()); + if (haveError.length > 0) { + return Promise.resolve(haveError); + } + return await fetch(`/control/install/configure`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(initialconfigurationValid.serialize()), + }).then(async (res) => { + if (res.status === 200) { + return res.status; + } else { + return new Error(String(res.status)); + } + }) + } + + static async installConfigureBeta(initialconfigurationbeta: IInitialConfigurationBeta): Promise { + const haveError: string[] = []; + const initialconfigurationbetaValid = new InitialConfigurationBeta(initialconfigurationbeta); + haveError.push(...initialconfigurationbetaValid.validate()); + if (haveError.length > 0) { + return Promise.resolve(haveError); + } + return await fetch(`/control/install/configure_beta`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(initialconfigurationbetaValid.serialize()), + }).then(async (res) => { + if (res.status === 200) { + return res.status; + } else { + return new Error(String(res.status)); + } + }) + } + + static async installGetAddresses(): Promise { + return await fetch(`/control/install/get_addresses`, { + method: 'GET', + }).then(async (res) => { + if (res.status === 200) { + return res.json(); + } else { + return new Error(String(res.status)); + } + }) + } + + static async installGetAddressesBeta(): Promise { + return await fetch(`/control/install/get_addresses_beta`, { + method: 'GET', + }).then(async (res) => { + if (res.status === 200) { + return res.json(); + } else { + return new Error(String(res.status)); + } + }) + } +} diff --git a/client2/src/lib/apis/log.ts b/client2/src/lib/apis/log.ts new file mode 100644 index 00000000..f0aa37fb --- /dev/null +++ b/client2/src/lib/apis/log.ts @@ -0,0 +1,72 @@ +import qs from 'qs'; +import QueryLog, { IQueryLog } from 'Entities/QueryLog'; +import QueryLogConfig, { IQueryLogConfig } from 'Entities/QueryLogConfig'; + +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export default class LogApi { + static async queryLog(older_than?: string, offset?: number, limit?: number, search?: string, response_status?: string): Promise { + const queryParams = { + older_than: older_than, + offset: offset, + limit: limit, + search: search, + response_status: response_status, + } + return await fetch(`/control/querylog?${qs.stringify(queryParams, { arrayFormat: 'comma' })}`, { + method: 'GET', + }).then(async (res) => { + if (res.status === 200) { + return res.json(); + } else { + return new Error(String(res.status)); + } + }) + } + + static async queryLogConfig(querylogconfig: IQueryLogConfig): Promise { + const haveError: string[] = []; + const querylogconfigValid = new QueryLogConfig(querylogconfig); + haveError.push(...querylogconfigValid.validate()); + if (haveError.length > 0) { + return Promise.resolve(haveError); + } + return await fetch(`/control/querylog_config`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(querylogconfigValid.serialize()), + }).then(async (res) => { + if (res.status === 200) { + return res.status; + } else { + return new Error(String(res.status)); + } + }) + } + + static async queryLogInfo(): Promise { + return await fetch(`/control/querylog_info`, { + method: 'GET', + }).then(async (res) => { + if (res.status === 200) { + return res.json(); + } else { + return new Error(String(res.status)); + } + }) + } + + static async querylogClear(): Promise { + return await fetch(`/control/querylog_clear`, { + method: 'POST', + }).then(async (res) => { + if (res.status === 200) { + return res.status; + } else { + return new Error(String(res.status)); + } + }) + } +} diff --git a/client2/src/lib/apis/mobileconfig.ts b/client2/src/lib/apis/mobileconfig.ts new file mode 100644 index 00000000..0d4603c6 --- /dev/null +++ b/client2/src/lib/apis/mobileconfig.ts @@ -0,0 +1,35 @@ +import qs from 'qs'; + +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export default class MobileconfigApi { + static async mobileConfigDoH(host?: string): Promise { + const queryParams = { + host: host, + } + return await fetch(`/control/apple/doh.mobileconfig?${qs.stringify(queryParams, { arrayFormat: 'comma' })}`, { + method: 'GET', + }).then(async (res) => { + if (res.status === 200) { + return res.status; + } else { + return new Error(String(res.status)); + } + }) + } + + static async mobileConfigDoT(host?: string): Promise { + const queryParams = { + host: host, + } + return await fetch(`/control/apple/dot.mobileconfig?${qs.stringify(queryParams, { arrayFormat: 'comma' })}`, { + method: 'GET', + }).then(async (res) => { + if (res.status === 200) { + return res.status; + } else { + return new Error(String(res.status)); + } + }) + } +} diff --git a/client2/src/lib/apis/parental.ts b/client2/src/lib/apis/parental.ts new file mode 100644 index 00000000..61f693f5 --- /dev/null +++ b/client2/src/lib/apis/parental.ts @@ -0,0 +1,44 @@ +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export default class ParentalApi { + static async parentalDisable(): Promise { + return await fetch(`/control/parental/disable`, { + method: 'POST', + }).then(async (res) => { + if (res.status === 200) { + return res.status; + } else { + return new Error(String(res.status)); + } + }) + } + + static async parentalEnable(data: string): Promise { + const params = String(data); + return await fetch(`/control/parental/enable`, { + method: 'POST', + headers: { + 'Content-Type': 'text/plain', + }, + body: params, + }).then(async (res) => { + if (res.status === 200) { + return res.status; + } else { + return new Error(String(res.status)); + } + }) + } + + static async parentalStatus(): Promise { + return await fetch(`/control/parental/status`, { + method: 'GET', + }).then(async (res) => { + if (res.status === 200) { + return res.json(); + } else { + return new Error(String(res.status)); + } + }) + } +} diff --git a/client2/src/lib/apis/rewrite.ts b/client2/src/lib/apis/rewrite.ts new file mode 100644 index 00000000..eded5c97 --- /dev/null +++ b/client2/src/lib/apis/rewrite.ts @@ -0,0 +1,61 @@ +import RewriteEntry, { IRewriteEntry } from 'Entities/RewriteEntry'; + +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export default class RewriteApi { + static async rewriteAdd(rewriteentry: IRewriteEntry): Promise { + const haveError: string[] = []; + const rewriteentryValid = new RewriteEntry(rewriteentry); + haveError.push(...rewriteentryValid.validate()); + if (haveError.length > 0) { + return Promise.resolve(haveError); + } + return await fetch(`/control/rewrite/add`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(rewriteentryValid.serialize()), + }).then(async (res) => { + if (res.status === 200) { + return res.status; + } else { + return new Error(String(res.status)); + } + }) + } + + static async rewriteDelete(rewriteentry: IRewriteEntry): Promise { + const haveError: string[] = []; + const rewriteentryValid = new RewriteEntry(rewriteentry); + haveError.push(...rewriteentryValid.validate()); + if (haveError.length > 0) { + return Promise.resolve(haveError); + } + return await fetch(`/control/rewrite/delete`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(rewriteentryValid.serialize()), + }).then(async (res) => { + if (res.status === 200) { + return res.status; + } else { + return new Error(String(res.status)); + } + }) + } + + static async rewriteList(): Promise { + return await fetch(`/control/rewrite/list`, { + method: 'GET', + }).then(async (res) => { + if (res.status === 200) { + return res.json(); + } else { + return new Error(String(res.status)); + } + }) + } +} diff --git a/client2/src/lib/apis/safebrowsing.ts b/client2/src/lib/apis/safebrowsing.ts new file mode 100644 index 00000000..9d82a5a1 --- /dev/null +++ b/client2/src/lib/apis/safebrowsing.ts @@ -0,0 +1,39 @@ +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export default class SafebrowsingApi { + static async safebrowsingDisable(): Promise { + return await fetch(`/control/safebrowsing/disable`, { + method: 'POST', + }).then(async (res) => { + if (res.status === 200) { + return res.status; + } else { + return new Error(String(res.status)); + } + }) + } + + static async safebrowsingEnable(): Promise { + return await fetch(`/control/safebrowsing/enable`, { + method: 'POST', + }).then(async (res) => { + if (res.status === 200) { + return res.status; + } else { + return new Error(String(res.status)); + } + }) + } + + static async safebrowsingStatus(): Promise { + return await fetch(`/control/safebrowsing/status`, { + method: 'GET', + }).then(async (res) => { + if (res.status === 200) { + return res.json(); + } else { + return new Error(String(res.status)); + } + }) + } +} diff --git a/client2/src/lib/apis/safesearch.ts b/client2/src/lib/apis/safesearch.ts new file mode 100644 index 00000000..e36e79f9 --- /dev/null +++ b/client2/src/lib/apis/safesearch.ts @@ -0,0 +1,39 @@ +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export default class SafesearchApi { + static async safesearchDisable(): Promise { + return await fetch(`/control/safesearch/disable`, { + method: 'POST', + }).then(async (res) => { + if (res.status === 200) { + return res.status; + } else { + return new Error(String(res.status)); + } + }) + } + + static async safesearchEnable(): Promise { + return await fetch(`/control/safesearch/enable`, { + method: 'POST', + }).then(async (res) => { + if (res.status === 200) { + return res.status; + } else { + return new Error(String(res.status)); + } + }) + } + + static async safesearchStatus(): Promise { + return await fetch(`/control/safesearch/status`, { + method: 'GET', + }).then(async (res) => { + if (res.status === 200) { + return res.json(); + } else { + return new Error(String(res.status)); + } + }) + } +} diff --git a/client2/src/lib/apis/stats.ts b/client2/src/lib/apis/stats.ts new file mode 100644 index 00000000..630f17c0 --- /dev/null +++ b/client2/src/lib/apis/stats.ts @@ -0,0 +1,64 @@ +import Stats, { IStats } from 'Entities/Stats'; +import StatsConfig, { IStatsConfig } from 'Entities/StatsConfig'; + +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export default class StatsApi { + static async stats(): Promise { + return await fetch(`/control/stats`, { + method: 'GET', + }).then(async (res) => { + if (res.status === 200) { + return res.json(); + } else { + return new Error(String(res.status)); + } + }) + } + + static async statsConfig(statsconfig: IStatsConfig): Promise { + const haveError: string[] = []; + const statsconfigValid = new StatsConfig(statsconfig); + haveError.push(...statsconfigValid.validate()); + if (haveError.length > 0) { + return Promise.resolve(haveError); + } + return await fetch(`/control/stats_config`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(statsconfigValid.serialize()), + }).then(async (res) => { + if (res.status === 200) { + return res.status; + } else { + return new Error(String(res.status)); + } + }) + } + + static async statsInfo(): Promise { + return await fetch(`/control/stats_info`, { + method: 'GET', + }).then(async (res) => { + if (res.status === 200) { + return res.json(); + } else { + return new Error(String(res.status)); + } + }) + } + + static async statsReset(): Promise { + return await fetch(`/control/stats_reset`, { + method: 'POST', + }).then(async (res) => { + if (res.status === 200) { + return res.status; + } else { + return new Error(String(res.status)); + } + }) + } +} diff --git a/client2/src/lib/apis/tls.ts b/client2/src/lib/apis/tls.ts new file mode 100644 index 00000000..c9f064d7 --- /dev/null +++ b/client2/src/lib/apis/tls.ts @@ -0,0 +1,61 @@ +import TlsConfig, { ITlsConfig } from 'Entities/TlsConfig'; + +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export default class TlsApi { + static async tlsConfigure(tlsconfig: ITlsConfig): Promise { + const haveError: string[] = []; + const tlsconfigValid = new TlsConfig(tlsconfig); + haveError.push(...tlsconfigValid.validate()); + if (haveError.length > 0) { + return Promise.resolve(haveError); + } + return await fetch(`/control/tls/configure`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(tlsconfigValid.serialize()), + }).then(async (res) => { + if (res.status === 200) { + return res.json(); + } else { + return new Error(String(res.status)); + } + }) + } + + static async tlsStatus(): Promise { + return await fetch(`/control/tls/status`, { + method: 'GET', + }).then(async (res) => { + if (res.status === 200) { + return res.json(); + } else { + return new Error(String(res.status)); + } + }) + } + + static async tlsValidate(tlsconfig: ITlsConfig): Promise { + const haveError: string[] = []; + const tlsconfigValid = new TlsConfig(tlsconfig); + haveError.push(...tlsconfigValid.validate()); + if (haveError.length > 0) { + return Promise.resolve(haveError); + } + return await fetch(`/control/tls/validate`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(tlsconfigValid.serialize()), + }).then(async (res) => { + if (res.status === 200) { + return res.json(); + } else { + return new Error(String(res.status)); + } + }) + } +} diff --git a/client2/src/lib/consts/common.ts b/client2/src/lib/consts/common.ts new file mode 100644 index 00000000..77ad1796 --- /dev/null +++ b/client2/src/lib/consts/common.ts @@ -0,0 +1 @@ +export const DEFAULT_NOTIFICATION_DURATION = 5; diff --git a/client2/src/lib/consts/forms.ts b/client2/src/lib/consts/forms.ts new file mode 100644 index 00000000..85b4220d --- /dev/null +++ b/client2/src/lib/consts/forms.ts @@ -0,0 +1 @@ +export const EMPTY_FIELD_ERROR = 'empty_field'; diff --git a/client2/src/lib/consts/install.ts b/client2/src/lib/consts/install.ts new file mode 100644 index 00000000..fcc3904f --- /dev/null +++ b/client2/src/lib/consts/install.ts @@ -0,0 +1,7 @@ +export const DEFAULT_IP_ADDRESS = '0.0.0.0'; + +export const DEFAULT_IP_PORT = 80; + +export const DEFAULT_DNS_ADDRESS = '0.0.0.0'; + +export const DEFAULT_DNS_PORT = 53; diff --git a/client2/src/lib/entities/AddUrlRequest.ts b/client2/src/lib/entities/AddUrlRequest.ts new file mode 100644 index 00000000..076dd4d2 --- /dev/null +++ b/client2/src/lib/entities/AddUrlRequest.ts @@ -0,0 +1,78 @@ +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface IAddUrlRequest { + name?: string; + url?: string; + whitelist?: boolean; +} + +export default class AddUrlRequest { + readonly _name: string | undefined; + + get name(): string | undefined { + return this._name; + } + + readonly _url: string | undefined; + + /** + * Description: URL or an absolute path to the file containing filtering rules. + * + * Example: https://filters.adtidy.org/windows/filters/15.txt + */ + get url(): string | undefined { + return this._url; + } + + readonly _whitelist: boolean | undefined; + + get whitelist(): boolean | undefined { + return this._whitelist; + } + + constructor(props: IAddUrlRequest) { + if (typeof props.name === 'string') { + this._name = props.name.trim(); + } + if (typeof props.url === 'string') { + this._url = props.url.trim(); + } + if (typeof props.whitelist === 'boolean') { + this._whitelist = props.whitelist; + } + } + + serialize(): IAddUrlRequest { + const data: IAddUrlRequest = { + }; + if (typeof this._name !== 'undefined') { + data.name = this._name; + } + if (typeof this._url !== 'undefined') { + data.url = this._url; + } + if (typeof this._whitelist !== 'undefined') { + data.whitelist = this._whitelist; + } + return data; + } + + validate(): string[] { + const validate = { + name: !this._name ? true : typeof this._name === 'string' && !this._name ? true : this._name, + url: !this._url ? true : typeof this._url === 'string' && !this._url ? true : this._url, + whitelist: !this._whitelist ? true : typeof this._whitelist === 'boolean', + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): AddUrlRequest { + return new AddUrlRequest({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/AddressInfo.ts b/client2/src/lib/entities/AddressInfo.ts new file mode 100644 index 00000000..1afdecb4 --- /dev/null +++ b/client2/src/lib/entities/AddressInfo.ts @@ -0,0 +1,67 @@ +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface IAddressInfo { + ip: string; + port: number; +} + +export default class AddressInfo { + readonly _ip: string; + + /** + * Description: undefined + * Example: 127.0.0.1 + */ + get ip(): string { + return this._ip; + } + + static ipValidate(ip: string): boolean { + return typeof ip === 'string' && !!ip.trim(); + } + + readonly _port: number; + + /** + * Description: undefined + * Example: 53 + */ + get port(): number { + return this._port; + } + + static portValidate(port: number): boolean { + return typeof port === 'number'; + } + + constructor(props: IAddressInfo) { + this._ip = props.ip.trim(); + this._port = props.port; + } + + serialize(): IAddressInfo { + const data: IAddressInfo = { + ip: this._ip, + port: this._port, + }; + return data; + } + + validate(): string[] { + const validate = { + ip: typeof this._ip === 'string' && !this._ip ? true : this._ip, + port: typeof this._port === 'number', + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): AddressInfo { + return new AddressInfo({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/AddressInfoBeta.ts b/client2/src/lib/entities/AddressInfoBeta.ts new file mode 100644 index 00000000..8b5073a3 --- /dev/null +++ b/client2/src/lib/entities/AddressInfoBeta.ts @@ -0,0 +1,71 @@ +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface IAddressInfoBeta { + ip: string[]; + port: number; +} + +export default class AddressInfoBeta { + readonly _ip: string[]; + + /** + * Description: undefined + * Example: 127.0.0.1 + */ + get ip(): string[] { + return this._ip; + } + + static get ipMinItems() { + return 1; + } + + static ipValidate(ip: string[]): boolean { + return ip.reduce((result, p) => result && (typeof p === 'string' && !!p.trim()), true); + } + + readonly _port: number; + + /** + * Description: undefined + * Example: 53 + */ + get port(): number { + return this._port; + } + + static portValidate(port: number): boolean { + return typeof port === 'number'; + } + + constructor(props: IAddressInfoBeta) { + this._ip = props.ip; + this._port = props.port; + } + + serialize(): IAddressInfoBeta { + const data: IAddressInfoBeta = { + ip: this._ip, + port: this._port, + }; + return data; + } + + validate(): string[] { + const validate = { + ip: this._ip.reduce((result, p) => result && typeof p === 'string', true), + port: typeof this._port === 'number', + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): AddressInfoBeta { + return new AddressInfoBeta({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/AddressesInfo.ts b/client2/src/lib/entities/AddressesInfo.ts new file mode 100644 index 00000000..dc814eeb --- /dev/null +++ b/client2/src/lib/entities/AddressesInfo.ts @@ -0,0 +1,81 @@ +import NetInterface, { INetInterface } from './NetInterface'; + +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface IAddressesInfo { + dns_port: number; + interfaces: { [key: string]: INetInterface }; + web_port: number; +} + +export default class AddressesInfo { + readonly _dns_port: number; + + /** + * Description: undefined + * Example: 53 + */ + get dnsPort(): number { + return this._dns_port; + } + + static dnsPortValidate(dnsPort: number): boolean { + return typeof dnsPort === 'number'; + } + + readonly _interfaces: { [key: string]: NetInterface }; + + /** */ + get interfaces(): { [key: string]: NetInterface } { + return this._interfaces; + } + + readonly _web_port: number; + + /** + * Description: undefined + * Example: 80 + */ + get webPort(): number { + return this._web_port; + } + + static webPortValidate(webPort: number): boolean { + return typeof webPort === 'number'; + } + + constructor(props: IAddressesInfo) { + this._dns_port = props.dns_port; + this._interfaces = Object.keys(props.interfaces).reduce((prev, key) => { + return { ...prev, [key]: new NetInterface(props.interfaces[key])}; + },{}) + this._web_port = props.web_port; + } + + serialize(): IAddressesInfo { + const data: IAddressesInfo = { + dns_port: this._dns_port, + interfaces: Object.keys(this._interfaces).reduce>((prev, key) => ({ ...prev, [key]: this._interfaces[key].serialize() }), {}), + web_port: this._web_port, + }; + return data; + } + + validate(): string[] { + const validate = { + dns_port: typeof this._dns_port === 'number', + web_port: typeof this._web_port === 'number', + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): AddressesInfo { + return new AddressesInfo({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/AddressesInfoBeta.ts b/client2/src/lib/entities/AddressesInfoBeta.ts new file mode 100644 index 00000000..603adafe --- /dev/null +++ b/client2/src/lib/entities/AddressesInfoBeta.ts @@ -0,0 +1,80 @@ +import NetInterface, { INetInterface } from './NetInterface'; + +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface IAddressesInfoBeta { + dns_port: number; + interfaces: INetInterface[]; + web_port: number; +} + +export default class AddressesInfoBeta { + readonly _dns_port: number; + + /** + * Description: undefined + * Example: 53 + */ + get dnsPort(): number { + return this._dns_port; + } + + static dnsPortValidate(dnsPort: number): boolean { + return typeof dnsPort === 'number'; + } + + readonly _interfaces: NetInterface[]; + + /** */ + get interfaces(): NetInterface[] { + return this._interfaces; + } + + readonly _web_port: number; + + /** + * Description: undefined + * Example: 80 + */ + get webPort(): number { + return this._web_port; + } + + static webPortValidate(webPort: number): boolean { + return typeof webPort === 'number'; + } + + constructor(props: IAddressesInfoBeta) { + this._dns_port = props.dns_port; + this._interfaces = props.interfaces.map((p) => new NetInterface(p)); + this._web_port = props.web_port; + } + + serialize(): IAddressesInfoBeta { + const data: IAddressesInfoBeta = { + dns_port: this._dns_port, + interfaces: this._interfaces.map((p) => p.serialize()), + web_port: this._web_port, + }; + return data; + } + + validate(): string[] { + const validate = { + dns_port: typeof this._dns_port === 'number', + web_port: typeof this._web_port === 'number', + interfaces: this._interfaces.reduce((result, p) => result && p.validate().length === 0, true), + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): AddressesInfoBeta { + return new AddressesInfoBeta({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/BlockedServicesArray.ts b/client2/src/lib/entities/BlockedServicesArray.ts new file mode 100644 index 00000000..6c175abe --- /dev/null +++ b/client2/src/lib/entities/BlockedServicesArray.ts @@ -0,0 +1,31 @@ +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface IBlockedServicesArray { +} + +export default class BlockedServicesArray { + constructor(props: IBlockedServicesArray) { + } + + serialize(): IBlockedServicesArray { + const data: IBlockedServicesArray = { + }; + return data; + } + + validate(): string[] { + const validate = { + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): BlockedServicesArray { + return new BlockedServicesArray({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/CheckConfigRequest.ts b/client2/src/lib/entities/CheckConfigRequest.ts new file mode 100644 index 00000000..dba0f741 --- /dev/null +++ b/client2/src/lib/entities/CheckConfigRequest.ts @@ -0,0 +1,75 @@ +import CheckConfigRequestInfo, { ICheckConfigRequestInfo } from './CheckConfigRequestInfo'; + +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface ICheckConfigRequest { + dns?: ICheckConfigRequestInfo; + set_static_ip?: boolean; + web?: ICheckConfigRequestInfo; +} + +export default class CheckConfigRequest { + readonly _dns: CheckConfigRequestInfo | undefined; + + get dns(): CheckConfigRequestInfo | undefined { + return this._dns; + } + + readonly _set_static_ip: boolean | undefined; + + get setStaticIp(): boolean | undefined { + return this._set_static_ip; + } + + readonly _web: CheckConfigRequestInfo | undefined; + + get web(): CheckConfigRequestInfo | undefined { + return this._web; + } + + constructor(props: ICheckConfigRequest) { + if (props.dns) { + this._dns = new CheckConfigRequestInfo(props.dns); + } + if (typeof props.set_static_ip === 'boolean') { + this._set_static_ip = props.set_static_ip; + } + if (props.web) { + this._web = new CheckConfigRequestInfo(props.web); + } + } + + serialize(): ICheckConfigRequest { + const data: ICheckConfigRequest = { + }; + if (typeof this._dns !== 'undefined') { + data.dns = this._dns.serialize(); + } + if (typeof this._set_static_ip !== 'undefined') { + data.set_static_ip = this._set_static_ip; + } + if (typeof this._web !== 'undefined') { + data.web = this._web.serialize(); + } + return data; + } + + validate(): string[] { + const validate = { + dns: !this._dns ? true : this._dns.validate().length === 0, + web: !this._web ? true : this._web.validate().length === 0, + set_static_ip: !this._set_static_ip ? true : typeof this._set_static_ip === 'boolean', + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): CheckConfigRequest { + return new CheckConfigRequest({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/CheckConfigRequestBeta.ts b/client2/src/lib/entities/CheckConfigRequestBeta.ts new file mode 100644 index 00000000..b34c855f --- /dev/null +++ b/client2/src/lib/entities/CheckConfigRequestBeta.ts @@ -0,0 +1,75 @@ +import CheckConfigRequestInfoBeta, { ICheckConfigRequestInfoBeta } from './CheckConfigRequestInfoBeta'; + +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface ICheckConfigRequestBeta { + dns?: ICheckConfigRequestInfoBeta; + set_static_ip?: boolean; + web?: ICheckConfigRequestInfoBeta; +} + +export default class CheckConfigRequestBeta { + readonly _dns: CheckConfigRequestInfoBeta | undefined; + + get dns(): CheckConfigRequestInfoBeta | undefined { + return this._dns; + } + + readonly _set_static_ip: boolean | undefined; + + get setStaticIp(): boolean | undefined { + return this._set_static_ip; + } + + readonly _web: CheckConfigRequestInfoBeta | undefined; + + get web(): CheckConfigRequestInfoBeta | undefined { + return this._web; + } + + constructor(props: ICheckConfigRequestBeta) { + if (props.dns) { + this._dns = new CheckConfigRequestInfoBeta(props.dns); + } + if (typeof props.set_static_ip === 'boolean') { + this._set_static_ip = props.set_static_ip; + } + if (props.web) { + this._web = new CheckConfigRequestInfoBeta(props.web); + } + } + + serialize(): ICheckConfigRequestBeta { + const data: ICheckConfigRequestBeta = { + }; + if (typeof this._dns !== 'undefined') { + data.dns = this._dns.serialize(); + } + if (typeof this._set_static_ip !== 'undefined') { + data.set_static_ip = this._set_static_ip; + } + if (typeof this._web !== 'undefined') { + data.web = this._web.serialize(); + } + return data; + } + + validate(): string[] { + const validate = { + dns: !this._dns ? true : this._dns.validate().length === 0, + web: !this._web ? true : this._web.validate().length === 0, + set_static_ip: !this._set_static_ip ? true : typeof this._set_static_ip === 'boolean', + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): CheckConfigRequestBeta { + return new CheckConfigRequestBeta({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/CheckConfigRequestInfo.ts b/client2/src/lib/entities/CheckConfigRequestInfo.ts new file mode 100644 index 00000000..c8da0cf3 --- /dev/null +++ b/client2/src/lib/entities/CheckConfigRequestInfo.ts @@ -0,0 +1,81 @@ +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface ICheckConfigRequestInfo { + autofix?: boolean; + ip?: string; + port?: number; +} + +export default class CheckConfigRequestInfo { + readonly _autofix: boolean | undefined; + + get autofix(): boolean | undefined { + return this._autofix; + } + + readonly _ip: string | undefined; + + /** + * Description: undefined + * Example: 127.0.0.1 + */ + get ip(): string | undefined { + return this._ip; + } + + readonly _port: number | undefined; + + /** + * Description: undefined + * Example: 53 + */ + get port(): number | undefined { + return this._port; + } + + constructor(props: ICheckConfigRequestInfo) { + if (typeof props.autofix === 'boolean') { + this._autofix = props.autofix; + } + if (typeof props.ip === 'string') { + this._ip = props.ip.trim(); + } + if (typeof props.port === 'number') { + this._port = props.port; + } + } + + serialize(): ICheckConfigRequestInfo { + const data: ICheckConfigRequestInfo = { + }; + if (typeof this._autofix !== 'undefined') { + data.autofix = this._autofix; + } + if (typeof this._ip !== 'undefined') { + data.ip = this._ip; + } + if (typeof this._port !== 'undefined') { + data.port = this._port; + } + return data; + } + + validate(): string[] { + const validate = { + ip: !this._ip ? true : typeof this._ip === 'string' && !this._ip ? true : this._ip, + port: !this._port ? true : typeof this._port === 'number', + autofix: !this._autofix ? true : typeof this._autofix === 'boolean', + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): CheckConfigRequestInfo { + return new CheckConfigRequestInfo({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/CheckConfigRequestInfoBeta.ts b/client2/src/lib/entities/CheckConfigRequestInfoBeta.ts new file mode 100644 index 00000000..d086e771 --- /dev/null +++ b/client2/src/lib/entities/CheckConfigRequestInfoBeta.ts @@ -0,0 +1,85 @@ +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface ICheckConfigRequestInfoBeta { + autofix?: boolean; + ip?: string[]; + port?: number; +} + +export default class CheckConfigRequestInfoBeta { + readonly _autofix: boolean | undefined; + + get autofix(): boolean | undefined { + return this._autofix; + } + + readonly _ip: string[] | undefined; + + /** + * Description: undefined + * Example: 127.0.0.1 + */ + get ip(): string[] | undefined { + return this._ip; + } + + static get ipMinItems() { + return 1; + } + + readonly _port: number | undefined; + + /** + * Description: undefined + * Example: 53 + */ + get port(): number | undefined { + return this._port; + } + + constructor(props: ICheckConfigRequestInfoBeta) { + if (typeof props.autofix === 'boolean') { + this._autofix = props.autofix; + } + if (props.ip) { + this._ip = props.ip; + } + if (typeof props.port === 'number') { + this._port = props.port; + } + } + + serialize(): ICheckConfigRequestInfoBeta { + const data: ICheckConfigRequestInfoBeta = { + }; + if (typeof this._autofix !== 'undefined') { + data.autofix = this._autofix; + } + if (typeof this._ip !== 'undefined') { + data.ip = this._ip; + } + if (typeof this._port !== 'undefined') { + data.port = this._port; + } + return data; + } + + validate(): string[] { + const validate = { + ip: !this._ip ? true : this._ip.reduce((result, p) => result && typeof p === 'string', true), + port: !this._port ? true : typeof this._port === 'number', + autofix: !this._autofix ? true : typeof this._autofix === 'boolean', + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): CheckConfigRequestInfoBeta { + return new CheckConfigRequestInfoBeta({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/CheckConfigResponse.ts b/client2/src/lib/entities/CheckConfigResponse.ts new file mode 100644 index 00000000..74d45519 --- /dev/null +++ b/client2/src/lib/entities/CheckConfigResponse.ts @@ -0,0 +1,64 @@ +import CheckConfigResponseInfo, { ICheckConfigResponseInfo } from './CheckConfigResponseInfo'; +import CheckConfigStaticIpInfo, { ICheckConfigStaticIpInfo } from './CheckConfigStaticIpInfo'; + +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface ICheckConfigResponse { + dns: ICheckConfigResponseInfo; + static_ip: ICheckConfigStaticIpInfo; + web: ICheckConfigResponseInfo; +} + +export default class CheckConfigResponse { + readonly _dns: CheckConfigResponseInfo; + + get dns(): CheckConfigResponseInfo { + return this._dns; + } + + readonly _static_ip: CheckConfigStaticIpInfo; + + get staticIp(): CheckConfigStaticIpInfo { + return this._static_ip; + } + + readonly _web: CheckConfigResponseInfo; + + get web(): CheckConfigResponseInfo { + return this._web; + } + + constructor(props: ICheckConfigResponse) { + this._dns = new CheckConfigResponseInfo(props.dns); + this._static_ip = new CheckConfigStaticIpInfo(props.static_ip); + this._web = new CheckConfigResponseInfo(props.web); + } + + serialize(): ICheckConfigResponse { + const data: ICheckConfigResponse = { + dns: this._dns.serialize(), + static_ip: this._static_ip.serialize(), + web: this._web.serialize(), + }; + return data; + } + + validate(): string[] { + const validate = { + dns: this._dns.validate().length === 0, + web: this._web.validate().length === 0, + static_ip: this._static_ip.validate().length === 0, + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): CheckConfigResponse { + return new CheckConfigResponse({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/CheckConfigResponseInfo.ts b/client2/src/lib/entities/CheckConfigResponseInfo.ts new file mode 100644 index 00000000..09d9d2a9 --- /dev/null +++ b/client2/src/lib/entities/CheckConfigResponseInfo.ts @@ -0,0 +1,59 @@ +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface ICheckConfigResponseInfo { + can_autofix: boolean; + status: string; +} + +export default class CheckConfigResponseInfo { + readonly _can_autofix: boolean; + + get canAutofix(): boolean { + return this._can_autofix; + } + + static canAutofixValidate(canAutofix: boolean): boolean { + return typeof canAutofix === 'boolean'; + } + + readonly _status: string; + + get status(): string { + return this._status; + } + + static statusValidate(status: string): boolean { + return typeof status === 'string' && !!status.trim(); + } + + constructor(props: ICheckConfigResponseInfo) { + this._can_autofix = props.can_autofix; + this._status = props.status.trim(); + } + + serialize(): ICheckConfigResponseInfo { + const data: ICheckConfigResponseInfo = { + can_autofix: this._can_autofix, + status: this._status, + }; + return data; + } + + validate(): string[] { + const validate = { + status: typeof this._status === 'string' && !this._status ? true : this._status, + can_autofix: typeof this._can_autofix === 'boolean', + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): CheckConfigResponseInfo { + return new CheckConfigResponseInfo({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/CheckConfigStaticIpInfo.ts b/client2/src/lib/entities/CheckConfigStaticIpInfo.ts new file mode 100644 index 00000000..b7c7a34c --- /dev/null +++ b/client2/src/lib/entities/CheckConfigStaticIpInfo.ts @@ -0,0 +1,79 @@ +import { CheckConfigStaticIpInfoStatic } from './CheckConfigStaticIpInfoStatic'; + +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface ICheckConfigStaticIpInfo { + error?: string; + ip?: string; + static?: CheckConfigStaticIpInfoStatic; +} + +export default class CheckConfigStaticIpInfo { + readonly _error: string | undefined; + + /** */ + get error(): string | undefined { + return this._error; + } + + readonly _ip: string | undefined; + + /** + * Description: Current dynamic IP address. Set if static=no + * Example: 192.168.1.1 + */ + get ip(): string | undefined { + return this._ip; + } + + readonly _static: CheckConfigStaticIpInfoStatic | undefined; + + get static(): CheckConfigStaticIpInfoStatic | undefined { + return this._static; + } + + constructor(props: ICheckConfigStaticIpInfo) { + if (typeof props.error === 'string') { + this._error = props.error.trim(); + } + if (typeof props.ip === 'string') { + this._ip = props.ip.trim(); + } + if (props.static) { + this._static = props.static; + } + } + + serialize(): ICheckConfigStaticIpInfo { + const data: ICheckConfigStaticIpInfo = { + }; + if (typeof this._error !== 'undefined') { + data.error = this._error; + } + if (typeof this._ip !== 'undefined') { + data.ip = this._ip; + } + if (typeof this._static !== 'undefined') { + data.static = this._static; + } + return data; + } + + validate(): string[] { + const validate = { + ip: !this._ip ? true : typeof this._ip === 'string' && !this._ip ? true : this._ip, + error: !this._error ? true : typeof this._error === 'string' && !this._error ? true : this._error, + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): CheckConfigStaticIpInfo { + return new CheckConfigStaticIpInfo({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/CheckConfigStaticIpInfoStatic.ts b/client2/src/lib/entities/CheckConfigStaticIpInfoStatic.ts new file mode 100644 index 00000000..9609e1ec --- /dev/null +++ b/client2/src/lib/entities/CheckConfigStaticIpInfoStatic.ts @@ -0,0 +1,7 @@ +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export enum CheckConfigStaticIpInfoStatic { + YES = 'yes', + NO = 'no', + ERROR = 'error' +} diff --git a/client2/src/lib/entities/Client.ts b/client2/src/lib/entities/Client.ts new file mode 100644 index 00000000..c86a9d8f --- /dev/null +++ b/client2/src/lib/entities/Client.ts @@ -0,0 +1,176 @@ +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface IClient { + blocked_services?: string[]; + filtering_enabled?: boolean; + ids?: string[]; + name?: string; + parental_enabled?: boolean; + safebrowsing_enabled?: boolean; + safesearch_enabled?: boolean; + upstreams?: string[]; + use_global_blocked_services?: boolean; + use_global_settings?: boolean; +} + +export default class Client { + readonly _blocked_services: string[] | undefined; + + get blockedServices(): string[] | undefined { + return this._blocked_services; + } + + readonly _filtering_enabled: boolean | undefined; + + get filteringEnabled(): boolean | undefined { + return this._filtering_enabled; + } + + readonly _ids: string[] | undefined; + + /** */ + get ids(): string[] | undefined { + return this._ids; + } + + readonly _name: string | undefined; + + /** + * Description: Name + * Example: localhost + */ + get name(): string | undefined { + return this._name; + } + + readonly _parental_enabled: boolean | undefined; + + get parentalEnabled(): boolean | undefined { + return this._parental_enabled; + } + + readonly _safebrowsing_enabled: boolean | undefined; + + get safebrowsingEnabled(): boolean | undefined { + return this._safebrowsing_enabled; + } + + readonly _safesearch_enabled: boolean | undefined; + + get safesearchEnabled(): boolean | undefined { + return this._safesearch_enabled; + } + + readonly _upstreams: string[] | undefined; + + get upstreams(): string[] | undefined { + return this._upstreams; + } + + readonly _use_global_blocked_services: boolean | undefined; + + get useGlobalBlockedServices(): boolean | undefined { + return this._use_global_blocked_services; + } + + readonly _use_global_settings: boolean | undefined; + + get useGlobalSettings(): boolean | undefined { + return this._use_global_settings; + } + + constructor(props: IClient) { + if (props.blocked_services) { + this._blocked_services = props.blocked_services; + } + if (typeof props.filtering_enabled === 'boolean') { + this._filtering_enabled = props.filtering_enabled; + } + if (props.ids) { + this._ids = props.ids; + } + if (typeof props.name === 'string') { + this._name = props.name.trim(); + } + if (typeof props.parental_enabled === 'boolean') { + this._parental_enabled = props.parental_enabled; + } + if (typeof props.safebrowsing_enabled === 'boolean') { + this._safebrowsing_enabled = props.safebrowsing_enabled; + } + if (typeof props.safesearch_enabled === 'boolean') { + this._safesearch_enabled = props.safesearch_enabled; + } + if (props.upstreams) { + this._upstreams = props.upstreams; + } + if (typeof props.use_global_blocked_services === 'boolean') { + this._use_global_blocked_services = props.use_global_blocked_services; + } + if (typeof props.use_global_settings === 'boolean') { + this._use_global_settings = props.use_global_settings; + } + } + + serialize(): IClient { + const data: IClient = { + }; + if (typeof this._blocked_services !== 'undefined') { + data.blocked_services = this._blocked_services; + } + if (typeof this._filtering_enabled !== 'undefined') { + data.filtering_enabled = this._filtering_enabled; + } + if (typeof this._ids !== 'undefined') { + data.ids = this._ids; + } + if (typeof this._name !== 'undefined') { + data.name = this._name; + } + if (typeof this._parental_enabled !== 'undefined') { + data.parental_enabled = this._parental_enabled; + } + if (typeof this._safebrowsing_enabled !== 'undefined') { + data.safebrowsing_enabled = this._safebrowsing_enabled; + } + if (typeof this._safesearch_enabled !== 'undefined') { + data.safesearch_enabled = this._safesearch_enabled; + } + if (typeof this._upstreams !== 'undefined') { + data.upstreams = this._upstreams; + } + if (typeof this._use_global_blocked_services !== 'undefined') { + data.use_global_blocked_services = this._use_global_blocked_services; + } + if (typeof this._use_global_settings !== 'undefined') { + data.use_global_settings = this._use_global_settings; + } + return data; + } + + validate(): string[] { + const validate = { + name: !this._name ? true : typeof this._name === 'string' && !this._name ? true : this._name, + ids: !this._ids ? true : this._ids.reduce((result, p) => result && typeof p === 'string', true), + use_global_settings: !this._use_global_settings ? true : typeof this._use_global_settings === 'boolean', + filtering_enabled: !this._filtering_enabled ? true : typeof this._filtering_enabled === 'boolean', + parental_enabled: !this._parental_enabled ? true : typeof this._parental_enabled === 'boolean', + safebrowsing_enabled: !this._safebrowsing_enabled ? true : typeof this._safebrowsing_enabled === 'boolean', + safesearch_enabled: !this._safesearch_enabled ? true : typeof this._safesearch_enabled === 'boolean', + use_global_blocked_services: !this._use_global_blocked_services ? true : typeof this._use_global_blocked_services === 'boolean', + blocked_services: !this._blocked_services ? true : this._blocked_services.reduce((result, p) => result && typeof p === 'string', true), + upstreams: !this._upstreams ? true : this._upstreams.reduce((result, p) => result && typeof p === 'string', true), + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): Client { + return new Client({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/ClientAuto.ts b/client2/src/lib/entities/ClientAuto.ts new file mode 100644 index 00000000..2696a4c2 --- /dev/null +++ b/client2/src/lib/entities/ClientAuto.ts @@ -0,0 +1,85 @@ +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface IClientAuto { + ip?: string; + name?: string; + source?: string; +} + +export default class ClientAuto { + readonly _ip: string | undefined; + + /** + * Description: IP address + * Example: 127.0.0.1 + */ + get ip(): string | undefined { + return this._ip; + } + + readonly _name: string | undefined; + + /** + * Description: Name + * Example: localhost + */ + get name(): string | undefined { + return this._name; + } + + readonly _source: string | undefined; + + /** + * Description: The source of this information + * Example: etc/hosts + */ + get source(): string | undefined { + return this._source; + } + + constructor(props: IClientAuto) { + if (typeof props.ip === 'string') { + this._ip = props.ip.trim(); + } + if (typeof props.name === 'string') { + this._name = props.name.trim(); + } + if (typeof props.source === 'string') { + this._source = props.source.trim(); + } + } + + serialize(): IClientAuto { + const data: IClientAuto = { + }; + if (typeof this._ip !== 'undefined') { + data.ip = this._ip; + } + if (typeof this._name !== 'undefined') { + data.name = this._name; + } + if (typeof this._source !== 'undefined') { + data.source = this._source; + } + return data; + } + + validate(): string[] { + const validate = { + ip: !this._ip ? true : typeof this._ip === 'string' && !this._ip ? true : this._ip, + name: !this._name ? true : typeof this._name === 'string' && !this._name ? true : this._name, + source: !this._source ? true : typeof this._source === 'string' && !this._source ? true : this._source, + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): ClientAuto { + return new ClientAuto({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/ClientDelete.ts b/client2/src/lib/entities/ClientDelete.ts new file mode 100644 index 00000000..6a009c03 --- /dev/null +++ b/client2/src/lib/entities/ClientDelete.ts @@ -0,0 +1,45 @@ +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface IClientDelete { + name?: string; +} + +export default class ClientDelete { + readonly _name: string | undefined; + + get name(): string | undefined { + return this._name; + } + + constructor(props: IClientDelete) { + if (typeof props.name === 'string') { + this._name = props.name.trim(); + } + } + + serialize(): IClientDelete { + const data: IClientDelete = { + }; + if (typeof this._name !== 'undefined') { + data.name = this._name; + } + return data; + } + + validate(): string[] { + const validate = { + name: !this._name ? true : typeof this._name === 'string' && !this._name ? true : this._name, + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): ClientDelete { + return new ClientDelete({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/ClientFindSubEntry.ts b/client2/src/lib/entities/ClientFindSubEntry.ts new file mode 100644 index 00000000..670d2da3 --- /dev/null +++ b/client2/src/lib/entities/ClientFindSubEntry.ts @@ -0,0 +1,222 @@ +import WhoisInfo, { IWhoisInfo } from './WhoisInfo'; + +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface IClientFindSubEntry { + blocked_services?: string[]; + disallowed?: boolean; + disallowed_rule?: string; + filtering_enabled?: boolean; + ids?: string[]; + name?: string; + parental_enabled?: boolean; + safebrowsing_enabled?: boolean; + safesearch_enabled?: boolean; + upstreams?: string[]; + use_global_blocked_services?: boolean; + use_global_settings?: boolean; + whois_info?: IWhoisInfo[]; +} + +export default class ClientFindSubEntry { + readonly _blocked_services: string[] | undefined; + + get blockedServices(): string[] | undefined { + return this._blocked_services; + } + + readonly _disallowed: boolean | undefined; + + /** */ + get disallowed(): boolean | undefined { + return this._disallowed; + } + + readonly _disallowed_rule: string | undefined; + + /** */ + get disallowedRule(): string | undefined { + return this._disallowed_rule; + } + + readonly _filtering_enabled: boolean | undefined; + + get filteringEnabled(): boolean | undefined { + return this._filtering_enabled; + } + + readonly _ids: string[] | undefined; + + /** */ + get ids(): string[] | undefined { + return this._ids; + } + + readonly _name: string | undefined; + + /** + * Description: Name + * Example: localhost + */ + get name(): string | undefined { + return this._name; + } + + readonly _parental_enabled: boolean | undefined; + + get parentalEnabled(): boolean | undefined { + return this._parental_enabled; + } + + readonly _safebrowsing_enabled: boolean | undefined; + + get safebrowsingEnabled(): boolean | undefined { + return this._safebrowsing_enabled; + } + + readonly _safesearch_enabled: boolean | undefined; + + get safesearchEnabled(): boolean | undefined { + return this._safesearch_enabled; + } + + readonly _upstreams: string[] | undefined; + + get upstreams(): string[] | undefined { + return this._upstreams; + } + + readonly _use_global_blocked_services: boolean | undefined; + + get useGlobalBlockedServices(): boolean | undefined { + return this._use_global_blocked_services; + } + + readonly _use_global_settings: boolean | undefined; + + get useGlobalSettings(): boolean | undefined { + return this._use_global_settings; + } + + readonly _whois_info: WhoisInfo[] | undefined; + + get whoisInfo(): WhoisInfo[] | undefined { + return this._whois_info; + } + + constructor(props: IClientFindSubEntry) { + if (props.blocked_services) { + this._blocked_services = props.blocked_services; + } + if (typeof props.disallowed === 'boolean') { + this._disallowed = props.disallowed; + } + if (typeof props.disallowed_rule === 'string') { + this._disallowed_rule = props.disallowed_rule.trim(); + } + if (typeof props.filtering_enabled === 'boolean') { + this._filtering_enabled = props.filtering_enabled; + } + if (props.ids) { + this._ids = props.ids; + } + if (typeof props.name === 'string') { + this._name = props.name.trim(); + } + if (typeof props.parental_enabled === 'boolean') { + this._parental_enabled = props.parental_enabled; + } + if (typeof props.safebrowsing_enabled === 'boolean') { + this._safebrowsing_enabled = props.safebrowsing_enabled; + } + if (typeof props.safesearch_enabled === 'boolean') { + this._safesearch_enabled = props.safesearch_enabled; + } + if (props.upstreams) { + this._upstreams = props.upstreams; + } + if (typeof props.use_global_blocked_services === 'boolean') { + this._use_global_blocked_services = props.use_global_blocked_services; + } + if (typeof props.use_global_settings === 'boolean') { + this._use_global_settings = props.use_global_settings; + } + if (props.whois_info) { + this._whois_info = props.whois_info.map((p) => new WhoisInfo(p)); + } + } + + serialize(): IClientFindSubEntry { + const data: IClientFindSubEntry = { + }; + if (typeof this._blocked_services !== 'undefined') { + data.blocked_services = this._blocked_services; + } + if (typeof this._disallowed !== 'undefined') { + data.disallowed = this._disallowed; + } + if (typeof this._disallowed_rule !== 'undefined') { + data.disallowed_rule = this._disallowed_rule; + } + if (typeof this._filtering_enabled !== 'undefined') { + data.filtering_enabled = this._filtering_enabled; + } + if (typeof this._ids !== 'undefined') { + data.ids = this._ids; + } + if (typeof this._name !== 'undefined') { + data.name = this._name; + } + if (typeof this._parental_enabled !== 'undefined') { + data.parental_enabled = this._parental_enabled; + } + if (typeof this._safebrowsing_enabled !== 'undefined') { + data.safebrowsing_enabled = this._safebrowsing_enabled; + } + if (typeof this._safesearch_enabled !== 'undefined') { + data.safesearch_enabled = this._safesearch_enabled; + } + if (typeof this._upstreams !== 'undefined') { + data.upstreams = this._upstreams; + } + if (typeof this._use_global_blocked_services !== 'undefined') { + data.use_global_blocked_services = this._use_global_blocked_services; + } + if (typeof this._use_global_settings !== 'undefined') { + data.use_global_settings = this._use_global_settings; + } + if (typeof this._whois_info !== 'undefined') { + data.whois_info = this._whois_info.map((p) => p.serialize()); + } + return data; + } + + validate(): string[] { + const validate = { + name: !this._name ? true : typeof this._name === 'string' && !this._name ? true : this._name, + ids: !this._ids ? true : this._ids.reduce((result, p) => result && typeof p === 'string', true), + use_global_settings: !this._use_global_settings ? true : typeof this._use_global_settings === 'boolean', + filtering_enabled: !this._filtering_enabled ? true : typeof this._filtering_enabled === 'boolean', + parental_enabled: !this._parental_enabled ? true : typeof this._parental_enabled === 'boolean', + safebrowsing_enabled: !this._safebrowsing_enabled ? true : typeof this._safebrowsing_enabled === 'boolean', + safesearch_enabled: !this._safesearch_enabled ? true : typeof this._safesearch_enabled === 'boolean', + use_global_blocked_services: !this._use_global_blocked_services ? true : typeof this._use_global_blocked_services === 'boolean', + blocked_services: !this._blocked_services ? true : this._blocked_services.reduce((result, p) => result && typeof p === 'string', true), + upstreams: !this._upstreams ? true : this._upstreams.reduce((result, p) => result && typeof p === 'string', true), + whois_info: !this._whois_info ? true : this._whois_info.reduce((result, p) => result && p.validate().length === 0, true), + disallowed: !this._disallowed ? true : typeof this._disallowed === 'boolean', + disallowed_rule: !this._disallowed_rule ? true : typeof this._disallowed_rule === 'string' && !this._disallowed_rule ? true : this._disallowed_rule, + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): ClientFindSubEntry { + return new ClientFindSubEntry({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/ClientUpdate.ts b/client2/src/lib/entities/ClientUpdate.ts new file mode 100644 index 00000000..2a817b3a --- /dev/null +++ b/client2/src/lib/entities/ClientUpdate.ts @@ -0,0 +1,61 @@ +import Client, { IClient } from './Client'; + +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface IClientUpdate { + data?: IClient; + name?: string; +} + +export default class ClientUpdate { + readonly _data: Client | undefined; + + get data(): Client | undefined { + return this._data; + } + + readonly _name: string | undefined; + + get name(): string | undefined { + return this._name; + } + + constructor(props: IClientUpdate) { + if (props.data) { + this._data = new Client(props.data); + } + if (typeof props.name === 'string') { + this._name = props.name.trim(); + } + } + + serialize(): IClientUpdate { + const data: IClientUpdate = { + }; + if (typeof this._data !== 'undefined') { + data.data = this._data.serialize(); + } + if (typeof this._name !== 'undefined') { + data.name = this._name; + } + return data; + } + + validate(): string[] { + const validate = { + name: !this._name ? true : typeof this._name === 'string' && !this._name ? true : this._name, + data: !this._data ? true : this._data.validate().length === 0, + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): ClientUpdate { + return new ClientUpdate({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/Clients.ts b/client2/src/lib/entities/Clients.ts new file mode 100644 index 00000000..8ba5a8e8 --- /dev/null +++ b/client2/src/lib/entities/Clients.ts @@ -0,0 +1,62 @@ +import Client, { IClient } from './Client'; +import ClientAuto, { IClientAuto } from './ClientAuto'; + +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface IClients { + auto_clients?: IClientAuto[]; + clients?: IClient[]; +} + +export default class Clients { + readonly _auto_clients: ClientAuto[] | undefined; + + get autoClients(): ClientAuto[] | undefined { + return this._auto_clients; + } + + readonly _clients: Client[] | undefined; + + get clients(): Client[] | undefined { + return this._clients; + } + + constructor(props: IClients) { + if (props.auto_clients) { + this._auto_clients = props.auto_clients.map((p) => new ClientAuto(p)); + } + if (props.clients) { + this._clients = props.clients.map((p) => new Client(p)); + } + } + + serialize(): IClients { + const data: IClients = { + }; + if (typeof this._auto_clients !== 'undefined') { + data.auto_clients = this._auto_clients.map((p) => p.serialize()); + } + if (typeof this._clients !== 'undefined') { + data.clients = this._clients.map((p) => p.serialize()); + } + return data; + } + + validate(): string[] { + const validate = { + clients: !this._clients ? true : this._clients.reduce((result, p) => result && p.validate().length === 0, true), + auto_clients: !this._auto_clients ? true : this._auto_clients.reduce((result, p) => result && p.validate().length === 0, true), + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): Clients { + return new Clients({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/ClientsArray.ts b/client2/src/lib/entities/ClientsArray.ts new file mode 100644 index 00000000..774e162b --- /dev/null +++ b/client2/src/lib/entities/ClientsArray.ts @@ -0,0 +1,31 @@ +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface IClientsArray { +} + +export default class ClientsArray { + constructor(props: IClientsArray) { + } + + serialize(): IClientsArray { + const data: IClientsArray = { + }; + return data; + } + + validate(): string[] { + const validate = { + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): ClientsArray { + return new ClientsArray({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/ClientsAutoArray.ts b/client2/src/lib/entities/ClientsAutoArray.ts new file mode 100644 index 00000000..855c9e8a --- /dev/null +++ b/client2/src/lib/entities/ClientsAutoArray.ts @@ -0,0 +1,31 @@ +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface IClientsAutoArray { +} + +export default class ClientsAutoArray { + constructor(props: IClientsAutoArray) { + } + + serialize(): IClientsAutoArray { + const data: IClientsAutoArray = { + }; + return data; + } + + validate(): string[] { + const validate = { + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): ClientsAutoArray { + return new ClientsAutoArray({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/ClientsFindEntry.ts b/client2/src/lib/entities/ClientsFindEntry.ts new file mode 100644 index 00000000..9dd3099c --- /dev/null +++ b/client2/src/lib/entities/ClientsFindEntry.ts @@ -0,0 +1,31 @@ +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface IClientsFindEntry { +} + +export default class ClientsFindEntry { + constructor(props: IClientsFindEntry) { + } + + serialize(): IClientsFindEntry { + const data: IClientsFindEntry = { + }; + return data; + } + + validate(): string[] { + const validate = { + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): ClientsFindEntry { + return new ClientsFindEntry({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/ClientsFindResponse.ts b/client2/src/lib/entities/ClientsFindResponse.ts new file mode 100644 index 00000000..63685be6 --- /dev/null +++ b/client2/src/lib/entities/ClientsFindResponse.ts @@ -0,0 +1,31 @@ +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface IClientsFindResponse { +} + +export default class ClientsFindResponse { + constructor(props: IClientsFindResponse) { + } + + serialize(): IClientsFindResponse { + const data: IClientsFindResponse = { + }; + return data; + } + + validate(): string[] { + const validate = { + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): ClientsFindResponse { + return new ClientsFindResponse({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/DNSConfig.ts b/client2/src/lib/entities/DNSConfig.ts new file mode 100644 index 00000000..d56883e5 --- /dev/null +++ b/client2/src/lib/entities/DNSConfig.ts @@ -0,0 +1,250 @@ +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface IDNSConfig { + blocking_ipv4?: string; + blocking_ipv6?: string; + blocking_mode?: string; + bootstrap_dns?: string[]; + cache_size?: number; + cache_ttl_max?: number; + cache_ttl_min?: number; + dhcp_available?: boolean; + dnssec_enabled?: boolean; + edns_cs_enabled?: boolean; + protection_enabled?: boolean; + ratelimit?: number; + upstream_dns?: string[]; + upstream_dns_file?: string; + upstream_mode?: any; +} + +export default class DNSConfig { + readonly _blocking_ipv4: string | undefined; + + get blockingIpv4(): string | undefined { + return this._blocking_ipv4; + } + + readonly _blocking_ipv6: string | undefined; + + get blockingIpv6(): string | undefined { + return this._blocking_ipv6; + } + + readonly _blocking_mode: string | undefined; + + get blockingMode(): string | undefined { + return this._blocking_mode; + } + + readonly _bootstrap_dns: string[] | undefined; + + /** + * Description: Bootstrap servers, port is optional after colon. Empty value will reset it to default values. + * + * Example: 8.8.8.8:53,1.1.1.1:53 + */ + get bootstrapDns(): string[] | undefined { + return this._bootstrap_dns; + } + + readonly _cache_size: number | undefined; + + get cacheSize(): number | undefined { + return this._cache_size; + } + + readonly _cache_ttl_max: number | undefined; + + get cacheTtlMax(): number | undefined { + return this._cache_ttl_max; + } + + readonly _cache_ttl_min: number | undefined; + + get cacheTtlMin(): number | undefined { + return this._cache_ttl_min; + } + + readonly _dhcp_available: boolean | undefined; + + get dhcpAvailable(): boolean | undefined { + return this._dhcp_available; + } + + readonly _dnssec_enabled: boolean | undefined; + + get dnssecEnabled(): boolean | undefined { + return this._dnssec_enabled; + } + + readonly _edns_cs_enabled: boolean | undefined; + + get ednsCsEnabled(): boolean | undefined { + return this._edns_cs_enabled; + } + + readonly _protection_enabled: boolean | undefined; + + get protectionEnabled(): boolean | undefined { + return this._protection_enabled; + } + + readonly _ratelimit: number | undefined; + + get ratelimit(): number | undefined { + return this._ratelimit; + } + + readonly _upstream_dns: string[] | undefined; + + /** + * Description: Upstream servers, port is optional after colon. Empty value will reset it to default values. + * + * Example: tls://1.1.1.1,tls://1.0.0.1 + */ + get upstreamDns(): string[] | undefined { + return this._upstream_dns; + } + + readonly _upstream_dns_file: string | undefined; + + get upstreamDnsFile(): string | undefined { + return this._upstream_dns_file; + } + + readonly _upstream_mode: any | undefined; + + get upstreamMode(): any | undefined { + return this._upstream_mode; + } + + constructor(props: IDNSConfig) { + if (typeof props.blocking_ipv4 === 'string') { + this._blocking_ipv4 = props.blocking_ipv4.trim(); + } + if (typeof props.blocking_ipv6 === 'string') { + this._blocking_ipv6 = props.blocking_ipv6.trim(); + } + if (typeof props.blocking_mode === 'string') { + this._blocking_mode = props.blocking_mode.trim(); + } + if (props.bootstrap_dns) { + this._bootstrap_dns = props.bootstrap_dns; + } + if (typeof props.cache_size === 'number') { + this._cache_size = props.cache_size; + } + if (typeof props.cache_ttl_max === 'number') { + this._cache_ttl_max = props.cache_ttl_max; + } + if (typeof props.cache_ttl_min === 'number') { + this._cache_ttl_min = props.cache_ttl_min; + } + if (typeof props.dhcp_available === 'boolean') { + this._dhcp_available = props.dhcp_available; + } + if (typeof props.dnssec_enabled === 'boolean') { + this._dnssec_enabled = props.dnssec_enabled; + } + if (typeof props.edns_cs_enabled === 'boolean') { + this._edns_cs_enabled = props.edns_cs_enabled; + } + if (typeof props.protection_enabled === 'boolean') { + this._protection_enabled = props.protection_enabled; + } + if (typeof props.ratelimit === 'number') { + this._ratelimit = props.ratelimit; + } + if (props.upstream_dns) { + this._upstream_dns = props.upstream_dns; + } + if (typeof props.upstream_dns_file === 'string') { + this._upstream_dns_file = props.upstream_dns_file.trim(); + } + if (props.upstream_mode) { + this._upstream_mode = props.upstream_mode; + } + } + + serialize(): IDNSConfig { + const data: IDNSConfig = { + }; + if (typeof this._blocking_ipv4 !== 'undefined') { + data.blocking_ipv4 = this._blocking_ipv4; + } + if (typeof this._blocking_ipv6 !== 'undefined') { + data.blocking_ipv6 = this._blocking_ipv6; + } + if (typeof this._blocking_mode !== 'undefined') { + data.blocking_mode = this._blocking_mode; + } + if (typeof this._bootstrap_dns !== 'undefined') { + data.bootstrap_dns = this._bootstrap_dns; + } + if (typeof this._cache_size !== 'undefined') { + data.cache_size = this._cache_size; + } + if (typeof this._cache_ttl_max !== 'undefined') { + data.cache_ttl_max = this._cache_ttl_max; + } + if (typeof this._cache_ttl_min !== 'undefined') { + data.cache_ttl_min = this._cache_ttl_min; + } + if (typeof this._dhcp_available !== 'undefined') { + data.dhcp_available = this._dhcp_available; + } + if (typeof this._dnssec_enabled !== 'undefined') { + data.dnssec_enabled = this._dnssec_enabled; + } + if (typeof this._edns_cs_enabled !== 'undefined') { + data.edns_cs_enabled = this._edns_cs_enabled; + } + if (typeof this._protection_enabled !== 'undefined') { + data.protection_enabled = this._protection_enabled; + } + if (typeof this._ratelimit !== 'undefined') { + data.ratelimit = this._ratelimit; + } + if (typeof this._upstream_dns !== 'undefined') { + data.upstream_dns = this._upstream_dns; + } + if (typeof this._upstream_dns_file !== 'undefined') { + data.upstream_dns_file = this._upstream_dns_file; + } + if (typeof this._upstream_mode !== 'undefined') { + data.upstream_mode = this._upstream_mode; + } + return data; + } + + validate(): string[] { + const validate = { + bootstrap_dns: !this._bootstrap_dns ? true : this._bootstrap_dns.reduce((result, p) => result && typeof p === 'string', true), + upstream_dns: !this._upstream_dns ? true : this._upstream_dns.reduce((result, p) => result && typeof p === 'string', true), + upstream_dns_file: !this._upstream_dns_file ? true : typeof this._upstream_dns_file === 'string' && !this._upstream_dns_file ? true : this._upstream_dns_file, + protection_enabled: !this._protection_enabled ? true : typeof this._protection_enabled === 'boolean', + dhcp_available: !this._dhcp_available ? true : typeof this._dhcp_available === 'boolean', + ratelimit: !this._ratelimit ? true : typeof this._ratelimit === 'number', + blocking_mode: !this._blocking_mode ? true : typeof this._blocking_mode === 'string' && !this._blocking_mode ? true : this._blocking_mode, + blocking_ipv4: !this._blocking_ipv4 ? true : typeof this._blocking_ipv4 === 'string' && !this._blocking_ipv4 ? true : this._blocking_ipv4, + blocking_ipv6: !this._blocking_ipv6 ? true : typeof this._blocking_ipv6 === 'string' && !this._blocking_ipv6 ? true : this._blocking_ipv6, + edns_cs_enabled: !this._edns_cs_enabled ? true : typeof this._edns_cs_enabled === 'boolean', + dnssec_enabled: !this._dnssec_enabled ? true : typeof this._dnssec_enabled === 'boolean', + cache_size: !this._cache_size ? true : typeof this._cache_size === 'number', + cache_ttl_min: !this._cache_ttl_min ? true : typeof this._cache_ttl_min === 'number', + cache_ttl_max: !this._cache_ttl_max ? true : typeof this._cache_ttl_max === 'number', + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): DNSConfig { + return new DNSConfig({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/DhcpConfig.ts b/client2/src/lib/entities/DhcpConfig.ts new file mode 100644 index 00000000..61874da0 --- /dev/null +++ b/client2/src/lib/entities/DhcpConfig.ts @@ -0,0 +1,90 @@ +import DhcpConfigV4, { IDhcpConfigV4 } from './DhcpConfigV4'; +import DhcpConfigV6, { IDhcpConfigV6 } from './DhcpConfigV6'; + +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface IDhcpConfig { + enabled?: boolean; + interface_name?: string; + v4?: IDhcpConfigV4; + v6?: IDhcpConfigV6; +} + +export default class DhcpConfig { + readonly _enabled: boolean | undefined; + + get enabled(): boolean | undefined { + return this._enabled; + } + + readonly _interface_name: string | undefined; + + get interfaceName(): string | undefined { + return this._interface_name; + } + + readonly _v4: DhcpConfigV4 | undefined; + + get v4(): DhcpConfigV4 | undefined { + return this._v4; + } + + readonly _v6: DhcpConfigV6 | undefined; + + get v6(): DhcpConfigV6 | undefined { + return this._v6; + } + + constructor(props: IDhcpConfig) { + if (typeof props.enabled === 'boolean') { + this._enabled = props.enabled; + } + if (typeof props.interface_name === 'string') { + this._interface_name = props.interface_name.trim(); + } + if (props.v4) { + this._v4 = new DhcpConfigV4(props.v4); + } + if (props.v6) { + this._v6 = new DhcpConfigV6(props.v6); + } + } + + serialize(): IDhcpConfig { + const data: IDhcpConfig = { + }; + if (typeof this._enabled !== 'undefined') { + data.enabled = this._enabled; + } + if (typeof this._interface_name !== 'undefined') { + data.interface_name = this._interface_name; + } + if (typeof this._v4 !== 'undefined') { + data.v4 = this._v4.serialize(); + } + if (typeof this._v6 !== 'undefined') { + data.v6 = this._v6.serialize(); + } + return data; + } + + validate(): string[] { + const validate = { + enabled: !this._enabled ? true : typeof this._enabled === 'boolean', + interface_name: !this._interface_name ? true : typeof this._interface_name === 'string' && !this._interface_name ? true : this._interface_name, + v4: !this._v4 ? true : this._v4.validate().length === 0, + v6: !this._v6 ? true : this._v6.validate().length === 0, + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): DhcpConfig { + return new DhcpConfig({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/DhcpConfigV4.ts b/client2/src/lib/entities/DhcpConfigV4.ts new file mode 100644 index 00000000..ae4ebdc2 --- /dev/null +++ b/client2/src/lib/entities/DhcpConfigV4.ts @@ -0,0 +1,117 @@ +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface IDhcpConfigV4 { + gateway_ip?: string; + lease_duration?: number; + range_end?: string; + range_start?: string; + subnet_mask?: string; +} + +export default class DhcpConfigV4 { + readonly _gateway_ip: string | undefined; + + /** + * Description: undefined + * Example: 192.168.1.1 + */ + get gatewayIp(): string | undefined { + return this._gateway_ip; + } + + readonly _lease_duration: number | undefined; + + get leaseDuration(): number | undefined { + return this._lease_duration; + } + + readonly _range_end: string | undefined; + + /** + * Description: undefined + * Example: 192.168.10.50 + */ + get rangeEnd(): string | undefined { + return this._range_end; + } + + readonly _range_start: string | undefined; + + /** + * Description: undefined + * Example: 192.168.1.2 + */ + get rangeStart(): string | undefined { + return this._range_start; + } + + readonly _subnet_mask: string | undefined; + + /** + * Description: undefined + * Example: 255.255.255.0 + */ + get subnetMask(): string | undefined { + return this._subnet_mask; + } + + constructor(props: IDhcpConfigV4) { + if (typeof props.gateway_ip === 'string') { + this._gateway_ip = props.gateway_ip.trim(); + } + if (typeof props.lease_duration === 'number') { + this._lease_duration = props.lease_duration; + } + if (typeof props.range_end === 'string') { + this._range_end = props.range_end.trim(); + } + if (typeof props.range_start === 'string') { + this._range_start = props.range_start.trim(); + } + if (typeof props.subnet_mask === 'string') { + this._subnet_mask = props.subnet_mask.trim(); + } + } + + serialize(): IDhcpConfigV4 { + const data: IDhcpConfigV4 = { + }; + if (typeof this._gateway_ip !== 'undefined') { + data.gateway_ip = this._gateway_ip; + } + if (typeof this._lease_duration !== 'undefined') { + data.lease_duration = this._lease_duration; + } + if (typeof this._range_end !== 'undefined') { + data.range_end = this._range_end; + } + if (typeof this._range_start !== 'undefined') { + data.range_start = this._range_start; + } + if (typeof this._subnet_mask !== 'undefined') { + data.subnet_mask = this._subnet_mask; + } + return data; + } + + validate(): string[] { + const validate = { + gateway_ip: !this._gateway_ip ? true : typeof this._gateway_ip === 'string' && !this._gateway_ip ? true : this._gateway_ip, + subnet_mask: !this._subnet_mask ? true : typeof this._subnet_mask === 'string' && !this._subnet_mask ? true : this._subnet_mask, + range_start: !this._range_start ? true : typeof this._range_start === 'string' && !this._range_start ? true : this._range_start, + range_end: !this._range_end ? true : typeof this._range_end === 'string' && !this._range_end ? true : this._range_end, + lease_duration: !this._lease_duration ? true : typeof this._lease_duration === 'number', + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): DhcpConfigV4 { + return new DhcpConfigV4({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/DhcpConfigV6.ts b/client2/src/lib/entities/DhcpConfigV6.ts new file mode 100644 index 00000000..cf7d6763 --- /dev/null +++ b/client2/src/lib/entities/DhcpConfigV6.ts @@ -0,0 +1,59 @@ +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface IDhcpConfigV6 { + lease_duration?: number; + range_start?: string; +} + +export default class DhcpConfigV6 { + readonly _lease_duration: number | undefined; + + get leaseDuration(): number | undefined { + return this._lease_duration; + } + + readonly _range_start: string | undefined; + + get rangeStart(): string | undefined { + return this._range_start; + } + + constructor(props: IDhcpConfigV6) { + if (typeof props.lease_duration === 'number') { + this._lease_duration = props.lease_duration; + } + if (typeof props.range_start === 'string') { + this._range_start = props.range_start.trim(); + } + } + + serialize(): IDhcpConfigV6 { + const data: IDhcpConfigV6 = { + }; + if (typeof this._lease_duration !== 'undefined') { + data.lease_duration = this._lease_duration; + } + if (typeof this._range_start !== 'undefined') { + data.range_start = this._range_start; + } + return data; + } + + validate(): string[] { + const validate = { + range_start: !this._range_start ? true : typeof this._range_start === 'string' && !this._range_start ? true : this._range_start, + lease_duration: !this._lease_duration ? true : typeof this._lease_duration === 'number', + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): DhcpConfigV6 { + return new DhcpConfigV6({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/DhcpLease.ts b/client2/src/lib/entities/DhcpLease.ts new file mode 100644 index 00000000..b285496b --- /dev/null +++ b/client2/src/lib/entities/DhcpLease.ts @@ -0,0 +1,103 @@ +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface IDhcpLease { + expires: string; + hostname: string; + ip: string; + mac: string; +} + +export default class DhcpLease { + readonly _expires: string; + + /** + * Description: undefined + * Example: 2017-07-21T17:32:28Z + */ + get expires(): string { + return this._expires; + } + + static expiresValidate(expires: string): boolean { + return typeof expires === 'string' && !!expires.trim(); + } + + readonly _hostname: string; + + /** + * Description: undefined + * Example: dell + */ + get hostname(): string { + return this._hostname; + } + + static hostnameValidate(hostname: string): boolean { + return typeof hostname === 'string' && !!hostname.trim(); + } + + readonly _ip: string; + + /** + * Description: undefined + * Example: 192.168.1.22 + */ + get ip(): string { + return this._ip; + } + + static ipValidate(ip: string): boolean { + return typeof ip === 'string' && !!ip.trim(); + } + + readonly _mac: string; + + /** + * Description: undefined + * Example: 00:11:09:b3:b3:b8 + */ + get mac(): string { + return this._mac; + } + + static macValidate(mac: string): boolean { + return typeof mac === 'string' && !!mac.trim(); + } + + constructor(props: IDhcpLease) { + this._expires = props.expires.trim(); + this._hostname = props.hostname.trim(); + this._ip = props.ip.trim(); + this._mac = props.mac.trim(); + } + + serialize(): IDhcpLease { + const data: IDhcpLease = { + expires: this._expires, + hostname: this._hostname, + ip: this._ip, + mac: this._mac, + }; + return data; + } + + validate(): string[] { + const validate = { + mac: typeof this._mac === 'string' && !this._mac ? true : this._mac, + ip: typeof this._ip === 'string' && !this._ip ? true : this._ip, + hostname: typeof this._hostname === 'string' && !this._hostname ? true : this._hostname, + expires: typeof this._expires === 'string' && !this._expires ? true : this._expires, + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): DhcpLease { + return new DhcpLease({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/DhcpSearchResult.ts b/client2/src/lib/entities/DhcpSearchResult.ts new file mode 100644 index 00000000..c168a244 --- /dev/null +++ b/client2/src/lib/entities/DhcpSearchResult.ts @@ -0,0 +1,62 @@ +import DhcpSearchV4, { IDhcpSearchV4 } from './DhcpSearchV4'; +import DhcpSearchV6, { IDhcpSearchV6 } from './DhcpSearchV6'; + +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface IDhcpSearchResult { + v4?: IDhcpSearchV4; + v6?: IDhcpSearchV6; +} + +export default class DhcpSearchResult { + readonly _v4: DhcpSearchV4 | undefined; + + get v4(): DhcpSearchV4 | undefined { + return this._v4; + } + + readonly _v6: DhcpSearchV6 | undefined; + + get v6(): DhcpSearchV6 | undefined { + return this._v6; + } + + constructor(props: IDhcpSearchResult) { + if (props.v4) { + this._v4 = new DhcpSearchV4(props.v4); + } + if (props.v6) { + this._v6 = new DhcpSearchV6(props.v6); + } + } + + serialize(): IDhcpSearchResult { + const data: IDhcpSearchResult = { + }; + if (typeof this._v4 !== 'undefined') { + data.v4 = this._v4.serialize(); + } + if (typeof this._v6 !== 'undefined') { + data.v6 = this._v6.serialize(); + } + return data; + } + + validate(): string[] { + const validate = { + v4: !this._v4 ? true : this._v4.validate().length === 0, + v6: !this._v6 ? true : this._v6.validate().length === 0, + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): DhcpSearchResult { + return new DhcpSearchResult({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/DhcpSearchResultOtherServer.ts b/client2/src/lib/entities/DhcpSearchResultOtherServer.ts new file mode 100644 index 00000000..36f2bc64 --- /dev/null +++ b/client2/src/lib/entities/DhcpSearchResultOtherServer.ts @@ -0,0 +1,64 @@ +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface IDhcpSearchResultOtherServer { + error?: string; + found?: string; +} + +export default class DhcpSearchResultOtherServer { + readonly _error: string | undefined; + + /** */ + get error(): string | undefined { + return this._error; + } + + readonly _found: string | undefined; + + /** + * Description: yes|no|error + * Example: no + */ + get found(): string | undefined { + return this._found; + } + + constructor(props: IDhcpSearchResultOtherServer) { + if (typeof props.error === 'string') { + this._error = props.error.trim(); + } + if (typeof props.found === 'string') { + this._found = props.found.trim(); + } + } + + serialize(): IDhcpSearchResultOtherServer { + const data: IDhcpSearchResultOtherServer = { + }; + if (typeof this._error !== 'undefined') { + data.error = this._error; + } + if (typeof this._found !== 'undefined') { + data.found = this._found; + } + return data; + } + + validate(): string[] { + const validate = { + found: !this._found ? true : typeof this._found === 'string' && !this._found ? true : this._found, + error: !this._error ? true : typeof this._error === 'string' && !this._error ? true : this._error, + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): DhcpSearchResultOtherServer { + return new DhcpSearchResultOtherServer({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/DhcpSearchResultStaticIP.ts b/client2/src/lib/entities/DhcpSearchResultStaticIP.ts new file mode 100644 index 00000000..7b27411a --- /dev/null +++ b/client2/src/lib/entities/DhcpSearchResultStaticIP.ts @@ -0,0 +1,64 @@ +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface IDhcpSearchResultStaticIP { + ip?: string; + static?: string; +} + +export default class DhcpSearchResultStaticIP { + readonly _ip: string | undefined; + + /** */ + get ip(): string | undefined { + return this._ip; + } + + readonly _static: string | undefined; + + /** + * Description: yes|no|error + * Example: yes + */ + get static(): string | undefined { + return this._static; + } + + constructor(props: IDhcpSearchResultStaticIP) { + if (typeof props.ip === 'string') { + this._ip = props.ip.trim(); + } + if (typeof props.static === 'string') { + this._static = props.static.trim(); + } + } + + serialize(): IDhcpSearchResultStaticIP { + const data: IDhcpSearchResultStaticIP = { + }; + if (typeof this._ip !== 'undefined') { + data.ip = this._ip; + } + if (typeof this._static !== 'undefined') { + data.static = this._static; + } + return data; + } + + validate(): string[] { + const validate = { + static: !this._static ? true : typeof this._static === 'string' && !this._static ? true : this._static, + ip: !this._ip ? true : typeof this._ip === 'string' && !this._ip ? true : this._ip, + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): DhcpSearchResultStaticIP { + return new DhcpSearchResultStaticIP({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/DhcpSearchV4.ts b/client2/src/lib/entities/DhcpSearchV4.ts new file mode 100644 index 00000000..01af4419 --- /dev/null +++ b/client2/src/lib/entities/DhcpSearchV4.ts @@ -0,0 +1,62 @@ +import DhcpSearchResultOtherServer, { IDhcpSearchResultOtherServer } from './DhcpSearchResultOtherServer'; +import DhcpSearchResultStaticIP, { IDhcpSearchResultStaticIP } from './DhcpSearchResultStaticIP'; + +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface IDhcpSearchV4 { + other_server?: IDhcpSearchResultOtherServer; + static_ip?: IDhcpSearchResultStaticIP; +} + +export default class DhcpSearchV4 { + readonly _other_server: DhcpSearchResultOtherServer | undefined; + + get otherServer(): DhcpSearchResultOtherServer | undefined { + return this._other_server; + } + + readonly _static_ip: DhcpSearchResultStaticIP | undefined; + + get staticIp(): DhcpSearchResultStaticIP | undefined { + return this._static_ip; + } + + constructor(props: IDhcpSearchV4) { + if (props.other_server) { + this._other_server = new DhcpSearchResultOtherServer(props.other_server); + } + if (props.static_ip) { + this._static_ip = new DhcpSearchResultStaticIP(props.static_ip); + } + } + + serialize(): IDhcpSearchV4 { + const data: IDhcpSearchV4 = { + }; + if (typeof this._other_server !== 'undefined') { + data.other_server = this._other_server.serialize(); + } + if (typeof this._static_ip !== 'undefined') { + data.static_ip = this._static_ip.serialize(); + } + return data; + } + + validate(): string[] { + const validate = { + other_server: !this._other_server ? true : this._other_server.validate().length === 0, + static_ip: !this._static_ip ? true : this._static_ip.validate().length === 0, + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): DhcpSearchV4 { + return new DhcpSearchV4({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/DhcpSearchV6.ts b/client2/src/lib/entities/DhcpSearchV6.ts new file mode 100644 index 00000000..e02134a5 --- /dev/null +++ b/client2/src/lib/entities/DhcpSearchV6.ts @@ -0,0 +1,47 @@ +import DhcpSearchResultOtherServer, { IDhcpSearchResultOtherServer } from './DhcpSearchResultOtherServer'; + +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface IDhcpSearchV6 { + other_server?: IDhcpSearchResultOtherServer; +} + +export default class DhcpSearchV6 { + readonly _other_server: DhcpSearchResultOtherServer | undefined; + + get otherServer(): DhcpSearchResultOtherServer | undefined { + return this._other_server; + } + + constructor(props: IDhcpSearchV6) { + if (props.other_server) { + this._other_server = new DhcpSearchResultOtherServer(props.other_server); + } + } + + serialize(): IDhcpSearchV6 { + const data: IDhcpSearchV6 = { + }; + if (typeof this._other_server !== 'undefined') { + data.other_server = this._other_server.serialize(); + } + return data; + } + + validate(): string[] { + const validate = { + other_server: !this._other_server ? true : this._other_server.validate().length === 0, + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): DhcpSearchV6 { + return new DhcpSearchV6({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/DhcpStaticLease.ts b/client2/src/lib/entities/DhcpStaticLease.ts new file mode 100644 index 00000000..a365f3b6 --- /dev/null +++ b/client2/src/lib/entities/DhcpStaticLease.ts @@ -0,0 +1,85 @@ +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface IDhcpStaticLease { + hostname: string; + ip: string; + mac: string; +} + +export default class DhcpStaticLease { + readonly _hostname: string; + + /** + * Description: undefined + * Example: dell + */ + get hostname(): string { + return this._hostname; + } + + static hostnameValidate(hostname: string): boolean { + return typeof hostname === 'string' && !!hostname.trim(); + } + + readonly _ip: string; + + /** + * Description: undefined + * Example: 192.168.1.22 + */ + get ip(): string { + return this._ip; + } + + static ipValidate(ip: string): boolean { + return typeof ip === 'string' && !!ip.trim(); + } + + readonly _mac: string; + + /** + * Description: undefined + * Example: 00:11:09:b3:b3:b8 + */ + get mac(): string { + return this._mac; + } + + static macValidate(mac: string): boolean { + return typeof mac === 'string' && !!mac.trim(); + } + + constructor(props: IDhcpStaticLease) { + this._hostname = props.hostname.trim(); + this._ip = props.ip.trim(); + this._mac = props.mac.trim(); + } + + serialize(): IDhcpStaticLease { + const data: IDhcpStaticLease = { + hostname: this._hostname, + ip: this._ip, + mac: this._mac, + }; + return data; + } + + validate(): string[] { + const validate = { + mac: typeof this._mac === 'string' && !this._mac ? true : this._mac, + ip: typeof this._ip === 'string' && !this._ip ? true : this._ip, + hostname: typeof this._hostname === 'string' && !this._hostname ? true : this._hostname, + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): DhcpStaticLease { + return new DhcpStaticLease({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/DhcpStatus.ts b/client2/src/lib/entities/DhcpStatus.ts new file mode 100644 index 00000000..604daf95 --- /dev/null +++ b/client2/src/lib/entities/DhcpStatus.ts @@ -0,0 +1,116 @@ +import DhcpConfigV4, { IDhcpConfigV4 } from './DhcpConfigV4'; +import DhcpConfigV6, { IDhcpConfigV6 } from './DhcpConfigV6'; +import DhcpLease, { IDhcpLease } from './DhcpLease'; +import DhcpStaticLease, { IDhcpStaticLease } from './DhcpStaticLease'; + +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface IDhcpStatus { + enabled?: boolean; + interface_name?: string; + leases: IDhcpLease[]; + static_leases?: IDhcpStaticLease[]; + v4?: IDhcpConfigV4; + v6?: IDhcpConfigV6; +} + +export default class DhcpStatus { + readonly _enabled: boolean | undefined; + + get enabled(): boolean | undefined { + return this._enabled; + } + + readonly _interface_name: string | undefined; + + get interfaceName(): string | undefined { + return this._interface_name; + } + + readonly _leases: DhcpLease[]; + + get leases(): DhcpLease[] { + return this._leases; + } + + readonly _static_leases: DhcpStaticLease[] | undefined; + + get staticLeases(): DhcpStaticLease[] | undefined { + return this._static_leases; + } + + readonly _v4: DhcpConfigV4 | undefined; + + get v4(): DhcpConfigV4 | undefined { + return this._v4; + } + + readonly _v6: DhcpConfigV6 | undefined; + + get v6(): DhcpConfigV6 | undefined { + return this._v6; + } + + constructor(props: IDhcpStatus) { + if (typeof props.enabled === 'boolean') { + this._enabled = props.enabled; + } + if (typeof props.interface_name === 'string') { + this._interface_name = props.interface_name.trim(); + } + this._leases = props.leases.map((p) => new DhcpLease(p)); + if (props.static_leases) { + this._static_leases = props.static_leases.map((p) => new DhcpStaticLease(p)); + } + if (props.v4) { + this._v4 = new DhcpConfigV4(props.v4); + } + if (props.v6) { + this._v6 = new DhcpConfigV6(props.v6); + } + } + + serialize(): IDhcpStatus { + const data: IDhcpStatus = { + leases: this._leases.map((p) => p.serialize()), + }; + if (typeof this._enabled !== 'undefined') { + data.enabled = this._enabled; + } + if (typeof this._interface_name !== 'undefined') { + data.interface_name = this._interface_name; + } + if (typeof this._static_leases !== 'undefined') { + data.static_leases = this._static_leases.map((p) => p.serialize()); + } + if (typeof this._v4 !== 'undefined') { + data.v4 = this._v4.serialize(); + } + if (typeof this._v6 !== 'undefined') { + data.v6 = this._v6.serialize(); + } + return data; + } + + validate(): string[] { + const validate = { + enabled: !this._enabled ? true : typeof this._enabled === 'boolean', + interface_name: !this._interface_name ? true : typeof this._interface_name === 'string' && !this._interface_name ? true : this._interface_name, + v4: !this._v4 ? true : this._v4.validate().length === 0, + v6: !this._v6 ? true : this._v6.validate().length === 0, + leases: this._leases.reduce((result, p) => result && p.validate().length === 0, true), + static_leases: !this._static_leases ? true : this._static_leases.reduce((result, p) => result && p.validate().length === 0, true), + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): DhcpStatus { + return new DhcpStatus({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/DnsAnswer.ts b/client2/src/lib/entities/DnsAnswer.ts new file mode 100644 index 00000000..d3351be3 --- /dev/null +++ b/client2/src/lib/entities/DnsAnswer.ts @@ -0,0 +1,85 @@ +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface IDnsAnswer { + ttl?: number; + type?: string; + value?: string; +} + +export default class DnsAnswer { + readonly _ttl: number | undefined; + + /** + * Description: undefined + * Example: 55 + */ + get ttl(): number | undefined { + return this._ttl; + } + + readonly _type: string | undefined; + + /** + * Description: undefined + * Example: A + */ + get type(): string | undefined { + return this._type; + } + + readonly _value: string | undefined; + + /** + * Description: undefined + * Example: 217.69.139.201 + */ + get value(): string | undefined { + return this._value; + } + + constructor(props: IDnsAnswer) { + if (typeof props.ttl === 'number') { + this._ttl = props.ttl; + } + if (typeof props.type === 'string') { + this._type = props.type.trim(); + } + if (typeof props.value === 'string') { + this._value = props.value.trim(); + } + } + + serialize(): IDnsAnswer { + const data: IDnsAnswer = { + }; + if (typeof this._ttl !== 'undefined') { + data.ttl = this._ttl; + } + if (typeof this._type !== 'undefined') { + data.type = this._type; + } + if (typeof this._value !== 'undefined') { + data.value = this._value; + } + return data; + } + + validate(): string[] { + const validate = { + ttl: !this._ttl ? true : typeof this._ttl === 'number', + type: !this._type ? true : typeof this._type === 'string' && !this._type ? true : this._type, + value: !this._value ? true : typeof this._value === 'string' && !this._value ? true : this._value, + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): DnsAnswer { + return new DnsAnswer({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/DnsQuestion.ts b/client2/src/lib/entities/DnsQuestion.ts new file mode 100644 index 00000000..42849b17 --- /dev/null +++ b/client2/src/lib/entities/DnsQuestion.ts @@ -0,0 +1,85 @@ +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface IDnsQuestion { + class?: string; + host?: string; + type?: string; +} + +export default class DnsQuestion { + readonly _class: string | undefined; + + /** + * Description: undefined + * Example: IN + */ + get class(): string | undefined { + return this._class; + } + + readonly _host: string | undefined; + + /** + * Description: undefined + * Example: example.org + */ + get host(): string | undefined { + return this._host; + } + + readonly _type: string | undefined; + + /** + * Description: undefined + * Example: A + */ + get type(): string | undefined { + return this._type; + } + + constructor(props: IDnsQuestion) { + if (typeof props.class === 'string') { + this._class = props.class.trim(); + } + if (typeof props.host === 'string') { + this._host = props.host.trim(); + } + if (typeof props.type === 'string') { + this._type = props.type.trim(); + } + } + + serialize(): IDnsQuestion { + const data: IDnsQuestion = { + }; + if (typeof this._class !== 'undefined') { + data.class = this._class; + } + if (typeof this._host !== 'undefined') { + data.host = this._host; + } + if (typeof this._type !== 'undefined') { + data.type = this._type; + } + return data; + } + + validate(): string[] { + const validate = { + class: !this._class ? true : typeof this._class === 'string' && !this._class ? true : this._class, + host: !this._host ? true : typeof this._host === 'string' && !this._host ? true : this._host, + type: !this._type ? true : typeof this._type === 'string' && !this._type ? true : this._type, + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): DnsQuestion { + return new DnsQuestion({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/Error.ts b/client2/src/lib/entities/Error.ts new file mode 100644 index 00000000..79d16b5c --- /dev/null +++ b/client2/src/lib/entities/Error.ts @@ -0,0 +1,46 @@ +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface IError { + message?: string; +} + +export default class Error { + readonly _message: string | undefined; + + /** */ + get message(): string | undefined { + return this._message; + } + + constructor(props: IError) { + if (typeof props.message === 'string') { + this._message = props.message.trim(); + } + } + + serialize(): IError { + const data: IError = { + }; + if (typeof this._message !== 'undefined') { + data.message = this._message; + } + return data; + } + + validate(): string[] { + const validate = { + message: !this._message ? true : typeof this._message === 'string' && !this._message ? true : this._message, + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): Error { + return new Error({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/Filter.ts b/client2/src/lib/entities/Filter.ts new file mode 100644 index 00000000..71255c8c --- /dev/null +++ b/client2/src/lib/entities/Filter.ts @@ -0,0 +1,136 @@ +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface IFilter { + enabled: boolean; + id: number; + lastUpdated: string; + name: string; + rulesCount: number; + url: string; +} + +export default class Filter { + readonly _enabled: boolean; + + get enabled(): boolean { + return this._enabled; + } + + static enabledValidate(enabled: boolean): boolean { + return typeof enabled === 'boolean'; + } + + readonly _id: number; + + /** + * Description: undefined + * Example: 1234 + */ + get id(): number { + return this._id; + } + + static idValidate(id: number): boolean { + return typeof id === 'number'; + } + + readonly _lastUpdated: string; + + /** + * Description: undefined + * Example: 2018-10-30T12:18:57+03:00 + */ + get lastUpdated(): string { + return this._lastUpdated; + } + + static lastUpdatedValidate(lastUpdated: string): boolean { + return typeof lastUpdated === 'string' && !!lastUpdated.trim(); + } + + readonly _name: string; + + /** + * Description: undefined + * Example: AdGuard Simplified Domain Names filter + */ + get name(): string { + return this._name; + } + + static nameValidate(name: string): boolean { + return typeof name === 'string' && !!name.trim(); + } + + readonly _rulesCount: number; + + /** + * Description: undefined + * Example: 5912 + */ + get rulesCount(): number { + return this._rulesCount; + } + + static rulesCountValidate(rulesCount: number): boolean { + return typeof rulesCount === 'number'; + } + + readonly _url: string; + + /** + * Description: undefined + * Example: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt + * + */ + get url(): string { + return this._url; + } + + static urlValidate(url: string): boolean { + return typeof url === 'string' && !!url.trim(); + } + + constructor(props: IFilter) { + this._enabled = props.enabled; + this._id = props.id; + this._lastUpdated = props.lastUpdated.trim(); + this._name = props.name.trim(); + this._rulesCount = props.rulesCount; + this._url = props.url.trim(); + } + + serialize(): IFilter { + const data: IFilter = { + enabled: this._enabled, + id: this._id, + lastUpdated: this._lastUpdated, + name: this._name, + rulesCount: this._rulesCount, + url: this._url, + }; + return data; + } + + validate(): string[] { + const validate = { + enabled: typeof this._enabled === 'boolean', + id: typeof this._id === 'number', + lastUpdated: typeof this._lastUpdated === 'string' && !this._lastUpdated ? true : this._lastUpdated, + name: typeof this._name === 'string' && !this._name ? true : this._name, + rulesCount: typeof this._rulesCount === 'number', + url: typeof this._url === 'string' && !this._url ? true : this._url, + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): Filter { + return new Filter({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/FilterCheckHostResponse.ts b/client2/src/lib/entities/FilterCheckHostResponse.ts new file mode 100644 index 00000000..2f0daa9a --- /dev/null +++ b/client2/src/lib/entities/FilterCheckHostResponse.ts @@ -0,0 +1,143 @@ +import ResultRule, { IResultRule } from './ResultRule'; + +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface IFilterCheckHostResponse { + cname?: string; + filter_id?: number; + ip_addrs?: string[]; + reason?: string; + rule?: string; + rules?: IResultRule[]; + service_name?: string; +} + +export default class FilterCheckHostResponse { + readonly _cname: string | undefined; + + /** */ + get cname(): string | undefined { + return this._cname; + } + + readonly _filter_id: number | undefined; + + /** */ + get filterId(): number | undefined { + return this._filter_id; + } + + readonly _ip_addrs: string[] | undefined; + + /** */ + get ipAddrs(): string[] | undefined { + return this._ip_addrs; + } + + readonly _reason: string | undefined; + + /** */ + get reason(): string | undefined { + return this._reason; + } + + readonly _rule: string | undefined; + + /** + * Description: Filtering rule applied to the request (if any). + * Deprecated: use `rules[*].text` instead. + * + * Example: ||example.org^ + */ + get rule(): string | undefined { + return this._rule; + } + + readonly _rules: ResultRule[] | undefined; + + /** */ + get rules(): ResultRule[] | undefined { + return this._rules; + } + + readonly _service_name: string | undefined; + + /** */ + get serviceName(): string | undefined { + return this._service_name; + } + + constructor(props: IFilterCheckHostResponse) { + if (typeof props.cname === 'string') { + this._cname = props.cname.trim(); + } + if (typeof props.filter_id === 'number') { + this._filter_id = props.filter_id; + } + if (props.ip_addrs) { + this._ip_addrs = props.ip_addrs; + } + if (typeof props.reason === 'string') { + this._reason = props.reason.trim(); + } + if (typeof props.rule === 'string') { + this._rule = props.rule.trim(); + } + if (props.rules) { + this._rules = props.rules.map((p) => new ResultRule(p)); + } + if (typeof props.service_name === 'string') { + this._service_name = props.service_name.trim(); + } + } + + serialize(): IFilterCheckHostResponse { + const data: IFilterCheckHostResponse = { + }; + if (typeof this._cname !== 'undefined') { + data.cname = this._cname; + } + if (typeof this._filter_id !== 'undefined') { + data.filter_id = this._filter_id; + } + if (typeof this._ip_addrs !== 'undefined') { + data.ip_addrs = this._ip_addrs; + } + if (typeof this._reason !== 'undefined') { + data.reason = this._reason; + } + if (typeof this._rule !== 'undefined') { + data.rule = this._rule; + } + if (typeof this._rules !== 'undefined') { + data.rules = this._rules.map((p) => p.serialize()); + } + if (typeof this._service_name !== 'undefined') { + data.service_name = this._service_name; + } + return data; + } + + validate(): string[] { + const validate = { + reason: !this._reason ? true : typeof this._reason === 'string' && !this._reason ? true : this._reason, + filter_id: !this._filter_id ? true : typeof this._filter_id === 'number', + rule: !this._rule ? true : typeof this._rule === 'string' && !this._rule ? true : this._rule, + rules: !this._rules ? true : this._rules.reduce((result, p) => result && p.validate().length === 0, true), + service_name: !this._service_name ? true : typeof this._service_name === 'string' && !this._service_name ? true : this._service_name, + cname: !this._cname ? true : typeof this._cname === 'string' && !this._cname ? true : this._cname, + ip_addrs: !this._ip_addrs ? true : this._ip_addrs.reduce((result, p) => result && typeof p === 'string', true), + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): FilterCheckHostResponse { + return new FilterCheckHostResponse({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/FilterConfig.ts b/client2/src/lib/entities/FilterConfig.ts new file mode 100644 index 00000000..b1691de1 --- /dev/null +++ b/client2/src/lib/entities/FilterConfig.ts @@ -0,0 +1,59 @@ +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface IFilterConfig { + enabled?: boolean; + interval?: number; +} + +export default class FilterConfig { + readonly _enabled: boolean | undefined; + + get enabled(): boolean | undefined { + return this._enabled; + } + + readonly _interval: number | undefined; + + get interval(): number | undefined { + return this._interval; + } + + constructor(props: IFilterConfig) { + if (typeof props.enabled === 'boolean') { + this._enabled = props.enabled; + } + if (typeof props.interval === 'number') { + this._interval = props.interval; + } + } + + serialize(): IFilterConfig { + const data: IFilterConfig = { + }; + if (typeof this._enabled !== 'undefined') { + data.enabled = this._enabled; + } + if (typeof this._interval !== 'undefined') { + data.interval = this._interval; + } + return data; + } + + validate(): string[] { + const validate = { + enabled: !this._enabled ? true : typeof this._enabled === 'boolean', + interval: !this._interval ? true : typeof this._interval === 'number', + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): FilterConfig { + return new FilterConfig({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/FilterRefreshRequest.ts b/client2/src/lib/entities/FilterRefreshRequest.ts new file mode 100644 index 00000000..3b274ddb --- /dev/null +++ b/client2/src/lib/entities/FilterRefreshRequest.ts @@ -0,0 +1,45 @@ +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface IFilterRefreshRequest { + whitelist?: boolean; +} + +export default class FilterRefreshRequest { + readonly _whitelist: boolean | undefined; + + get whitelist(): boolean | undefined { + return this._whitelist; + } + + constructor(props: IFilterRefreshRequest) { + if (typeof props.whitelist === 'boolean') { + this._whitelist = props.whitelist; + } + } + + serialize(): IFilterRefreshRequest { + const data: IFilterRefreshRequest = { + }; + if (typeof this._whitelist !== 'undefined') { + data.whitelist = this._whitelist; + } + return data; + } + + validate(): string[] { + const validate = { + whitelist: !this._whitelist ? true : typeof this._whitelist === 'boolean', + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): FilterRefreshRequest { + return new FilterRefreshRequest({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/FilterRefreshResponse.ts b/client2/src/lib/entities/FilterRefreshResponse.ts new file mode 100644 index 00000000..96d45b09 --- /dev/null +++ b/client2/src/lib/entities/FilterRefreshResponse.ts @@ -0,0 +1,45 @@ +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface IFilterRefreshResponse { + updated?: number; +} + +export default class FilterRefreshResponse { + readonly _updated: number | undefined; + + get updated(): number | undefined { + return this._updated; + } + + constructor(props: IFilterRefreshResponse) { + if (typeof props.updated === 'number') { + this._updated = props.updated; + } + } + + serialize(): IFilterRefreshResponse { + const data: IFilterRefreshResponse = { + }; + if (typeof this._updated !== 'undefined') { + data.updated = this._updated; + } + return data; + } + + validate(): string[] { + const validate = { + updated: !this._updated ? true : typeof this._updated === 'number', + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): FilterRefreshResponse { + return new FilterRefreshResponse({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/FilterSetUrl.ts b/client2/src/lib/entities/FilterSetUrl.ts new file mode 100644 index 00000000..92eccfc7 --- /dev/null +++ b/client2/src/lib/entities/FilterSetUrl.ts @@ -0,0 +1,72 @@ +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface IFilterSetUrl { + data?: any; + url?: string; + whitelist?: boolean; +} + +export default class FilterSetUrl { + readonly _data: any | undefined; + + get data(): any | undefined { + return this._data; + } + + readonly _url: string | undefined; + + get url(): string | undefined { + return this._url; + } + + readonly _whitelist: boolean | undefined; + + get whitelist(): boolean | undefined { + return this._whitelist; + } + + constructor(props: IFilterSetUrl) { + if (props.data) { + this._data = props.data; + } + if (typeof props.url === 'string') { + this._url = props.url.trim(); + } + if (typeof props.whitelist === 'boolean') { + this._whitelist = props.whitelist; + } + } + + serialize(): IFilterSetUrl { + const data: IFilterSetUrl = { + }; + if (typeof this._data !== 'undefined') { + data.data = this._data; + } + if (typeof this._url !== 'undefined') { + data.url = this._url; + } + if (typeof this._whitelist !== 'undefined') { + data.whitelist = this._whitelist; + } + return data; + } + + validate(): string[] { + const validate = { + url: !this._url ? true : typeof this._url === 'string' && !this._url ? true : this._url, + whitelist: !this._whitelist ? true : typeof this._whitelist === 'boolean', + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): FilterSetUrl { + return new FilterSetUrl({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/FilterStatus.ts b/client2/src/lib/entities/FilterStatus.ts new file mode 100644 index 00000000..be63d512 --- /dev/null +++ b/client2/src/lib/entities/FilterStatus.ts @@ -0,0 +1,89 @@ +import Filter, { IFilter } from './Filter'; + +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface IFilterStatus { + enabled?: boolean; + filters?: IFilter[]; + interval?: number; + user_rules?: string[]; +} + +export default class FilterStatus { + readonly _enabled: boolean | undefined; + + get enabled(): boolean | undefined { + return this._enabled; + } + + readonly _filters: Filter[] | undefined; + + get filters(): Filter[] | undefined { + return this._filters; + } + + readonly _interval: number | undefined; + + get interval(): number | undefined { + return this._interval; + } + + readonly _user_rules: string[] | undefined; + + get userRules(): string[] | undefined { + return this._user_rules; + } + + constructor(props: IFilterStatus) { + if (typeof props.enabled === 'boolean') { + this._enabled = props.enabled; + } + if (props.filters) { + this._filters = props.filters.map((p) => new Filter(p)); + } + if (typeof props.interval === 'number') { + this._interval = props.interval; + } + if (props.user_rules) { + this._user_rules = props.user_rules; + } + } + + serialize(): IFilterStatus { + const data: IFilterStatus = { + }; + if (typeof this._enabled !== 'undefined') { + data.enabled = this._enabled; + } + if (typeof this._filters !== 'undefined') { + data.filters = this._filters.map((p) => p.serialize()); + } + if (typeof this._interval !== 'undefined') { + data.interval = this._interval; + } + if (typeof this._user_rules !== 'undefined') { + data.user_rules = this._user_rules; + } + return data; + } + + validate(): string[] { + const validate = { + enabled: !this._enabled ? true : typeof this._enabled === 'boolean', + interval: !this._interval ? true : typeof this._interval === 'number', + filters: !this._filters ? true : this._filters.reduce((result, p) => result && p.validate().length === 0, true), + user_rules: !this._user_rules ? true : this._user_rules.reduce((result, p) => result && typeof p === 'string', true), + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): FilterStatus { + return new FilterStatus({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/GetVersionRequest.ts b/client2/src/lib/entities/GetVersionRequest.ts new file mode 100644 index 00000000..8677bb11 --- /dev/null +++ b/client2/src/lib/entities/GetVersionRequest.ts @@ -0,0 +1,46 @@ +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface IGetVersionRequest { + recheck_now?: boolean; +} + +export default class GetVersionRequest { + readonly _recheck_now: boolean | undefined; + + /** */ + get recheckNow(): boolean | undefined { + return this._recheck_now; + } + + constructor(props: IGetVersionRequest) { + if (typeof props.recheck_now === 'boolean') { + this._recheck_now = props.recheck_now; + } + } + + serialize(): IGetVersionRequest { + const data: IGetVersionRequest = { + }; + if (typeof this._recheck_now !== 'undefined') { + data.recheck_now = this._recheck_now; + } + return data; + } + + validate(): string[] { + const validate = { + recheck_now: !this._recheck_now ? true : typeof this._recheck_now === 'boolean', + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): GetVersionRequest { + return new GetVersionRequest({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/InitialConfiguration.ts b/client2/src/lib/entities/InitialConfiguration.ts new file mode 100644 index 00000000..1bb32c3c --- /dev/null +++ b/client2/src/lib/entities/InitialConfiguration.ts @@ -0,0 +1,89 @@ +import AddressInfo, { IAddressInfo } from './AddressInfo'; + +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface IInitialConfiguration { + dns: IAddressInfo; + password: string; + username: string; + web: IAddressInfo; +} + +export default class InitialConfiguration { + readonly _dns: AddressInfo; + + get dns(): AddressInfo { + return this._dns; + } + + readonly _password: string; + + /** + * Description: Basic auth password + * Example: password + */ + get password(): string { + return this._password; + } + + static passwordValidate(password: string): boolean { + return typeof password === 'string' && !!password.trim(); + } + + readonly _username: string; + + /** + * Description: Basic auth username + * Example: admin + */ + get username(): string { + return this._username; + } + + static usernameValidate(username: string): boolean { + return typeof username === 'string' && !!username.trim(); + } + + readonly _web: AddressInfo; + + get web(): AddressInfo { + return this._web; + } + + constructor(props: IInitialConfiguration) { + this._dns = new AddressInfo(props.dns); + this._password = props.password.trim(); + this._username = props.username.trim(); + this._web = new AddressInfo(props.web); + } + + serialize(): IInitialConfiguration { + const data: IInitialConfiguration = { + dns: this._dns.serialize(), + password: this._password, + username: this._username, + web: this._web.serialize(), + }; + return data; + } + + validate(): string[] { + const validate = { + dns: this._dns.validate().length === 0, + web: this._web.validate().length === 0, + username: typeof this._username === 'string' && !this._username ? true : this._username, + password: typeof this._password === 'string' && !this._password ? true : this._password, + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): InitialConfiguration { + return new InitialConfiguration({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/InitialConfigurationBeta.ts b/client2/src/lib/entities/InitialConfigurationBeta.ts new file mode 100644 index 00000000..bbf09595 --- /dev/null +++ b/client2/src/lib/entities/InitialConfigurationBeta.ts @@ -0,0 +1,89 @@ +import AddressInfoBeta, { IAddressInfoBeta } from './AddressInfoBeta'; + +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface IInitialConfigurationBeta { + dns: IAddressInfoBeta; + password: string; + username: string; + web: IAddressInfoBeta; +} + +export default class InitialConfigurationBeta { + readonly _dns: AddressInfoBeta; + + get dns(): AddressInfoBeta { + return this._dns; + } + + readonly _password: string; + + /** + * Description: Basic auth password + * Example: password + */ + get password(): string { + return this._password; + } + + static passwordValidate(password: string): boolean { + return typeof password === 'string' && !!password.trim(); + } + + readonly _username: string; + + /** + * Description: Basic auth username + * Example: admin + */ + get username(): string { + return this._username; + } + + static usernameValidate(username: string): boolean { + return typeof username === 'string' && !!username.trim(); + } + + readonly _web: AddressInfoBeta; + + get web(): AddressInfoBeta { + return this._web; + } + + constructor(props: IInitialConfigurationBeta) { + this._dns = new AddressInfoBeta(props.dns); + this._password = props.password.trim(); + this._username = props.username.trim(); + this._web = new AddressInfoBeta(props.web); + } + + serialize(): IInitialConfigurationBeta { + const data: IInitialConfigurationBeta = { + dns: this._dns.serialize(), + password: this._password, + username: this._username, + web: this._web.serialize(), + }; + return data; + } + + validate(): string[] { + const validate = { + dns: this._dns.validate().length === 0, + web: this._web.validate().length === 0, + username: typeof this._username === 'string' && !this._username ? true : this._username, + password: typeof this._password === 'string' && !this._password ? true : this._password, + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): InitialConfigurationBeta { + return new InitialConfigurationBeta({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/Login.ts b/client2/src/lib/entities/Login.ts new file mode 100644 index 00000000..9b642f45 --- /dev/null +++ b/client2/src/lib/entities/Login.ts @@ -0,0 +1,61 @@ +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface ILogin { + password?: string; + username?: string; +} + +export default class Login { + readonly _password: string | undefined; + + /** */ + get password(): string | undefined { + return this._password; + } + + readonly _username: string | undefined; + + /** */ + get username(): string | undefined { + return this._username; + } + + constructor(props: ILogin) { + if (typeof props.password === 'string') { + this._password = props.password.trim(); + } + if (typeof props.username === 'string') { + this._username = props.username.trim(); + } + } + + serialize(): ILogin { + const data: ILogin = { + }; + if (typeof this._password !== 'undefined') { + data.password = this._password; + } + if (typeof this._username !== 'undefined') { + data.username = this._username; + } + return data; + } + + validate(): string[] { + const validate = { + username: !this._username ? true : typeof this._username === 'string' && !this._username ? true : this._username, + password: !this._password ? true : typeof this._password === 'string' && !this._password ? true : this._password, + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): Login { + return new Login({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/NetInterface.ts b/client2/src/lib/entities/NetInterface.ts new file mode 100644 index 00000000..3f3d6e3e --- /dev/null +++ b/client2/src/lib/entities/NetInterface.ts @@ -0,0 +1,113 @@ +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface INetInterface { + flags: string; + hardware_address: string; + ip_addresses?: string[]; + mtu: number; + name: string; +} + +export default class NetInterface { + readonly _flags: string; + + /** + * Description: undefined + * Example: up|broadcast|multicast + */ + get flags(): string { + return this._flags; + } + + static flagsValidate(flags: string): boolean { + return typeof flags === 'string' && !!flags.trim(); + } + + readonly _hardware_address: string; + + /** + * Description: undefined + * Example: 52:54:00:11:09:ba + */ + get hardwareAddress(): string { + return this._hardware_address; + } + + static hardwareAddressValidate(hardwareAddress: string): boolean { + return typeof hardwareAddress === 'string' && !!hardwareAddress.trim(); + } + + readonly _ip_addresses: string[] | undefined; + + get ipAddresses(): string[] | undefined { + return this._ip_addresses; + } + + readonly _mtu: number; + + get mtu(): number { + return this._mtu; + } + + static mtuValidate(mtu: number): boolean { + return typeof mtu === 'number'; + } + + readonly _name: string; + + /** + * Description: undefined + * Example: eth0 + */ + get name(): string { + return this._name; + } + + static nameValidate(name: string): boolean { + return typeof name === 'string' && !!name.trim(); + } + + constructor(props: INetInterface) { + this._flags = props.flags.trim(); + this._hardware_address = props.hardware_address.trim(); + if (props.ip_addresses) { + this._ip_addresses = props.ip_addresses; + } + this._mtu = props.mtu; + this._name = props.name.trim(); + } + + serialize(): INetInterface { + const data: INetInterface = { + flags: this._flags, + hardware_address: this._hardware_address, + mtu: this._mtu, + name: this._name, + }; + if (typeof this._ip_addresses !== 'undefined') { + data.ip_addresses = this._ip_addresses; + } + return data; + } + + validate(): string[] { + const validate = { + flags: typeof this._flags === 'string' && !this._flags ? true : this._flags, + hardware_address: typeof this._hardware_address === 'string' && !this._hardware_address ? true : this._hardware_address, + name: typeof this._name === 'string' && !this._name ? true : this._name, + ip_addresses: !this._ip_addresses ? true : this._ip_addresses.reduce((result, p) => result && typeof p === 'string', true), + mtu: typeof this._mtu === 'number', + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): NetInterface { + return new NetInterface({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/ProfileInfo.ts b/client2/src/lib/entities/ProfileInfo.ts new file mode 100644 index 00000000..0a8f099c --- /dev/null +++ b/client2/src/lib/entities/ProfileInfo.ts @@ -0,0 +1,45 @@ +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface IProfileInfo { + name?: string; +} + +export default class ProfileInfo { + readonly _name: string | undefined; + + get name(): string | undefined { + return this._name; + } + + constructor(props: IProfileInfo) { + if (typeof props.name === 'string') { + this._name = props.name.trim(); + } + } + + serialize(): IProfileInfo { + const data: IProfileInfo = { + }; + if (typeof this._name !== 'undefined') { + data.name = this._name; + } + return data; + } + + validate(): string[] { + const validate = { + name: !this._name ? true : typeof this._name === 'string' && !this._name ? true : this._name, + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): ProfileInfo { + return new ProfileInfo({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/QueryLog.ts b/client2/src/lib/entities/QueryLog.ts new file mode 100644 index 00000000..da2b7761 --- /dev/null +++ b/client2/src/lib/entities/QueryLog.ts @@ -0,0 +1,65 @@ +import QueryLogItem, { IQueryLogItem } from './QueryLogItem'; + +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface IQueryLog { + data?: IQueryLogItem[]; + oldest?: string; +} + +export default class QueryLog { + readonly _data: QueryLogItem[] | undefined; + + get data(): QueryLogItem[] | undefined { + return this._data; + } + + readonly _oldest: string | undefined; + + /** + * Description: undefined + * Example: 2018-11-26T00:02:41+03:00 + */ + get oldest(): string | undefined { + return this._oldest; + } + + constructor(props: IQueryLog) { + if (props.data) { + this._data = props.data.map((p) => new QueryLogItem(p)); + } + if (typeof props.oldest === 'string') { + this._oldest = props.oldest.trim(); + } + } + + serialize(): IQueryLog { + const data: IQueryLog = { + }; + if (typeof this._data !== 'undefined') { + data.data = this._data.map((p) => p.serialize()); + } + if (typeof this._oldest !== 'undefined') { + data.oldest = this._oldest; + } + return data; + } + + validate(): string[] { + const validate = { + oldest: !this._oldest ? true : typeof this._oldest === 'string' && !this._oldest ? true : this._oldest, + data: !this._data ? true : this._data.reduce((result, p) => result && p.validate().length === 0, true), + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): QueryLog { + return new QueryLog({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/QueryLogConfig.ts b/client2/src/lib/entities/QueryLogConfig.ts new file mode 100644 index 00000000..e767d72c --- /dev/null +++ b/client2/src/lib/entities/QueryLogConfig.ts @@ -0,0 +1,76 @@ +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface IQueryLogConfig { + anonymize_client_ip?: boolean; + enabled?: boolean; + interval?: number; +} + +export default class QueryLogConfig { + readonly _anonymize_client_ip: boolean | undefined; + + /** */ + get anonymizeClientIp(): boolean | undefined { + return this._anonymize_client_ip; + } + + readonly _enabled: boolean | undefined; + + /** */ + get enabled(): boolean | undefined { + return this._enabled; + } + + readonly _interval: number | undefined; + + /** */ + get interval(): number | undefined { + return this._interval; + } + + constructor(props: IQueryLogConfig) { + if (typeof props.anonymize_client_ip === 'boolean') { + this._anonymize_client_ip = props.anonymize_client_ip; + } + if (typeof props.enabled === 'boolean') { + this._enabled = props.enabled; + } + if (typeof props.interval === 'number') { + this._interval = props.interval; + } + } + + serialize(): IQueryLogConfig { + const data: IQueryLogConfig = { + }; + if (typeof this._anonymize_client_ip !== 'undefined') { + data.anonymize_client_ip = this._anonymize_client_ip; + } + if (typeof this._enabled !== 'undefined') { + data.enabled = this._enabled; + } + if (typeof this._interval !== 'undefined') { + data.interval = this._interval; + } + return data; + } + + validate(): string[] { + const validate = { + enabled: !this._enabled ? true : typeof this._enabled === 'boolean', + interval: !this._interval ? true : typeof this._interval === 'number', + anonymize_client_ip: !this._anonymize_client_ip ? true : typeof this._anonymize_client_ip === 'boolean', + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): QueryLogConfig { + return new QueryLogConfig({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/QueryLogItem.ts b/client2/src/lib/entities/QueryLogItem.ts new file mode 100644 index 00000000..caf7819c --- /dev/null +++ b/client2/src/lib/entities/QueryLogItem.ts @@ -0,0 +1,277 @@ +import DnsAnswer, { IDnsAnswer } from './DnsAnswer'; +import DnsQuestion, { IDnsQuestion } from './DnsQuestion'; +import ResultRule, { IResultRule } from './ResultRule'; + +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface IQueryLogItem { + answer?: IDnsAnswer[]; + answer_dnssec?: boolean; + client?: string; + client_proto?: any; + elapsedMs?: string; + filterId?: number; + original_answer?: IDnsAnswer[]; + question?: IDnsQuestion; + reason?: string; + rule?: string; + rules?: IResultRule[]; + service_name?: string; + status?: string; + time?: string; + upstream?: string; +} + +export default class QueryLogItem { + readonly _answer: DnsAnswer[] | undefined; + + get answer(): DnsAnswer[] | undefined { + return this._answer; + } + + readonly _answer_dnssec: boolean | undefined; + + get answerDnssec(): boolean | undefined { + return this._answer_dnssec; + } + + readonly _client: string | undefined; + + /** + * Description: undefined + * Example: 192.168.0.1 + */ + get client(): string | undefined { + return this._client; + } + + readonly _client_proto: any | undefined; + + get clientProto(): any | undefined { + return this._client_proto; + } + + readonly _elapsedMs: string | undefined; + + /** + * Description: undefined + * Example: 54.023928 + */ + get elapsedMs(): string | undefined { + return this._elapsedMs; + } + + readonly _filterId: number | undefined; + + /** + * Description: In case if there's a rule applied to this DNS request, this is ID of the filter list that the rule belongs to. + * Deprecated: use `rules[*].filter_list_id` instead. + * + * Example: 123123 + */ + get filterId(): number | undefined { + return this._filterId; + } + + readonly _original_answer: DnsAnswer[] | undefined; + + /** */ + get originalAnswer(): DnsAnswer[] | undefined { + return this._original_answer; + } + + readonly _question: DnsQuestion | undefined; + + get question(): DnsQuestion | undefined { + return this._question; + } + + readonly _reason: string | undefined; + + /** */ + get reason(): string | undefined { + return this._reason; + } + + readonly _rule: string | undefined; + + /** + * Description: Filtering rule applied to the request (if any). + * Deprecated: use `rules[*].text` instead. + * + * Example: ||example.org^ + */ + get rule(): string | undefined { + return this._rule; + } + + readonly _rules: ResultRule[] | undefined; + + /** */ + get rules(): ResultRule[] | undefined { + return this._rules; + } + + readonly _service_name: string | undefined; + + /** */ + get serviceName(): string | undefined { + return this._service_name; + } + + readonly _status: string | undefined; + + /** + * Description: DNS response status + * Example: NOERROR + */ + get status(): string | undefined { + return this._status; + } + + readonly _time: string | undefined; + + /** + * Description: DNS request processing start time + * Example: 2018-11-26T00:02:41+03:00 + */ + get time(): string | undefined { + return this._time; + } + + readonly _upstream: string | undefined; + + /** */ + get upstream(): string | undefined { + return this._upstream; + } + + constructor(props: IQueryLogItem) { + if (props.answer) { + this._answer = props.answer.map((p) => new DnsAnswer(p)); + } + if (typeof props.answer_dnssec === 'boolean') { + this._answer_dnssec = props.answer_dnssec; + } + if (typeof props.client === 'string') { + this._client = props.client.trim(); + } + if (props.client_proto) { + this._client_proto = props.client_proto; + } + if (typeof props.elapsedMs === 'string') { + this._elapsedMs = props.elapsedMs.trim(); + } + if (typeof props.filterId === 'number') { + this._filterId = props.filterId; + } + if (props.original_answer) { + this._original_answer = props.original_answer.map((p) => new DnsAnswer(p)); + } + if (props.question) { + this._question = new DnsQuestion(props.question); + } + if (typeof props.reason === 'string') { + this._reason = props.reason.trim(); + } + if (typeof props.rule === 'string') { + this._rule = props.rule.trim(); + } + if (props.rules) { + this._rules = props.rules.map((p) => new ResultRule(p)); + } + if (typeof props.service_name === 'string') { + this._service_name = props.service_name.trim(); + } + if (typeof props.status === 'string') { + this._status = props.status.trim(); + } + if (typeof props.time === 'string') { + this._time = props.time.trim(); + } + if (typeof props.upstream === 'string') { + this._upstream = props.upstream.trim(); + } + } + + serialize(): IQueryLogItem { + const data: IQueryLogItem = { + }; + if (typeof this._answer !== 'undefined') { + data.answer = this._answer.map((p) => p.serialize()); + } + if (typeof this._answer_dnssec !== 'undefined') { + data.answer_dnssec = this._answer_dnssec; + } + if (typeof this._client !== 'undefined') { + data.client = this._client; + } + if (typeof this._client_proto !== 'undefined') { + data.client_proto = this._client_proto; + } + if (typeof this._elapsedMs !== 'undefined') { + data.elapsedMs = this._elapsedMs; + } + if (typeof this._filterId !== 'undefined') { + data.filterId = this._filterId; + } + if (typeof this._original_answer !== 'undefined') { + data.original_answer = this._original_answer.map((p) => p.serialize()); + } + if (typeof this._question !== 'undefined') { + data.question = this._question.serialize(); + } + if (typeof this._reason !== 'undefined') { + data.reason = this._reason; + } + if (typeof this._rule !== 'undefined') { + data.rule = this._rule; + } + if (typeof this._rules !== 'undefined') { + data.rules = this._rules.map((p) => p.serialize()); + } + if (typeof this._service_name !== 'undefined') { + data.service_name = this._service_name; + } + if (typeof this._status !== 'undefined') { + data.status = this._status; + } + if (typeof this._time !== 'undefined') { + data.time = this._time; + } + if (typeof this._upstream !== 'undefined') { + data.upstream = this._upstream; + } + return data; + } + + validate(): string[] { + const validate = { + answer: !this._answer ? true : this._answer.reduce((result, p) => result && p.validate().length === 0, true), + original_answer: !this._original_answer ? true : this._original_answer.reduce((result, p) => result && p.validate().length === 0, true), + upstream: !this._upstream ? true : typeof this._upstream === 'string' && !this._upstream ? true : this._upstream, + answer_dnssec: !this._answer_dnssec ? true : typeof this._answer_dnssec === 'boolean', + client: !this._client ? true : typeof this._client === 'string' && !this._client ? true : this._client, + elapsedMs: !this._elapsedMs ? true : typeof this._elapsedMs === 'string' && !this._elapsedMs ? true : this._elapsedMs, + question: !this._question ? true : this._question.validate().length === 0, + filterId: !this._filterId ? true : typeof this._filterId === 'number', + rule: !this._rule ? true : typeof this._rule === 'string' && !this._rule ? true : this._rule, + rules: !this._rules ? true : this._rules.reduce((result, p) => result && p.validate().length === 0, true), + reason: !this._reason ? true : typeof this._reason === 'string' && !this._reason ? true : this._reason, + service_name: !this._service_name ? true : typeof this._service_name === 'string' && !this._service_name ? true : this._service_name, + status: !this._status ? true : typeof this._status === 'string' && !this._status ? true : this._status, + time: !this._time ? true : typeof this._time === 'string' && !this._time ? true : this._time, + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): QueryLogItem { + return new QueryLogItem({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/RemoveUrlRequest.ts b/client2/src/lib/entities/RemoveUrlRequest.ts new file mode 100644 index 00000000..b45571a0 --- /dev/null +++ b/client2/src/lib/entities/RemoveUrlRequest.ts @@ -0,0 +1,49 @@ +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface IRemoveUrlRequest { + url?: string; +} + +export default class RemoveUrlRequest { + readonly _url: string | undefined; + + /** + * Description: Previously added URL containing filtering rules + * Example: https://filters.adtidy.org/windows/filters/15.txt + */ + get url(): string | undefined { + return this._url; + } + + constructor(props: IRemoveUrlRequest) { + if (typeof props.url === 'string') { + this._url = props.url.trim(); + } + } + + serialize(): IRemoveUrlRequest { + const data: IRemoveUrlRequest = { + }; + if (typeof this._url !== 'undefined') { + data.url = this._url; + } + return data; + } + + validate(): string[] { + const validate = { + url: !this._url ? true : typeof this._url === 'string' && !this._url ? true : this._url, + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): RemoveUrlRequest { + return new RemoveUrlRequest({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/ResultRule.ts b/client2/src/lib/entities/ResultRule.ts new file mode 100644 index 00000000..d0f03c98 --- /dev/null +++ b/client2/src/lib/entities/ResultRule.ts @@ -0,0 +1,69 @@ +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface IResultRule { + filter_list_id?: number; + text?: string; +} + +export default class ResultRule { + readonly _filter_list_id: number | undefined; + + /** + * Description: In case if there's a rule applied to this DNS request, this is ID of the filter list that the rule belongs to. + * + * Example: 123123 + */ + get filterListId(): number | undefined { + return this._filter_list_id; + } + + readonly _text: string | undefined; + + /** + * Description: The text of the filtering rule applied to the request (if any). + * + * Example: ||example.org^ + */ + get text(): string | undefined { + return this._text; + } + + constructor(props: IResultRule) { + if (typeof props.filter_list_id === 'number') { + this._filter_list_id = props.filter_list_id; + } + if (typeof props.text === 'string') { + this._text = props.text.trim(); + } + } + + serialize(): IResultRule { + const data: IResultRule = { + }; + if (typeof this._filter_list_id !== 'undefined') { + data.filter_list_id = this._filter_list_id; + } + if (typeof this._text !== 'undefined') { + data.text = this._text; + } + return data; + } + + validate(): string[] { + const validate = { + filter_list_id: !this._filter_list_id ? true : typeof this._filter_list_id === 'number', + text: !this._text ? true : typeof this._text === 'string' && !this._text ? true : this._text, + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): ResultRule { + return new ResultRule({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/RewriteEntry.ts b/client2/src/lib/entities/RewriteEntry.ts new file mode 100644 index 00000000..31612b57 --- /dev/null +++ b/client2/src/lib/entities/RewriteEntry.ts @@ -0,0 +1,67 @@ +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface IRewriteEntry { + answer?: string; + domain?: string; +} + +export default class RewriteEntry { + readonly _answer: string | undefined; + + /** + * Description: value of A, AAAA or CNAME DNS record + * Example: 127.0.0.1 + */ + get answer(): string | undefined { + return this._answer; + } + + readonly _domain: string | undefined; + + /** + * Description: Domain name + * Example: example.org + */ + get domain(): string | undefined { + return this._domain; + } + + constructor(props: IRewriteEntry) { + if (typeof props.answer === 'string') { + this._answer = props.answer.trim(); + } + if (typeof props.domain === 'string') { + this._domain = props.domain.trim(); + } + } + + serialize(): IRewriteEntry { + const data: IRewriteEntry = { + }; + if (typeof this._answer !== 'undefined') { + data.answer = this._answer; + } + if (typeof this._domain !== 'undefined') { + data.domain = this._domain; + } + return data; + } + + validate(): string[] { + const validate = { + domain: !this._domain ? true : typeof this._domain === 'string' && !this._domain ? true : this._domain, + answer: !this._answer ? true : typeof this._answer === 'string' && !this._answer ? true : this._answer, + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): RewriteEntry { + return new RewriteEntry({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/RewriteList.ts b/client2/src/lib/entities/RewriteList.ts new file mode 100644 index 00000000..a92ff328 --- /dev/null +++ b/client2/src/lib/entities/RewriteList.ts @@ -0,0 +1,31 @@ +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface IRewriteList { +} + +export default class RewriteList { + constructor(props: IRewriteList) { + } + + serialize(): IRewriteList { + const data: IRewriteList = { + }; + return data; + } + + validate(): string[] { + const validate = { + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): RewriteList { + return new RewriteList({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/ServerStatus.ts b/client2/src/lib/entities/ServerStatus.ts new file mode 100644 index 00000000..a17df251 --- /dev/null +++ b/client2/src/lib/entities/ServerStatus.ts @@ -0,0 +1,167 @@ +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface IServerStatus { + dhcp_available?: boolean; + dns_address: string; + dns_port: number; + language: string; + protection_enabled: boolean; + querylog_enabled: boolean; + running: boolean; + version: string; +} + +export default class ServerStatus { + readonly _dhcp_available: boolean | undefined; + + get dhcpAvailable(): boolean | undefined { + return this._dhcp_available; + } + + readonly _dns_address: string; + + /** + * Description: undefined + * Example: 127.0.0.1 + */ + get dnsAddress(): string { + return this._dns_address; + } + + static dnsAddressValidate(dnsAddress: string): boolean { + return typeof dnsAddress === 'string' && !!dnsAddress.trim(); + } + + readonly _dns_port: number; + + /** + * Description: undefined + * Example: 53 + */ + get dnsPort(): number { + return this._dns_port; + } + + static get dnsPortMinValue() { + return 1; + } + + static get dnsPortMaxValue() { + return 65535; + } + + static dnsPortValidate(dnsPort: number): boolean { + return dnsPort >= 1 && dnsPort <= 65535; + } + + readonly _language: string; + + /** + * Description: undefined + * Example: en + */ + get language(): string { + return this._language; + } + + static languageValidate(language: string): boolean { + return typeof language === 'string' && !!language.trim(); + } + + readonly _protection_enabled: boolean; + + get protectionEnabled(): boolean { + return this._protection_enabled; + } + + static protectionEnabledValidate(protectionEnabled: boolean): boolean { + return typeof protectionEnabled === 'boolean'; + } + + readonly _querylog_enabled: boolean; + + get querylogEnabled(): boolean { + return this._querylog_enabled; + } + + static querylogEnabledValidate(querylogEnabled: boolean): boolean { + return typeof querylogEnabled === 'boolean'; + } + + readonly _running: boolean; + + get running(): boolean { + return this._running; + } + + static runningValidate(running: boolean): boolean { + return typeof running === 'boolean'; + } + + readonly _version: string; + + /** + * Description: undefined + * Example: 0.1 + */ + get version(): string { + return this._version; + } + + static versionValidate(version: string): boolean { + return typeof version === 'string' && !!version.trim(); + } + + constructor(props: IServerStatus) { + if (typeof props.dhcp_available === 'boolean') { + this._dhcp_available = props.dhcp_available; + } + this._dns_address = props.dns_address.trim(); + this._dns_port = props.dns_port; + this._language = props.language.trim(); + this._protection_enabled = props.protection_enabled; + this._querylog_enabled = props.querylog_enabled; + this._running = props.running; + this._version = props.version.trim(); + } + + serialize(): IServerStatus { + const data: IServerStatus = { + dns_address: this._dns_address, + dns_port: this._dns_port, + language: this._language, + protection_enabled: this._protection_enabled, + querylog_enabled: this._querylog_enabled, + running: this._running, + version: this._version, + }; + if (typeof this._dhcp_available !== 'undefined') { + data.dhcp_available = this._dhcp_available; + } + return data; + } + + validate(): string[] { + const validate = { + dns_address: typeof this._dns_address === 'string' && !this._dns_address ? true : this._dns_address, + dns_port: this._dns_port >= 1 && this._dns_port <= 65535, + protection_enabled: typeof this._protection_enabled === 'boolean', + dhcp_available: !this._dhcp_available ? true : typeof this._dhcp_available === 'boolean', + querylog_enabled: typeof this._querylog_enabled === 'boolean', + running: typeof this._running === 'boolean', + version: typeof this._version === 'string' && !this._version ? true : this._version, + language: typeof this._language === 'string' && !this._language ? true : this._language, + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): ServerStatus { + return new ServerStatus({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/Stats.ts b/client2/src/lib/entities/Stats.ts new file mode 100644 index 00000000..6f01139d --- /dev/null +++ b/client2/src/lib/entities/Stats.ts @@ -0,0 +1,257 @@ +import TopArrayEntry, { ITopArrayEntry } from './TopArrayEntry'; + +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface IStats { + avg_processing_time?: number; + blocked_filtering?: number[]; + dns_queries?: number[]; + num_blocked_filtering?: number; + num_dns_queries?: number; + num_replaced_parental?: number; + num_replaced_safebrowsing?: number; + num_replaced_safesearch?: number; + replaced_parental?: number[]; + replaced_safebrowsing?: number[]; + time_units?: string; + top_blocked_domains?: ITopArrayEntry[]; + top_clients?: ITopArrayEntry[]; + top_queried_domains?: ITopArrayEntry[]; +} + +export default class Stats { + readonly _avg_processing_time: number | undefined; + + /** + * Description: Average time in milliseconds on processing a DNS + * Example: 0.34 + */ + get avgProcessingTime(): number | undefined { + return this._avg_processing_time; + } + + readonly _blocked_filtering: number[] | undefined; + + get blockedFiltering(): number[] | undefined { + return this._blocked_filtering; + } + + readonly _dns_queries: number[] | undefined; + + get dnsQueries(): number[] | undefined { + return this._dns_queries; + } + + readonly _num_blocked_filtering: number | undefined; + + /** + * Description: Number of requests blocked by filtering rules + * Example: 50 + */ + get numBlockedFiltering(): number | undefined { + return this._num_blocked_filtering; + } + + readonly _num_dns_queries: number | undefined; + + /** + * Description: Total number of DNS queries + * Example: 123 + */ + get numDnsQueries(): number | undefined { + return this._num_dns_queries; + } + + readonly _num_replaced_parental: number | undefined; + + /** + * Description: Number of blocked adult websites + * Example: 15 + */ + get numReplacedParental(): number | undefined { + return this._num_replaced_parental; + } + + readonly _num_replaced_safebrowsing: number | undefined; + + /** + * Description: Number of requests blocked by safebrowsing module + * Example: 5 + */ + get numReplacedSafebrowsing(): number | undefined { + return this._num_replaced_safebrowsing; + } + + readonly _num_replaced_safesearch: number | undefined; + + /** + * Description: Number of requests blocked by safesearch module + * Example: 5 + */ + get numReplacedSafesearch(): number | undefined { + return this._num_replaced_safesearch; + } + + readonly _replaced_parental: number[] | undefined; + + get replacedParental(): number[] | undefined { + return this._replaced_parental; + } + + readonly _replaced_safebrowsing: number[] | undefined; + + get replacedSafebrowsing(): number[] | undefined { + return this._replaced_safebrowsing; + } + + readonly _time_units: string | undefined; + + /** + * Description: Time units (hours | days) + * Example: hours + */ + get timeUnits(): string | undefined { + return this._time_units; + } + + readonly _top_blocked_domains: TopArrayEntry[] | undefined; + + get topBlockedDomains(): TopArrayEntry[] | undefined { + return this._top_blocked_domains; + } + + readonly _top_clients: TopArrayEntry[] | undefined; + + get topClients(): TopArrayEntry[] | undefined { + return this._top_clients; + } + + readonly _top_queried_domains: TopArrayEntry[] | undefined; + + get topQueriedDomains(): TopArrayEntry[] | undefined { + return this._top_queried_domains; + } + + constructor(props: IStats) { + if (typeof props.avg_processing_time === 'number') { + this._avg_processing_time = props.avg_processing_time; + } + if (props.blocked_filtering) { + this._blocked_filtering = props.blocked_filtering; + } + if (props.dns_queries) { + this._dns_queries = props.dns_queries; + } + if (typeof props.num_blocked_filtering === 'number') { + this._num_blocked_filtering = props.num_blocked_filtering; + } + if (typeof props.num_dns_queries === 'number') { + this._num_dns_queries = props.num_dns_queries; + } + if (typeof props.num_replaced_parental === 'number') { + this._num_replaced_parental = props.num_replaced_parental; + } + if (typeof props.num_replaced_safebrowsing === 'number') { + this._num_replaced_safebrowsing = props.num_replaced_safebrowsing; + } + if (typeof props.num_replaced_safesearch === 'number') { + this._num_replaced_safesearch = props.num_replaced_safesearch; + } + if (props.replaced_parental) { + this._replaced_parental = props.replaced_parental; + } + if (props.replaced_safebrowsing) { + this._replaced_safebrowsing = props.replaced_safebrowsing; + } + if (typeof props.time_units === 'string') { + this._time_units = props.time_units.trim(); + } + if (props.top_blocked_domains) { + this._top_blocked_domains = props.top_blocked_domains.map((p) => new TopArrayEntry(p)); + } + if (props.top_clients) { + this._top_clients = props.top_clients.map((p) => new TopArrayEntry(p)); + } + if (props.top_queried_domains) { + this._top_queried_domains = props.top_queried_domains.map((p) => new TopArrayEntry(p)); + } + } + + serialize(): IStats { + const data: IStats = { + }; + if (typeof this._avg_processing_time !== 'undefined') { + data.avg_processing_time = this._avg_processing_time; + } + if (typeof this._blocked_filtering !== 'undefined') { + data.blocked_filtering = this._blocked_filtering; + } + if (typeof this._dns_queries !== 'undefined') { + data.dns_queries = this._dns_queries; + } + if (typeof this._num_blocked_filtering !== 'undefined') { + data.num_blocked_filtering = this._num_blocked_filtering; + } + if (typeof this._num_dns_queries !== 'undefined') { + data.num_dns_queries = this._num_dns_queries; + } + if (typeof this._num_replaced_parental !== 'undefined') { + data.num_replaced_parental = this._num_replaced_parental; + } + if (typeof this._num_replaced_safebrowsing !== 'undefined') { + data.num_replaced_safebrowsing = this._num_replaced_safebrowsing; + } + if (typeof this._num_replaced_safesearch !== 'undefined') { + data.num_replaced_safesearch = this._num_replaced_safesearch; + } + if (typeof this._replaced_parental !== 'undefined') { + data.replaced_parental = this._replaced_parental; + } + if (typeof this._replaced_safebrowsing !== 'undefined') { + data.replaced_safebrowsing = this._replaced_safebrowsing; + } + if (typeof this._time_units !== 'undefined') { + data.time_units = this._time_units; + } + if (typeof this._top_blocked_domains !== 'undefined') { + data.top_blocked_domains = this._top_blocked_domains.map((p) => p.serialize()); + } + if (typeof this._top_clients !== 'undefined') { + data.top_clients = this._top_clients.map((p) => p.serialize()); + } + if (typeof this._top_queried_domains !== 'undefined') { + data.top_queried_domains = this._top_queried_domains.map((p) => p.serialize()); + } + return data; + } + + validate(): string[] { + const validate = { + time_units: !this._time_units ? true : typeof this._time_units === 'string' && !this._time_units ? true : this._time_units, + num_dns_queries: !this._num_dns_queries ? true : typeof this._num_dns_queries === 'number', + num_blocked_filtering: !this._num_blocked_filtering ? true : typeof this._num_blocked_filtering === 'number', + num_replaced_safebrowsing: !this._num_replaced_safebrowsing ? true : typeof this._num_replaced_safebrowsing === 'number', + num_replaced_safesearch: !this._num_replaced_safesearch ? true : typeof this._num_replaced_safesearch === 'number', + num_replaced_parental: !this._num_replaced_parental ? true : typeof this._num_replaced_parental === 'number', + avg_processing_time: !this._avg_processing_time ? true : typeof this._avg_processing_time === 'number', + top_queried_domains: !this._top_queried_domains ? true : this._top_queried_domains.reduce((result, p) => result && p.validate().length === 0, true), + top_clients: !this._top_clients ? true : this._top_clients.reduce((result, p) => result && p.validate().length === 0, true), + top_blocked_domains: !this._top_blocked_domains ? true : this._top_blocked_domains.reduce((result, p) => result && p.validate().length === 0, true), + dns_queries: !this._dns_queries ? true : this._dns_queries.reduce((result, p) => result && typeof p === 'number', true), + blocked_filtering: !this._blocked_filtering ? true : this._blocked_filtering.reduce((result, p) => result && typeof p === 'number', true), + replaced_safebrowsing: !this._replaced_safebrowsing ? true : this._replaced_safebrowsing.reduce((result, p) => result && typeof p === 'number', true), + replaced_parental: !this._replaced_parental ? true : this._replaced_parental.reduce((result, p) => result && typeof p === 'number', true), + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): Stats { + return new Stats({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/StatsConfig.ts b/client2/src/lib/entities/StatsConfig.ts new file mode 100644 index 00000000..516105ea --- /dev/null +++ b/client2/src/lib/entities/StatsConfig.ts @@ -0,0 +1,46 @@ +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface IStatsConfig { + interval?: number; +} + +export default class StatsConfig { + readonly _interval: number | undefined; + + /** */ + get interval(): number | undefined { + return this._interval; + } + + constructor(props: IStatsConfig) { + if (typeof props.interval === 'number') { + this._interval = props.interval; + } + } + + serialize(): IStatsConfig { + const data: IStatsConfig = { + }; + if (typeof this._interval !== 'undefined') { + data.interval = this._interval; + } + return data; + } + + validate(): string[] { + const validate = { + interval: !this._interval ? true : typeof this._interval === 'number', + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): StatsConfig { + return new StatsConfig({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/TlsConfig.ts b/client2/src/lib/entities/TlsConfig.ts new file mode 100644 index 00000000..be3ecad1 --- /dev/null +++ b/client2/src/lib/entities/TlsConfig.ts @@ -0,0 +1,404 @@ +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface ITlsConfig { + certificate_chain?: string; + certificate_path?: string; + dns_names?: string[]; + enabled?: boolean; + force_https?: boolean; + issuer?: string; + key_type?: string; + not_after?: string; + not_before?: string; + port_dns_over_quic?: number; + port_dns_over_tls?: number; + port_https?: number; + private_key?: string; + private_key_path?: string; + server_name?: string; + subject?: string; + valid_cert?: boolean; + valid_chain?: boolean; + valid_key?: boolean; + valid_pair?: boolean; + warning_validation?: string; +} + +export default class TlsConfig { + readonly _certificate_chain: string | undefined; + + /** */ + get certificateChain(): string | undefined { + return this._certificate_chain; + } + + readonly _certificate_path: string | undefined; + + /** */ + get certificatePath(): string | undefined { + return this._certificate_path; + } + + readonly _dns_names: string[] | undefined; + + /** + * Description: The value of SubjectAltNames field of the first certificate in the chain. + * + * Example: *.example.org + */ + get dnsNames(): string[] | undefined { + return this._dns_names; + } + + readonly _enabled: boolean | undefined; + + /** + * Description: enabled is the encryption (DOT/DOH/HTTPS) status + * Example: true + */ + get enabled(): boolean | undefined { + return this._enabled; + } + + readonly _force_https: boolean | undefined; + + /** + * Description: if true, forces HTTP->HTTPS redirect + * Example: true + */ + get forceHttps(): boolean | undefined { + return this._force_https; + } + + readonly _issuer: string | undefined; + + /** + * Description: The issuer of the first certificate in the chain. + * Example: CN=Let's Encrypt Authority X3,O=Let's Encrypt,C=US + */ + get issuer(): string | undefined { + return this._issuer; + } + + readonly _key_type: string | undefined; + + /** + * Description: Key type. + * Example: RSA + */ + get keyType(): string | undefined { + return this._key_type; + } + + readonly _not_after: string | undefined; + + /** + * Description: The NotAfter field of the first certificate in the chain. + * + * Example: 2019-05-01T10:47:32Z + */ + get notAfter(): string | undefined { + return this._not_after; + } + + readonly _not_before: string | undefined; + + /** + * Description: The NotBefore field of the first certificate in the chain. + * + * Example: 2019-01-31T10:47:32Z + */ + get notBefore(): string | undefined { + return this._not_before; + } + + readonly _port_dns_over_quic: number | undefined; + + /** + * Description: DNS-over-QUIC port. If 0, DOQ will be disabled. + * Example: 784 + */ + get portDnsOverQuic(): number | undefined { + return this._port_dns_over_quic; + } + + readonly _port_dns_over_tls: number | undefined; + + /** + * Description: DNS-over-TLS port. If 0, DOT will be disabled. + * Example: 853 + */ + get portDnsOverTls(): number | undefined { + return this._port_dns_over_tls; + } + + readonly _port_https: number | undefined; + + /** + * Description: HTTPS port. If 0, HTTPS will be disabled. + * Example: 443 + */ + get portHttps(): number | undefined { + return this._port_https; + } + + readonly _private_key: string | undefined; + + /** */ + get privateKey(): string | undefined { + return this._private_key; + } + + readonly _private_key_path: string | undefined; + + /** */ + get privateKeyPath(): string | undefined { + return this._private_key_path; + } + + readonly _server_name: string | undefined; + + /** + * Description: server_name is the hostname of your HTTPS/TLS server + * Example: example.org + */ + get serverName(): string | undefined { + return this._server_name; + } + + readonly _subject: string | undefined; + + /** + * Description: The subject of the first certificate in the chain. + * Example: CN=example.org + */ + get subject(): string | undefined { + return this._subject; + } + + readonly _valid_cert: boolean | undefined; + + /** + * Description: Set to true if the specified certificates chain is a valid chain of X509 certificates. + * + * Example: true + */ + get validCert(): boolean | undefined { + return this._valid_cert; + } + + readonly _valid_chain: boolean | undefined; + + /** + * Description: Set to true if the specified certificates chain is verified and issued by a known CA. + * + * Example: true + */ + get validChain(): boolean | undefined { + return this._valid_chain; + } + + readonly _valid_key: boolean | undefined; + + /** + * Description: Set to true if the key is a valid private key. + * Example: true + */ + get validKey(): boolean | undefined { + return this._valid_key; + } + + readonly _valid_pair: boolean | undefined; + + /** + * Description: Set to true if both certificate and private key are correct. + * + * Example: true + */ + get validPair(): boolean | undefined { + return this._valid_pair; + } + + readonly _warning_validation: string | undefined; + + /** + * Description: A validation warning message with the issue description. + * + * Example: You have specified an empty certificate + */ + get warningValidation(): string | undefined { + return this._warning_validation; + } + + constructor(props: ITlsConfig) { + if (typeof props.certificate_chain === 'string') { + this._certificate_chain = props.certificate_chain.trim(); + } + if (typeof props.certificate_path === 'string') { + this._certificate_path = props.certificate_path.trim(); + } + if (props.dns_names) { + this._dns_names = props.dns_names; + } + if (typeof props.enabled === 'boolean') { + this._enabled = props.enabled; + } + if (typeof props.force_https === 'boolean') { + this._force_https = props.force_https; + } + if (typeof props.issuer === 'string') { + this._issuer = props.issuer.trim(); + } + if (typeof props.key_type === 'string') { + this._key_type = props.key_type.trim(); + } + if (typeof props.not_after === 'string') { + this._not_after = props.not_after.trim(); + } + if (typeof props.not_before === 'string') { + this._not_before = props.not_before.trim(); + } + if (typeof props.port_dns_over_quic === 'number') { + this._port_dns_over_quic = props.port_dns_over_quic; + } + if (typeof props.port_dns_over_tls === 'number') { + this._port_dns_over_tls = props.port_dns_over_tls; + } + if (typeof props.port_https === 'number') { + this._port_https = props.port_https; + } + if (typeof props.private_key === 'string') { + this._private_key = props.private_key.trim(); + } + if (typeof props.private_key_path === 'string') { + this._private_key_path = props.private_key_path.trim(); + } + if (typeof props.server_name === 'string') { + this._server_name = props.server_name.trim(); + } + if (typeof props.subject === 'string') { + this._subject = props.subject.trim(); + } + if (typeof props.valid_cert === 'boolean') { + this._valid_cert = props.valid_cert; + } + if (typeof props.valid_chain === 'boolean') { + this._valid_chain = props.valid_chain; + } + if (typeof props.valid_key === 'boolean') { + this._valid_key = props.valid_key; + } + if (typeof props.valid_pair === 'boolean') { + this._valid_pair = props.valid_pair; + } + if (typeof props.warning_validation === 'string') { + this._warning_validation = props.warning_validation.trim(); + } + } + + serialize(): ITlsConfig { + const data: ITlsConfig = { + }; + if (typeof this._certificate_chain !== 'undefined') { + data.certificate_chain = this._certificate_chain; + } + if (typeof this._certificate_path !== 'undefined') { + data.certificate_path = this._certificate_path; + } + if (typeof this._dns_names !== 'undefined') { + data.dns_names = this._dns_names; + } + if (typeof this._enabled !== 'undefined') { + data.enabled = this._enabled; + } + if (typeof this._force_https !== 'undefined') { + data.force_https = this._force_https; + } + if (typeof this._issuer !== 'undefined') { + data.issuer = this._issuer; + } + if (typeof this._key_type !== 'undefined') { + data.key_type = this._key_type; + } + if (typeof this._not_after !== 'undefined') { + data.not_after = this._not_after; + } + if (typeof this._not_before !== 'undefined') { + data.not_before = this._not_before; + } + if (typeof this._port_dns_over_quic !== 'undefined') { + data.port_dns_over_quic = this._port_dns_over_quic; + } + if (typeof this._port_dns_over_tls !== 'undefined') { + data.port_dns_over_tls = this._port_dns_over_tls; + } + if (typeof this._port_https !== 'undefined') { + data.port_https = this._port_https; + } + if (typeof this._private_key !== 'undefined') { + data.private_key = this._private_key; + } + if (typeof this._private_key_path !== 'undefined') { + data.private_key_path = this._private_key_path; + } + if (typeof this._server_name !== 'undefined') { + data.server_name = this._server_name; + } + if (typeof this._subject !== 'undefined') { + data.subject = this._subject; + } + if (typeof this._valid_cert !== 'undefined') { + data.valid_cert = this._valid_cert; + } + if (typeof this._valid_chain !== 'undefined') { + data.valid_chain = this._valid_chain; + } + if (typeof this._valid_key !== 'undefined') { + data.valid_key = this._valid_key; + } + if (typeof this._valid_pair !== 'undefined') { + data.valid_pair = this._valid_pair; + } + if (typeof this._warning_validation !== 'undefined') { + data.warning_validation = this._warning_validation; + } + return data; + } + + validate(): string[] { + const validate = { + enabled: !this._enabled ? true : typeof this._enabled === 'boolean', + server_name: !this._server_name ? true : typeof this._server_name === 'string' && !this._server_name ? true : this._server_name, + force_https: !this._force_https ? true : typeof this._force_https === 'boolean', + port_https: !this._port_https ? true : typeof this._port_https === 'number', + port_dns_over_tls: !this._port_dns_over_tls ? true : typeof this._port_dns_over_tls === 'number', + port_dns_over_quic: !this._port_dns_over_quic ? true : typeof this._port_dns_over_quic === 'number', + certificate_chain: !this._certificate_chain ? true : typeof this._certificate_chain === 'string' && !this._certificate_chain ? true : this._certificate_chain, + private_key: !this._private_key ? true : typeof this._private_key === 'string' && !this._private_key ? true : this._private_key, + certificate_path: !this._certificate_path ? true : typeof this._certificate_path === 'string' && !this._certificate_path ? true : this._certificate_path, + private_key_path: !this._private_key_path ? true : typeof this._private_key_path === 'string' && !this._private_key_path ? true : this._private_key_path, + valid_cert: !this._valid_cert ? true : typeof this._valid_cert === 'boolean', + valid_chain: !this._valid_chain ? true : typeof this._valid_chain === 'boolean', + subject: !this._subject ? true : typeof this._subject === 'string' && !this._subject ? true : this._subject, + issuer: !this._issuer ? true : typeof this._issuer === 'string' && !this._issuer ? true : this._issuer, + not_before: !this._not_before ? true : typeof this._not_before === 'string' && !this._not_before ? true : this._not_before, + not_after: !this._not_after ? true : typeof this._not_after === 'string' && !this._not_after ? true : this._not_after, + dns_names: !this._dns_names ? true : this._dns_names.reduce((result, p) => result && typeof p === 'string', true), + valid_key: !this._valid_key ? true : typeof this._valid_key === 'boolean', + key_type: !this._key_type ? true : typeof this._key_type === 'string' && !this._key_type ? true : this._key_type, + warning_validation: !this._warning_validation ? true : typeof this._warning_validation === 'string' && !this._warning_validation ? true : this._warning_validation, + valid_pair: !this._valid_pair ? true : typeof this._valid_pair === 'boolean', + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): TlsConfig { + return new TlsConfig({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/TopArrayEntry.ts b/client2/src/lib/entities/TopArrayEntry.ts new file mode 100644 index 00000000..52916fd2 --- /dev/null +++ b/client2/src/lib/entities/TopArrayEntry.ts @@ -0,0 +1,45 @@ +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface ITopArrayEntry { + domain_or_ip?: number; +} + +export default class TopArrayEntry { + readonly _domain_or_ip: number | undefined; + + get domainOrIp(): number | undefined { + return this._domain_or_ip; + } + + constructor(props: ITopArrayEntry) { + if (typeof props.domain_or_ip === 'number') { + this._domain_or_ip = props.domain_or_ip; + } + } + + serialize(): ITopArrayEntry { + const data: ITopArrayEntry = { + }; + if (typeof this._domain_or_ip !== 'undefined') { + data.domain_or_ip = this._domain_or_ip; + } + return data; + } + + validate(): string[] { + const validate = { + domain_or_ip: !this._domain_or_ip ? true : typeof this._domain_or_ip === 'number', + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): TopArrayEntry { + return new TopArrayEntry({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/UpstreamsConfig.ts b/client2/src/lib/entities/UpstreamsConfig.ts new file mode 100644 index 00000000..5b9c0a6b --- /dev/null +++ b/client2/src/lib/entities/UpstreamsConfig.ts @@ -0,0 +1,69 @@ +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface IUpstreamsConfig { + bootstrap_dns: string[]; + upstream_dns: string[]; +} + +export default class UpstreamsConfig { + readonly _bootstrap_dns: string[]; + + /** + * Description: Bootstrap servers, port is optional after colon. Empty value will reset it to default values. + * + * Example: 8.8.8.8:53,1.1.1.1:53 + */ + get bootstrapDns(): string[] { + return this._bootstrap_dns; + } + + static bootstrapDnsValidate(bootstrapDns: string[]): boolean { + return bootstrapDns.reduce((result, p) => result && (typeof p === 'string' && !!p.trim()), true); + } + + readonly _upstream_dns: string[]; + + /** + * Description: Upstream servers, port is optional after colon. Empty value will reset it to default values. + * + * Example: tls://1.1.1.1,tls://1.0.0.1 + */ + get upstreamDns(): string[] { + return this._upstream_dns; + } + + static upstreamDnsValidate(upstreamDns: string[]): boolean { + return upstreamDns.reduce((result, p) => result && (typeof p === 'string' && !!p.trim()), true); + } + + constructor(props: IUpstreamsConfig) { + this._bootstrap_dns = props.bootstrap_dns; + this._upstream_dns = props.upstream_dns; + } + + serialize(): IUpstreamsConfig { + const data: IUpstreamsConfig = { + bootstrap_dns: this._bootstrap_dns, + upstream_dns: this._upstream_dns, + }; + return data; + } + + validate(): string[] { + const validate = { + bootstrap_dns: this._bootstrap_dns.reduce((result, p) => result && typeof p === 'string', true), + upstream_dns: this._upstream_dns.reduce((result, p) => result && typeof p === 'string', true), + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): UpstreamsConfig { + return new UpstreamsConfig({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/UpstreamsConfigResponse.ts b/client2/src/lib/entities/UpstreamsConfigResponse.ts new file mode 100644 index 00000000..9b342400 --- /dev/null +++ b/client2/src/lib/entities/UpstreamsConfigResponse.ts @@ -0,0 +1,31 @@ +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface IUpstreamsConfigResponse { +} + +export default class UpstreamsConfigResponse { + constructor(props: IUpstreamsConfigResponse) { + } + + serialize(): IUpstreamsConfigResponse { + const data: IUpstreamsConfigResponse = { + }; + return data; + } + + validate(): string[] { + const validate = { + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): UpstreamsConfigResponse { + return new UpstreamsConfigResponse({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/VersionInfo.ts b/client2/src/lib/entities/VersionInfo.ts new file mode 100644 index 00000000..7df508ea --- /dev/null +++ b/client2/src/lib/entities/VersionInfo.ts @@ -0,0 +1,100 @@ +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface IVersionInfo { + announcement?: string; + announcement_url?: string; + can_autoupdate?: boolean; + new_version?: string; +} + +export default class VersionInfo { + readonly _announcement: string | undefined; + + /** + * Description: undefined + * Example: AdGuard Home v0.9 is now available! + */ + get announcement(): string | undefined { + return this._announcement; + } + + readonly _announcement_url: string | undefined; + + /** + * Description: undefined + * Example: https://github.com/AdguardTeam/AdGuardHome/releases/tag/v0.9 + * + */ + get announcementUrl(): string | undefined { + return this._announcement_url; + } + + readonly _can_autoupdate: boolean | undefined; + + get canAutoupdate(): boolean | undefined { + return this._can_autoupdate; + } + + readonly _new_version: string | undefined; + + /** + * Description: undefined + * Example: v0.9 + */ + get newVersion(): string | undefined { + return this._new_version; + } + + constructor(props: IVersionInfo) { + if (typeof props.announcement === 'string') { + this._announcement = props.announcement.trim(); + } + if (typeof props.announcement_url === 'string') { + this._announcement_url = props.announcement_url.trim(); + } + if (typeof props.can_autoupdate === 'boolean') { + this._can_autoupdate = props.can_autoupdate; + } + if (typeof props.new_version === 'string') { + this._new_version = props.new_version.trim(); + } + } + + serialize(): IVersionInfo { + const data: IVersionInfo = { + }; + if (typeof this._announcement !== 'undefined') { + data.announcement = this._announcement; + } + if (typeof this._announcement_url !== 'undefined') { + data.announcement_url = this._announcement_url; + } + if (typeof this._can_autoupdate !== 'undefined') { + data.can_autoupdate = this._can_autoupdate; + } + if (typeof this._new_version !== 'undefined') { + data.new_version = this._new_version; + } + return data; + } + + validate(): string[] { + const validate = { + new_version: !this._new_version ? true : typeof this._new_version === 'string' && !this._new_version ? true : this._new_version, + announcement: !this._announcement ? true : typeof this._announcement === 'string' && !this._announcement ? true : this._announcement, + announcement_url: !this._announcement_url ? true : typeof this._announcement_url === 'string' && !this._announcement_url ? true : this._announcement_url, + can_autoupdate: !this._can_autoupdate ? true : typeof this._can_autoupdate === 'boolean', + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): VersionInfo { + return new VersionInfo({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/WhoisInfo.ts b/client2/src/lib/entities/WhoisInfo.ts new file mode 100644 index 00000000..f0f2db74 --- /dev/null +++ b/client2/src/lib/entities/WhoisInfo.ts @@ -0,0 +1,45 @@ +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface IWhoisInfo { + key?: string; +} + +export default class WhoisInfo { + readonly _key: string | undefined; + + get key(): string | undefined { + return this._key; + } + + constructor(props: IWhoisInfo) { + if (typeof props.key === 'string') { + this._key = props.key.trim(); + } + } + + serialize(): IWhoisInfo { + const data: IWhoisInfo = { + }; + if (typeof this._key !== 'undefined') { + data.key = this._key; + } + return data; + } + + validate(): string[] { + const validate = { + key: !this._key ? true : typeof this._key === 'string' && !this._key ? true : this._key, + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): WhoisInfo { + return new WhoisInfo({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/helpers/apiErrors.ts b/client2/src/lib/helpers/apiErrors.ts new file mode 100644 index 00000000..c1916e1f --- /dev/null +++ b/client2/src/lib/helpers/apiErrors.ts @@ -0,0 +1,14 @@ +interface ErrorCheck { + error?: Error; + result?: T; +} + +export function errorChecker(response: Error | any): ErrorCheck { + if (typeof response !== 'object') { + return { result: response }; + } + if (response instanceof Error) { + return { error: response }; + } + return { result: response }; +} diff --git a/client2/src/lib/helpers/installHelpers.ts b/client2/src/lib/helpers/installHelpers.ts new file mode 100644 index 00000000..a4258402 --- /dev/null +++ b/client2/src/lib/helpers/installHelpers.ts @@ -0,0 +1,17 @@ +export enum NETWORK_TYPE { + LOCAL = 'LOCAL', + ETHERNET = 'ETHERNET', + OTHER = 'OTHER', +} + +export const chechNetworkType = (network: string | undefined) => { + if (!network) { + return NETWORK_TYPE.OTHER; + } + if (network.includes('en')) { + return NETWORK_TYPE.ETHERNET; + } + if (network.includes('lo')) { + return NETWORK_TYPE.LOCAL; + } +}; diff --git a/client2/src/lib/theme/Form.module.pcss b/client2/src/lib/theme/Form.module.pcss new file mode 100644 index 00000000..f4988df6 --- /dev/null +++ b/client2/src/lib/theme/Form.module.pcss @@ -0,0 +1,78 @@ +.group { + display: block; + margin-bottom: 24px; + + &_last, + &:last-child { + margin-bottom: 0; + } +} + +.label { + margin-bottom: 4px; + font-size: 14px; + color: var(--gray700); +} + +.reveal { + color: var(--black); + transition: color var(--transition); + cursor: pointer; + + &:hover, + &:focus { + color: var(--gray); + } +} + +.reveal + .suffix { + margin-left: 16px; +} + +.addon { + display: flex; + height: 48px; + padding: 14px 3px 14px 14px; + font-size: 14px; + font-weight: 500; + line-height: 1.4; + cursor: pointer; + overflow: hidden; + + @media (--m-viewport) { + padding-right: 6px; + font-size: 16px; + line-height: 1.3; + } +} + +.addonCountry { + min-width: 28px; + margin-right: 3px; + + @media (--m-viewport) { + margin-right: 12px; + } +} + +.addonIcon { + position: relative; + top: -2px; + margin-right: 9px; + color: var(--concrete); +} + +.addonCode { + position: relative; + padding-left: 12px; + + &::before { + content: ""; + position: absolute; + top: -14px; + left: 0; + width: 1px; + height: 48px; + background-color: var(--borders-white); + } +} diff --git a/client2/src/lib/theme/Icons/Icon.pcss b/client2/src/lib/theme/Icons/Icon.pcss new file mode 100644 index 00000000..a77a74a7 --- /dev/null +++ b/client2/src/lib/theme/Icons/Icon.pcss @@ -0,0 +1,3 @@ +.icons { + display: none; +} diff --git a/client2/src/lib/theme/Icons/index.tsx b/client2/src/lib/theme/Icons/index.tsx new file mode 100644 index 00000000..23f0a95d --- /dev/null +++ b/client2/src/lib/theme/Icons/index.tsx @@ -0,0 +1,54 @@ +import React, { FC } from 'react'; +import './Icon.pcss'; + +export type IconType = + 'mainLogo' | + 'visibility_disable' | + 'visibility_enable'; + +const Icons: FC = () => ( + + + + + + + + + + + + +); + +export default Icons; diff --git a/client2/src/lib/theme/Typography.module.pcss b/client2/src/lib/theme/Typography.module.pcss new file mode 100644 index 00000000..b51e0e56 --- /dev/null +++ b/client2/src/lib/theme/Typography.module.pcss @@ -0,0 +1,36 @@ +.title, .subTitle, .text { + color: var(--gray900); + letter-spacing: 0px; +} + +.title { + font-size: 30px; + line-height: 36px; + margin-bottom: 16px; +} +.subTitle { + font-size: 20px; + line-height: 24px; + margin-bottom: 16px; +} +.text { + font-size: 16px; + line-height: 22px; + + &_block { + margin-bottom: 48px; + } + + &_base { + margin-bottom: 32px; + } +} +.subtext { + color: var(--gray400); +} + +.danger { + text-transform: capitalize; + color: var(--red400); + font-weight: bold; +} \ No newline at end of file diff --git a/client2/src/lib/theme/index.ts b/client2/src/lib/theme/index.ts new file mode 100644 index 00000000..43df8b68 --- /dev/null +++ b/client2/src/lib/theme/index.ts @@ -0,0 +1,9 @@ +import typography from './Typography.module.pcss'; +import form from './Form.module.pcss'; + +const theme = { + typography, + form, +}; + +export default theme; diff --git a/client2/src/localization/Translator/Translator.ts b/client2/src/localization/Translator/Translator.ts new file mode 100644 index 00000000..06b50cd1 --- /dev/null +++ b/client2/src/localization/Translator/Translator.ts @@ -0,0 +1,82 @@ +import translator from './lib/translator'; +import { AllowedValues } from './lib/formatter'; +import { getForm, GenericLocales, AvailableLocales } from './lib/plural'; + +type ExternalFormater = (data: any) => any; + +class Translator { + private _currentLocale: Locale; + + private _formatter: ExternalFormater = (data: string[]) => data.join(''); + + get currentLocale() { + return this._currentLocale; + } + + defaultLocale: Locale; + + updateTranslator = (locale: Locale) => { + return new Translator( + this.defaultLocale, + this.messages, + locale, + this._formatter, + ); + }; + + messages: Record; + + constructor( + defaultLocale: Locale, + messages: Record, + currentLocale?: Locale, + formatter?: ExternalFormater, + ) { + this.defaultLocale = defaultLocale; + this._currentLocale = currentLocale ?? defaultLocale; + this.messages = messages; + + if (formatter) { + this._formatter = formatter; + } + } + + public getMessage( + id: string, + params: AllowedValues = {}, + ): string { + const str = this.messages[this._currentLocale][id] + || this.messages[this.defaultLocale][id] + || id; + + const tranlation = translator(str, params); + return this._formatter(tranlation); + } + + public getPlural( + id: string, + number: number, + params: AllowedValues = {}, + ): string { + let locale: Locale | null = null; + if (this.messages[this._currentLocale][id]) { + locale = this._currentLocale; + } else if (this.messages[this.defaultLocale][id]) { + locale = this.defaultLocale; + } + const str = this.messages[this._currentLocale][id] + || this.messages[this.defaultLocale][id] + || id; + + if (!locale) { + throw new Error(`No translation for id: ${id}, neither in current locale: ${this._currentLocale} nor defaulkt locale ${this.defaultLocale}`); + } + + return this._formatter(translator( + getForm(str, number, locale as AvailableLocales, id), + { count: number, ...params }, + )); + } +} + +export default Translator; diff --git a/client2/src/localization/Translator/index.ts b/client2/src/localization/Translator/index.ts new file mode 100644 index 00000000..87f17fbc --- /dev/null +++ b/client2/src/localization/Translator/index.ts @@ -0,0 +1,2 @@ +export { default } from './Translator'; +export { SupportedLangs, GenericLocales, AvailableLocales, checkFormsExternal as checkForms } from './lib/plural'; diff --git a/client2/src/localization/Translator/lib/formatter.ts b/client2/src/localization/Translator/lib/formatter.ts new file mode 100644 index 00000000..15735f6a --- /dev/null +++ b/client2/src/localization/Translator/lib/formatter.ts @@ -0,0 +1,100 @@ +import { + isTextNode, + isTagNode, + isPlaceholderNode, + isVoidTagNode, + NODE, +} from './nodes'; + +/** + * Checks if target is function + * @param target + * @returns {boolean} + */ +const isFunction = (target: any) => { + return typeof target === 'function'; +}; + +type FormatingFunc = (chunks: string) => T; +export type AllowedValues = Record>; + +/** + * This function accepts an AST (abstract syntax tree) which is a result + * of the parser function call, and converts tree nodes into array of strings replacing node + * values with provided values. + * Values is a map with functions or strings, where each key is related to placeholder value + * or tag value + * e.g. + * string "text tag text %placeholder%" is parsed into next AST + * + * [ + * { type: 'text', value: 'text ' }, + * { + * type: 'tag', + * value: 'tag', + * children: [{ type: 'text', value: 'tag text' }], + * }, + * { type: 'text', value: ' ' }, + * { type: 'placeholder', value: 'placeholder' } + * ]; + * + * this AST after format and next values + * + * { + * // here used template strings, but it can be react components as well + * tag: (chunks) => `${chunks}`, + * placeholder: 'placeholder text' + * } + * + * will return next array + * + * [ 'text ', 'tag text', ' ', 'placeholder text' ] + * + * as you can see, was replaced by , and placeholder was replaced by placeholder text + * + * @param ast - AST (abstract syntax tree) + * @param values + * @returns {[]} + */ +const format = (ast: NODE[], values: AllowedValues) => { + const result: (string | T)[] = []; + let i = 0; + while (i < ast.length) { + const currentNode = ast[i]; + // if current node is text node, there is nothing to change, append value to the result + if (isTextNode(currentNode)) { + result.push(currentNode.value); + } else if (isTagNode(currentNode)) { + const children = [...format(currentNode.children ? currentNode.children : [], values)].join(''); + const value = values[currentNode.value]; + if (typeof value === 'string' || typeof value === 'number' || typeof value === 'function') { + if (isFunction(value)) { + result.push((value as FormatingFunc)(children)); + } else { + result.push(value.toString()); + } + } else { + throw new Error(`Value ${currentNode.value} wasn't provided`); + } + } else if (isVoidTagNode(currentNode)) { + const value = values[currentNode.value]; + if (typeof value === 'string' || typeof value === 'number') { + result.push(value.toString()); + } else { + throw new Error(`Value ${currentNode.value} wasn't provided`); + } + } else if (isPlaceholderNode(currentNode)) { + const value = values[currentNode.value]; + if (typeof value === 'string' || typeof value === 'number') { + result.push(value.toString()); + } else { + throw new Error(`Value ${currentNode.value} wasn't provided`); + } + } + i += 1; + } + + return result; +}; + +export default format; diff --git a/client2/src/localization/Translator/lib/nodes.ts b/client2/src/localization/Translator/lib/nodes.ts new file mode 100644 index 00000000..20f3835a --- /dev/null +++ b/client2/src/localization/Translator/lib/nodes.ts @@ -0,0 +1,50 @@ +export enum NODE_TYPES { + PLACEHOLDER = 'placeholder', + TEXT = 'text', + TAG = 'tag', + VOID_TAG = 'void_tag', +} + +export interface NODE { + type: NODE_TYPES; + value: string | keyof HTMLElementTagNameMap; + children?: NODE[]; +} + +export const isTextNode = (node: NODE) => { + return node?.type === NODE_TYPES.TEXT; +}; + +export const isTagNode = (node: NODE) => { + return node?.type === NODE_TYPES.TAG; +}; + +export const isPlaceholderNode = (node: NODE) => { + return node?.type === NODE_TYPES.PLACEHOLDER; +}; + +export const isVoidTagNode = (node: NODE) => { + return node?.type === NODE_TYPES.VOID_TAG; +}; + +export const placeholderNode = (value: string) => { + return { type: NODE_TYPES.PLACEHOLDER, value }; +}; + +export const textNode = (str: string) => { + return { type: NODE_TYPES.TEXT, value: str }; +}; + +export const tagNode = (tagName: keyof HTMLElementTagNameMap, children: NODE[]) => { + const value = tagName.trim(); + return { type: NODE_TYPES.TAG, value, children }; +}; + +export const voidTagNode = (tagName: keyof HTMLElementTagNameMap) => { + const value = tagName.trim(); + return { type: NODE_TYPES.VOID_TAG, value }; +}; + +export const isNode = (checked: any) => { + return !!checked?.type; +}; diff --git a/client2/src/localization/Translator/lib/parser.ts b/client2/src/localization/Translator/lib/parser.ts new file mode 100644 index 00000000..0c2134f0 --- /dev/null +++ b/client2/src/localization/Translator/lib/parser.ts @@ -0,0 +1,335 @@ +/* eslint-disable no-param-reassign */ +import { + tagNode, + textNode, + isNode, + placeholderNode, + voidTagNode, + NODE, +} from './nodes'; + +enum STATE { + /** + * parser function switches to the text state when parses simple text, + * or content between open and close tags + */ + TEXT = 'text', + + /** + * parser function switches to the tag state when meets open tag brace ("<"), and switches back, + * when meets closing tag brace (">") + */ + TAG = 'tag', + + /** + * Parser function switches to the placeholder state when meets in the text + * open placeholders brace ("{") and switches back to the text state, + * when meets close placeholder brace ("}") + */ + PLACEHOLDER = 'placeholder', +} + +enum CONTROL_CHARS { + TAG_OPEN_BRACE = '<', + TAG_CLOSE_BRACE = '>', + CLOSING_TAG_MARK = '/', + PLACEHOLDER_MARK = '%', +} + +interface Context { + /** + * Stack is used to keep and search nested tag nodes + * @type {*[]} + */ + stack: (NODE | keyof HTMLElementTagNameMap)[]; + /** + * Result is stack where function allocates nodes + * @type {*[]} + */ + result: NODE[]; + /** + * Current char index + * @type {number} + */ + currIdx: number; + /** + * Saves index of the last state change from the text state, + * used to restore parsed text if we moved into other state wrongly + */ + lastTextStateChangeIdx: number; + /** + * Accumulated tag value + */ + tag: string; + /** + * Accumulated text value + */ + text: string; + /** + * Accumulated placeholder value + */ + placeholder: string; + /** + * Parsed string + */ + str: string; + /** + * Currently parsed char + */ + currChar: string; +} + +/** + * Checks if text length is enough to create text node + * If text node created, then if stack is not empty it is pushed into stack, + * otherwise into result + * @param context + */ +const createTextNodeIfPossible = (context: Context) => { + const { text } = context; + + if (text.length > 0) { + const node = textNode(text); + if (context.stack.length > 0) { + context.stack.push(node); + } else { + context.result.push(node); + } + } + + context.text = ''; +}; + +/** + * Handles text state + * @returns {function} + */ +const textStateHandler = (context: Context) => { + const { currChar, currIdx } = context; + + // switches to the tag state + if (currChar === CONTROL_CHARS.TAG_OPEN_BRACE) { + context.lastTextStateChangeIdx = currIdx; + return STATE.TAG; + } + + // switches to the placeholder state + if (currChar === CONTROL_CHARS.PLACEHOLDER_MARK) { + context.lastTextStateChangeIdx = currIdx; + return STATE.PLACEHOLDER; + } + + // remains in the text state + context.text += currChar; + return STATE.TEXT; +}; + +/** + * Handles placeholder state + * @param context + * @returns {string} + */ +const placeholderStateHandler = (context: Context) => { + const { + currChar, + currIdx, + lastTextStateChangeIdx, + placeholder, + stack, + result, + str, + } = context; + + if (currChar === CONTROL_CHARS.PLACEHOLDER_MARK) { + // if distance between current index and last state change equal to 1, + // it means that placeholder mark was escaped by itself e.g. "%%", + // so we return to the text state + if (currIdx - lastTextStateChangeIdx === 1) { + context.text += str.substring(lastTextStateChangeIdx, currIdx); + return STATE.TEXT; + } + + createTextNodeIfPossible(context); + const node = placeholderNode(placeholder); + + // push node to the appropriate stack + if (stack.length > 0) { + stack.push(node); + } else { + result.push(node); + } + + context.placeholder = ''; + return STATE.TEXT; + } + + context.placeholder += currChar; + return STATE.PLACEHOLDER; +}; + +/** + * Switches current state to the tag state and returns tag state handler + * @returns {function} + */ +const tagStateHandler = (context: Context) => { + const { + currChar, + text, + stack, + result, + lastTextStateChangeIdx, + currIdx, + str, + } = context; + + let { tag } = context; + + // if found tag end ">" + if (currChar === CONTROL_CHARS.TAG_CLOSE_BRACE) { + // if the tag is close tag e.g. + if (tag.indexOf(CONTROL_CHARS.CLOSING_TAG_MARK) === 0) { + // remove slash from tag + tag = tag.substring(1); + + let children: NODE[] = []; + if (text.length > 0) { + children.push(textNode(text)); + context.text = ''; + } + + let pairTagFound = false; + // looking for the pair to the close tag + while (!pairTagFound && stack.length > 0) { + const lastFromStack = stack.pop(); + // if tag from stack equal to close tag + if (lastFromStack === tag) { + // create tag node + const node = tagNode(tag as keyof HTMLElementTagNameMap, children); + // and add it to the appropriate stack + if (stack.length > 0) { + stack.push(node); + } else { + result.push(node); + } + children = []; + pairTagFound = true; + } else if (isNode(lastFromStack)) { + // add nodes between close tag and open tag to the children + children.unshift(lastFromStack as NODE); + } else { + throw new Error(`String has unbalanced tags: ${str}`); + } + if (stack.length === 0 && children.length > 0) { + throw new Error(`String has unbalanced tags: ${str}`); + } + } + context.tag = ''; + return STATE.TEXT; + } + + // if the tag is void tag e.g. + if (tag.lastIndexOf(CONTROL_CHARS.CLOSING_TAG_MARK) === tag.length - 1) { + tag = tag.substring(0, tag.length - 1); + createTextNodeIfPossible(context); + const node = voidTagNode(tag as keyof HTMLElementTagNameMap); + // add node to the appropriate stack + if (stack.length > 0) { + stack.push(node); + } else { + result.push(node); + } + context.tag = ''; + return STATE.TEXT; + } + + createTextNodeIfPossible(context); + stack.push(tag as keyof HTMLElementTagNameMap); + context.tag = ''; + return STATE.TEXT; + } + + // If we meet open tag "<" it means that we wrongly moved into tag state + if (currChar === CONTROL_CHARS.TAG_OPEN_BRACE) { + context.text += str.substring(lastTextStateChangeIdx, currIdx); + context.lastTextStateChangeIdx = currIdx; + context.tag = ''; + return STATE.TAG; + } + + context.tag += currChar; + return STATE.TAG; +}; + +/** + * Parses string into AST (abstract syntax tree) and returns it + * e.g. + * parse("String to translate") -> + * ``` + * [ + * { type: 'text', value: 'String to ' }, + * { type: 'tag', value: 'a', children: [{ type: 'text', value: 'translate' }] } + * ]; + * ``` + * Empty string is parsed into empty AST (abstract syntax tree): "[]" + * If founds unbalanced tags, it throws error about it + * + * @param {string} str - message in simplified ICU like syntax without plural support + * @returns {[]} + */ +const parser = (str = '') => { + const context: Context = { + str, + stack: [], + result: [], + currIdx: 0, + lastTextStateChangeIdx: 0, + tag: '', + text: '', + placeholder: '', + currChar: '', + }; + + const STATE_HANDLERS = { + [STATE.TEXT]: textStateHandler, + [STATE.PLACEHOLDER]: placeholderStateHandler, + [STATE.TAG]: tagStateHandler, + }; + + // Start from text state + let currentState = STATE.TEXT; + + while (context.currIdx < str.length) { + context.currChar = str[context.currIdx]; + const currentStateHandler: (c: Context) => STATE = STATE_HANDLERS[currentState]; + currentState = currentStateHandler(context); + context.currIdx += 1; + } + + const { + result, + text, + stack, + lastTextStateChangeIdx, + } = context; + + // Means that tag or placeholder nodes were not closed, so we consider them as text + if (currentState !== STATE.TEXT) { + const restText = str.substring(lastTextStateChangeIdx); + if ((restText + text).length > 0) { + result.push(textNode(text + restText)); + } + } else { + // eslint-disable-next-line no-lonely-if + if (text.length > 0) { + result.push(textNode(text)); + } + } + + if (stack.length > 0) { + throw new Error(`String has unbalanced tags ${context.str}`); + } + + return result; +}; + +export default parser; diff --git a/client2/src/localization/Translator/lib/plural.ts b/client2/src/localization/Translator/lib/plural.ts new file mode 100644 index 00000000..a705fcca --- /dev/null +++ b/client2/src/localization/Translator/lib/plural.ts @@ -0,0 +1,394 @@ +/* eslint-disable no-nested-ternary */ +export type SupportedLangs = 'az' | 'bo' | 'dz' | 'id' | 'ja' | 'jv' | 'ka' | 'km' | 'kn' | 'ko' | 'ms' | 'th' | 'tr' | 'vi' | 'zh' | 'af' | 'bn' | 'bg' | 'ca' | 'da' | 'de' | 'el' | 'en' | 'eo' | 'es' | 'et' | 'eu' | 'fa' | 'fi' | 'fo' | 'fur' | 'fy' | 'gl' | 'gu' | 'ha' | 'he' | 'hu' | 'is' | 'it' | 'ku' | 'lb' | 'ml' | 'mn' | 'mr' | 'nah' | 'nb' | 'ne' | 'nl' | 'nn' | 'no' | 'oc' | 'om' | 'or' | 'pa' | 'pap' | 'ps' | 'pt' | 'so' | 'sq' | 'sv' | 'sw' | 'ta' | 'te' | 'tk' | 'ur' | 'zu' | 'am' | 'bh' | 'fil' | 'fr' | 'gun' | 'hi' | 'hy' | 'ln' | 'mg' | 'nso' | 'xbr' | 'ti' | 'wa' | 'be' | 'bs' | 'hr' | 'ru' | 'sr' | 'uk' | 'cs' | 'sk' | 'ga' | 'lt' | 'sl' | 'mk' | 'mt' | 'lv' | 'pl' | 'cy' | 'ro' | 'ar'; + +export type GenericLocales = { + [key in SupportedLangs]: SupportedLangs; +}; + +export enum AvailableLocales { + az = 'az', + bo = 'bo', + dz = 'dz', + id = 'id', + ja = 'ja', + jv = 'jv', + ka = 'ka', + km = 'km', + kn = 'kn', + ko = 'ko', + ms = 'ms', + th = 'th', + tr = 'tr', + vi = 'vi', + zh = 'zh', + af = 'af', + bn = 'bn', + bg = 'bg', + ca = 'ca', + da = 'da', + de = 'de', + el = 'el', + en = 'en', + eo = 'eo', + es = 'es', + et = 'et', + eu = 'eu', + fa = 'fa', + fi = 'fi', + fo = 'fo', + fur = 'fur', + fy = 'fy', + gl = 'gl', + gu = 'gu', + ha = 'ha', + he = 'he', + hu = 'hu', + is = 'is', + it = 'it', + ku = 'ku', + lb = 'lb', + ml = 'ml', + mn = 'mn', + mr = 'mr', + nah = 'nah', + nb = 'nb', + ne = 'ne', + nl = 'nl', + nn = 'nn', + no = 'no', + oc = 'oc', + om = 'om', + or = 'or', + pa = 'pa', + pap = 'pap', + ps = 'ps', + pt = 'pt', + so = 'so', + sq = 'sq', + sv = 'sv', + sw = 'sw', + ta = 'ta', + te = 'te', + tk = 'tk', + ur = 'ur', + zu = 'zu', + am = 'am', + bh = 'bh', + fil = 'fil', + fr = 'fr', + gun = 'gun', + hi = 'hi', + hy = 'hy', + ln = 'ln', + mg = 'mg', + nso = 'nso', + xbr = 'xbr', + ti = 'ti', + wa = 'wa', + be = 'be', + bs = 'bs', + hr = 'hr', + ru = 'ru', + sr = 'sr', + uk = 'uk', + cs = 'cs', + sk = 'sk', + ga = 'ga', + lt = 'lt', + sl = 'sl', + mk = 'mk', + mt = 'mt', + lv = 'lv', + pl = 'pl', + cy = 'cy', + ro = 'ro', + ar = 'ar', +} +export const getPluralFormId = (locale: AvailableLocales, number: number) => { + if (number === 0) { + return 0; + } + const slavNum = ((number % 10 === 1) && (number % 100 !== 11)) + ? 1 + : ( + ((number % 10 >= 2) && (number % 10 <= 4) && ((number % 100 < 10) + || (number % 100 >= 20)) + ) + ? 2 + : 3); + const supportedForms: Record = { + [AvailableLocales.az]: 1, + [AvailableLocales.bo]: 1, + [AvailableLocales.dz]: 1, + [AvailableLocales.id]: 1, + [AvailableLocales.ja]: 1, + [AvailableLocales.jv]: 1, + [AvailableLocales.ka]: 1, + [AvailableLocales.km]: 1, + [AvailableLocales.kn]: 1, + [AvailableLocales.ko]: 1, + [AvailableLocales.ms]: 1, + [AvailableLocales.th]: 1, + [AvailableLocales.tr]: 1, + [AvailableLocales.vi]: 1, + [AvailableLocales.zh]: 1, + + [AvailableLocales.af]: (number === 1) ? 1 : 2, + [AvailableLocales.bn]: (number === 1) ? 1 : 2, + [AvailableLocales.bg]: (number === 1) ? 1 : 2, + [AvailableLocales.ca]: (number === 1) ? 1 : 2, + [AvailableLocales.da]: (number === 1) ? 1 : 2, + [AvailableLocales.de]: (number === 1) ? 1 : 2, + [AvailableLocales.el]: (number === 1) ? 1 : 2, + [AvailableLocales.en]: (number === 1) ? 1 : 2, + [AvailableLocales.eo]: (number === 1) ? 1 : 2, + [AvailableLocales.es]: (number === 1) ? 1 : 2, + [AvailableLocales.et]: (number === 1) ? 1 : 2, + [AvailableLocales.eu]: (number === 1) ? 1 : 2, + [AvailableLocales.fa]: (number === 1) ? 1 : 2, + [AvailableLocales.fi]: (number === 1) ? 1 : 2, + [AvailableLocales.fo]: (number === 1) ? 1 : 2, + [AvailableLocales.fur]: (number === 1) ? 1 : 2, + [AvailableLocales.fy]: (number === 1) ? 1 : 2, + [AvailableLocales.gl]: (number === 1) ? 1 : 2, + [AvailableLocales.gu]: (number === 1) ? 1 : 2, + [AvailableLocales.ha]: (number === 1) ? 1 : 2, + [AvailableLocales.he]: (number === 1) ? 1 : 2, + [AvailableLocales.hu]: (number === 1) ? 1 : 2, + [AvailableLocales.is]: (number === 1) ? 1 : 2, + [AvailableLocales.it]: (number === 1) ? 1 : 2, + [AvailableLocales.ku]: (number === 1) ? 1 : 2, + [AvailableLocales.lb]: (number === 1) ? 1 : 2, + [AvailableLocales.ml]: (number === 1) ? 1 : 2, + [AvailableLocales.mn]: (number === 1) ? 1 : 2, + [AvailableLocales.mr]: (number === 1) ? 1 : 2, + [AvailableLocales.nah]: (number === 1) ? 1 : 2, + [AvailableLocales.nb]: (number === 1) ? 1 : 2, + [AvailableLocales.ne]: (number === 1) ? 1 : 2, + [AvailableLocales.nl]: (number === 1) ? 1 : 2, + [AvailableLocales.nn]: (number === 1) ? 1 : 2, + [AvailableLocales.no]: (number === 1) ? 1 : 2, + [AvailableLocales.oc]: (number === 1) ? 1 : 2, + [AvailableLocales.om]: (number === 1) ? 1 : 2, + [AvailableLocales.or]: (number === 1) ? 1 : 2, + [AvailableLocales.pa]: (number === 1) ? 1 : 2, + [AvailableLocales.pap]: (number === 1) ? 1 : 2, + [AvailableLocales.ps]: (number === 1) ? 1 : 2, + [AvailableLocales.pt]: (number === 1) ? 1 : 2, + [AvailableLocales.so]: (number === 1) ? 1 : 2, + [AvailableLocales.sq]: (number === 1) ? 1 : 2, + [AvailableLocales.sv]: (number === 1) ? 1 : 2, + [AvailableLocales.sw]: (number === 1) ? 1 : 2, + [AvailableLocales.ta]: (number === 1) ? 1 : 2, + [AvailableLocales.te]: (number === 1) ? 1 : 2, + [AvailableLocales.tk]: (number === 1) ? 1 : 2, + [AvailableLocales.ur]: (number === 1) ? 1 : 2, + [AvailableLocales.zu]: (number === 1) ? 1 : 2, + + // how it works with 0? + [AvailableLocales.am]: ((number === 0) || (number === 1)) ? 0 : 1, + [AvailableLocales.bh]: ((number === 0) || (number === 1)) ? 0 : 1, + [AvailableLocales.fil]: ((number === 0) || (number === 1)) ? 0 : 1, + [AvailableLocales.fr]: ((number === 0) || (number === 1)) ? 0 : 1, + [AvailableLocales.gun]: ((number === 0) || (number === 1)) ? 0 : 1, + [AvailableLocales.hi]: ((number === 0) || (number === 1)) ? 0 : 1, + [AvailableLocales.hy]: ((number === 0) || (number === 1)) ? 0 : 1, + [AvailableLocales.ln]: ((number === 0) || (number === 1)) ? 0 : 1, + [AvailableLocales.mg]: ((number === 0) || (number === 1)) ? 0 : 1, + [AvailableLocales.nso]: ((number === 0) || (number === 1)) ? 0 : 1, + [AvailableLocales.xbr]: ((number === 0) || (number === 1)) ? 0 : 1, + [AvailableLocales.ti]: ((number === 0) || (number === 1)) ? 0 : 1, + [AvailableLocales.wa]: ((number === 0) || (number === 1)) ? 0 : 1, + + [AvailableLocales.be]: slavNum, + [AvailableLocales.bs]: slavNum, + [AvailableLocales.hr]: slavNum, + [AvailableLocales.ru]: slavNum, + [AvailableLocales.sr]: slavNum, + [AvailableLocales.uk]: slavNum, + + [AvailableLocales.cs]: (number === 1) ? 1 : (((number >= 2) && (number <= 4)) ? 2 : 3), + [AvailableLocales.sk]: (number === 1) ? 1 : (((number >= 2) && (number <= 4)) ? 2 : 3), + [AvailableLocales.ga]: (number === 1) ? 1 : ((number === 2) ? 2 : 3), + [AvailableLocales.lt]: ((number % 10 === 1) && (number % 100 !== 11)) + ? 1 + : (((number % 10 >= 2) && ((number % 100 < 10) || (number % 100 >= 20))) + ? 2 + : 3), + [AvailableLocales.sl]: (number % 100 === 1) + ? 1 + : ((number % 100 === 2) + ? 2 + : (((number % 100 === 3) || (number % 100 === 4)) + ? 3 + : 4)), + [AvailableLocales.mk]: (number % 10 === 1) ? 1 : 2, + [AvailableLocales.mt]: (number === 1) + ? 1 + : (((number === 0) || ((number % 100 > 1) && (number % 100 < 11))) + ? 2 + : (((number % 100 > 10) && (number % 100 < 20)) + ? 3 + : 4)), + [AvailableLocales.lv]: (number === 0) + ? 0 + : (((number % 10 === 1) && (number % 100 !== 11)) + ? 1 + : 2), + [AvailableLocales.pl]: (number === 1) + ? 1 + : ( + ((number % 10 >= 2) && (number % 10 <= 4) && ((number % 100 < 12) + || (number % 100 > 14)) + ) + ? 2 + : 3), + [AvailableLocales.cy]: (number === 1) + ? 0 + : ((number === 2) + ? 1 + : (((number === 8) || (number === 11)) + ? 2 + : 3)), + [AvailableLocales.ro]: (number === 1) + ? 1 + : (((number === 1) || ((number % 100 > 0) && (number % 100 < 20))) + ? 2 + : 3), + [AvailableLocales.ar]: (number === 0) + ? 0 + : ((number === 1) + ? 1 + : ((number === 2) + ? 2 + : (((number % 100 >= 3) && (number % 100 <= 10)) + ? 3 + : (((number % 100 >= 11) && (number % 100 <= 99)) + ? 4 + : 5)))), + + }; + return supportedForms[locale]; +}; +export const pluraFormsCount: Record = { + [AvailableLocales.az]: 2, + [AvailableLocales.bo]: 2, + [AvailableLocales.dz]: 2, + [AvailableLocales.id]: 2, + [AvailableLocales.ja]: 2, + [AvailableLocales.jv]: 2, + [AvailableLocales.ka]: 2, + [AvailableLocales.km]: 2, + [AvailableLocales.kn]: 2, + [AvailableLocales.ko]: 2, + [AvailableLocales.ms]: 2, + [AvailableLocales.th]: 2, + [AvailableLocales.tr]: 2, + [AvailableLocales.vi]: 2, + [AvailableLocales.zh]: 2, + [AvailableLocales.af]: 3, + [AvailableLocales.bn]: 3, + [AvailableLocales.bg]: 3, + [AvailableLocales.ca]: 3, + [AvailableLocales.da]: 3, + [AvailableLocales.de]: 3, + [AvailableLocales.el]: 3, + [AvailableLocales.en]: 3, + [AvailableLocales.eo]: 3, + [AvailableLocales.es]: 3, + [AvailableLocales.et]: 3, + [AvailableLocales.eu]: 3, + [AvailableLocales.fa]: 3, + [AvailableLocales.fi]: 3, + [AvailableLocales.fo]: 3, + [AvailableLocales.fur]: 3, + [AvailableLocales.fy]: 3, + [AvailableLocales.gl]: 3, + [AvailableLocales.gu]: 3, + [AvailableLocales.ha]: 3, + [AvailableLocales.he]: 3, + [AvailableLocales.hu]: 3, + [AvailableLocales.is]: 3, + [AvailableLocales.it]: 3, + [AvailableLocales.ku]: 3, + [AvailableLocales.lb]: 3, + [AvailableLocales.ml]: 3, + [AvailableLocales.mn]: 3, + [AvailableLocales.mr]: 3, + [AvailableLocales.nah]: 3, + [AvailableLocales.nb]: 3, + [AvailableLocales.ne]: 3, + [AvailableLocales.nl]: 3, + [AvailableLocales.nn]: 3, + [AvailableLocales.no]: 3, + [AvailableLocales.oc]: 3, + [AvailableLocales.om]: 3, + [AvailableLocales.or]: 3, + [AvailableLocales.pa]: 3, + [AvailableLocales.pap]: 3, + [AvailableLocales.ps]: 3, + [AvailableLocales.pt]: 3, + [AvailableLocales.so]: 3, + [AvailableLocales.sq]: 3, + [AvailableLocales.sv]: 3, + [AvailableLocales.sw]: 3, + [AvailableLocales.ta]: 3, + [AvailableLocales.te]: 3, + [AvailableLocales.tk]: 3, + [AvailableLocales.ur]: 3, + [AvailableLocales.zu]: 3, + [AvailableLocales.am]: 2, + [AvailableLocales.bh]: 2, + [AvailableLocales.fil]: 2, + [AvailableLocales.fr]: 2, + [AvailableLocales.gun]: 2, + [AvailableLocales.hi]: 2, + [AvailableLocales.hy]: 2, + [AvailableLocales.ln]: 2, + [AvailableLocales.mg]: 2, + [AvailableLocales.nso]: 2, + [AvailableLocales.xbr]: 2, + [AvailableLocales.ti]: 2, + [AvailableLocales.wa]: 2, + [AvailableLocales.be]: 4, + [AvailableLocales.bs]: 4, + [AvailableLocales.hr]: 4, + [AvailableLocales.ru]: 4, + [AvailableLocales.sr]: 4, + [AvailableLocales.uk]: 4, + [AvailableLocales.cs]: 4, + [AvailableLocales.sk]: 4, + [AvailableLocales.ga]: 4, + [AvailableLocales.lt]: 4, + [AvailableLocales.sl]: 5, + [AvailableLocales.mk]: 3, + [AvailableLocales.mt]: 5, + [AvailableLocales.lv]: 3, + [AvailableLocales.pl]: 4, + [AvailableLocales.cy]: 4, + [AvailableLocales.ro]: 4, + [AvailableLocales.ar]: 6, +}; + +const PLURAL_STRING_DELIMITER = '|'; + +export const checkForms = (str: string, locale: AvailableLocales, id: string) => { + const forms = str.split(PLURAL_STRING_DELIMITER); + if (forms.length !== pluraFormsCount[locale]) { + throw new Error(`Invalid plural string "${id}" for locale ${locale}: ${forms.length} given; need: ${pluraFormsCount[locale]}`); + } +}; +export const checkFormsExternal = (str: string, locale: AvailableLocales, id: string) => { + try { + checkForms(str, locale, id); + return true; + } catch (error) { + return false; + } +}; +export const getForm = (str: string, number: number, locale: AvailableLocales, id: string) => { + checkForms(str, locale, id); + const forms = str.split(PLURAL_STRING_DELIMITER); + const currentForm = getPluralFormId(locale, number); + return forms[currentForm].trim(); +}; diff --git a/client2/src/localization/Translator/lib/translator.ts b/client2/src/localization/Translator/lib/translator.ts new file mode 100644 index 00000000..e791d065 --- /dev/null +++ b/client2/src/localization/Translator/lib/translator.ts @@ -0,0 +1,9 @@ +import parser from './parser'; +import format, { AllowedValues } from './formatter'; + +const translator = (message: string, values: AllowedValues) => { + const astMessage = parser(message); + const formatted = format(astMessage, values); + return formatted; +}; +export default translator; diff --git a/client2/src/localization/Translator/lib/validator.ts b/client2/src/localization/Translator/lib/validator.ts new file mode 100644 index 00000000..3ad7317d --- /dev/null +++ b/client2/src/localization/Translator/lib/validator.ts @@ -0,0 +1,60 @@ +import parser from './parser'; +import { isTextNode, NODE } from './nodes'; + +/** + * Compares two AST (abstract syntax tree) structures, + * view tests for examples + * @param baseAst + * @param targetAst + * @returns {boolean} + */ +const areAstStructuresSame = (baseAst: NODE[], targetAst: NODE[]) => { + const textNodeFilter = (node: NODE) => { + return !isTextNode(node); + }; + + const filteredBaseAst = baseAst.filter(textNodeFilter); + + const filteredTargetAst = targetAst.filter(textNodeFilter); + + // if AST structures have different lengths, they are not equal + if (filteredBaseAst.length !== filteredTargetAst.length) { + return false; + } + + for (let i = 0; i < filteredBaseAst.length; i += 1) { + const baseNode = filteredBaseAst[i]; + + const targetNode = filteredTargetAst.find((node) => { + return node.type === baseNode.type && node.value === baseNode.value; + }); + + if (!targetNode) { + return false; + } + + if (targetNode.children && baseNode.children) { + const areChildrenSame = areAstStructuresSame(baseNode.children, targetNode.children); + if (!areChildrenSame) { + return false; + } + } + } + + return true; +}; + +/** + * Validates translation against base string by AST (abstract syntax tree) structure + * @param baseStr + * @param targetStr + * @returns {boolean} + */ +export const isTargetStrValid = (baseStr: string, targetStr: string) => { + const baseAst = parser(baseStr); + const targetAst = parser(targetStr); + + const result = areAstStructuresSame(baseAst, targetAst); + + return result; +}; diff --git a/client2/src/localization/index.ts b/client2/src/localization/index.ts new file mode 100644 index 00000000..1006227e --- /dev/null +++ b/client2/src/localization/index.ts @@ -0,0 +1,6 @@ +import T from './Translator'; +import { Locale } from './locales'; + +export { messages, DatePickerLocale, Locale, DEFAULT_LOCALE, LANGUAGES, reactFormater } from './locales'; +export type Translator = T; +export default T; diff --git a/client2/src/localization/locales/en.json b/client2/src/localization/locales/en.json new file mode 100644 index 00000000..99ae88d4 --- /dev/null +++ b/client2/src/localization/locales/en.json @@ -0,0 +1,48 @@ +{ + "back": "Back", + "ethernet": "Ethernet", + "localhost": "localhost", + "login": "Login", + "password": "Password", + "next": "Next", + "port": "Port", + + + "install_admin_interface_port_desc": "Now it is working at 3000 port, just in case, but we recomended to use 80 port. Using this ports allow to access to Web interface like to common site", + "install_admin_interface_port": "Which port will be used", + "install_admin_interface_title_decs": "Admin web interface is used to control AdGuard Home. You can open it in your browser and it does not require using a client-side program", + "install_admin_interface_title": "Admin interface settings", + "install_admin_interface_where_interface_desc": "Set what kind of networks will be able to access to Admin interface. For example: if you choose a local interface only, then Admin inteface will be accessed by this local device only", + "install_admin_interface_where_interface": "Where can I open Admin interface", + + "install_all_networks_description": "All available web interfaces", + "install_all_networks": "All networks", + "install_choose_networks_desc": "For advanced users", + "install_choose_networks": "Choose manually", + + "install_wellcome_button": "Let's go", + "install_wellcome_desc": "You have installed AdGuard Home on your device. It is a network-wide ad-and-tracker blocking DNS server with Admin Web interface. Let’s set some settings to correct DNS working", + "install_wellcome_title": "Welcome to AdGuard Home", + + "install_auth_title": "Login and password", + "install_auth_description": "Set login and password for accessing to Web interface", + + "install_dns_server_title": "DNS server settings", + "install_dns_server_desc": "AdGuard DNS server works like common DNS server but also blocks ads and tracking domains", + "install_dns_server_network_interfaces": "Network interfaces", + "install_dns_server_network_interfaces_desc": "You should set for what kind of networks will be use AdGuard Home DNS server. Most often you need to have available all interfaces", + "install_dns_server_port": "Which port will be used", + "install_dns_server_port_desc": "You have to use port 53 for correct internet working. Change this value only if you have reason", + "install_dns_server_non_static_ip": "How to use non-static IP adresses?", + + "install_configure_title": "Configure your devices", + "install_configure_danger_notice": "IMPORTANT! To start using AdGuard Home, you need to configure your devices manually", + "install_configure_how_to_title": "How to configure Router", + "install_configure_router": "

This setup will automatically cover all the devices connected to your home router and you will not need to configure each of them manually.

Open the preferences for your router. Usually, you can access it from your browser via a URL (like http://192.168.0.1/ or http://192.168.1.1/). You may be asked to enter the password. If you don't remember it, you can often reset the password by pressing a button on the router itself. Some routers require a specific application, which in that case should be already installed on your computer/phone.

Find the DHCP/DNS settings. Look for the DNS letters next to a field which allows two or three sets of numbers, each broken into four groups of one to three digits.

Enter your AdGuard Home server addresses there.

", + "install_configure_windows": "

Open Control Panel through Start menu or Windows search.

Go to Network and Internet category and then to Network and Sharing Center.

On the left side of the screen find Change adapter settings and click on it.

Select your active connection, right-click on it and choose Properties.

Find Internet Protocol Version 4 (TCP/IP) in the list, select it and then click on Properties again.

Choose Use the following DNS server addresses and enter your AdGuard Home server addresses.

", + "install_configure_macos": "

Click on Apple icon and go to System Preferences.

Click on Network.

Select the first connection in your list and click Advanced.

Select the DNS tab and enter your AdGuard Home server addresses.

", + "install_configure_android": "

From the Android Menu home screen, tap Settings.

Tap Wi-Fi on the menu. The screen listing all of the available networks will be shown (it is impossible to set custom DNS for mobile connection).

Long press the network you're connected to, and tap Modify Network.

On some devices, you may need to check the box for Advanced to see further settings. To adjust your Android DNS settings, you will need to switch the IP settings from DHCP to Static.

Change set DNS 1 and DNS 2 values to your AdGuard Home server addresses.

", + "install_configure_ios": "

From the home screen, tap Settings.

Choose Wi-Fi in the left menu (it is impossible to configure DNS for mobile networks).

Tap on the name of the currently active network.

In the DNS field enter your AdGuard Home server addresses.

", + "install_configure_adresses": "AdGuard Home addresses:", + "install_configure_dhcp": "You can't set a custom DNS server on some types of routers. In this case it may help if you set up AdGuard Home as a DHCP server. Otherwise, you should search for the manual on how to customize DNS servers for your particular router model." +} \ No newline at end of file diff --git a/client2/src/localization/locales/index.ts b/client2/src/localization/locales/index.ts new file mode 100644 index 00000000..ec51ba71 --- /dev/null +++ b/client2/src/localization/locales/index.ts @@ -0,0 +1,43 @@ +import React from 'react'; +import 'dayjs/locale/ru'; + +import { PickerLocale } from 'antd/es/date-picker/generatePicker'; +import ruPicker from 'antd/es/date-picker/locale/ru_RU'; +import enPicker from 'antd/es/date-picker/locale/en_GB'; + +import ruLang from './ru.json'; +import enLang from './en.json'; + +export enum Locale { + en = 'en', + ru = 'ru', +} +export const DatePickerLocale: Record = { + [Locale.ru]: ruPicker, + [Locale.en]: enPicker, +}; + +export const messages: Record> = { + [Locale.ru]: ruLang, + [Locale.en]: enLang, +}; + +export const DEFAULT_LOCALE = Locale.en; + +export const reactFormater = (data: (JSX.Element | string)[]) => { + if (data.every((d) => typeof d === 'string')) { + return data.join(''); + } + return React.Children.toArray(data); +}; + +export const LANGUAGES: { code: Locale; name: string }[] = [ + { + code: Locale.en, + name: 'English', + }, + { + code: Locale.ru, + name: 'Русский', + }, +]; diff --git a/client2/src/localization/locales/ru.json b/client2/src/localization/locales/ru.json new file mode 100644 index 00000000..af53fc02 --- /dev/null +++ b/client2/src/localization/locales/ru.json @@ -0,0 +1,4 @@ +{ + "install_wellcome_title": "Добро пожаловать в AdGuard Home", + "install_wellcome_desc": "Русский текст" +} \ No newline at end of file diff --git a/client2/src/main.pcss b/client2/src/main.pcss new file mode 100644 index 00000000..5efd7a6b --- /dev/null +++ b/client2/src/main.pcss @@ -0,0 +1,33 @@ +:root { + /*GRAYSCALE*/ + --white: #ffffff; + --gray100: #f3f3f3; + --gray300: #d8d8d8; + --gray400: #a4a4a4; + --gray700: #888888; + --gray900: #4d4d4d; + --black: #131313; + /*FEEDBACK*/ + --red400: #c23814; + --orange400: #eb9300; + /*BRAND*/ + --green400: #67b279; + --green700: #4d995f; + /*ADDITIONAL*/ + --purple400: #b267a0; + --purple700: #994d87; + --slateblue400: #677bb2; + --slateblue700: #4d6199; + /*VPN*/ + --vpngreen400: #74a352; + --vpngreen700: #568040; + --vpnslateblue700: #575770; + --vpnslateblue800: #464558; + --vpnseagreen: #5eb0b7; + +} + +body { + font-size: 14px; + color: var(--gray900); +} \ No newline at end of file diff --git a/client2/src/store/index.ts b/client2/src/store/index.ts new file mode 100644 index 00000000..a1c7b93b --- /dev/null +++ b/client2/src/store/index.ts @@ -0,0 +1 @@ +export { default, Store, storeValue } from './store'; diff --git a/client2/src/store/installStore.ts b/client2/src/store/installStore.ts new file mode 100644 index 00000000..dfe7bfd7 --- /dev/null +++ b/client2/src/store/installStore.ts @@ -0,0 +1,19 @@ +import { createContext } from 'react'; +import Install from './stores/Install'; +import UI from './stores/ui'; + +export class Store { + ui: UI; + + install: Install; + + constructor() { + this.ui = new UI(this); + this.install = new Install(this); + } +} + +export const storeValue = new Store(); + +const StoreContext = createContext(storeValue); +export default StoreContext; diff --git a/client2/src/store/store.ts b/client2/src/store/store.ts new file mode 100644 index 00000000..24edb778 --- /dev/null +++ b/client2/src/store/store.ts @@ -0,0 +1,15 @@ +import { createContext } from 'react'; +import UI from './stores/ui'; + +export class Store { + ui: UI; + + constructor() { + this.ui = new UI(this); + } +} + +export const storeValue = new Store(); + +const StoreContext = createContext(storeValue); +export default StoreContext; diff --git a/client2/src/store/stores/Install.ts b/client2/src/store/stores/Install.ts new file mode 100644 index 00000000..bd861ef2 --- /dev/null +++ b/client2/src/store/stores/Install.ts @@ -0,0 +1,50 @@ +import InstallApi from 'Apis/install'; +import AddressesInfoBeta, { IAddressesInfoBeta } from 'Entities/AddressesInfoBeta'; +import { ICheckConfigRequestBeta } from 'Entities/CheckConfigRequestBeta'; +import CheckConfigResponse, { ICheckConfigResponse } from 'Entities/CheckConfigResponse'; +import { IInitialConfigurationBeta } from 'Entities/InitialConfigurationBeta'; +import { errorChecker } from 'Helpers/apiErrors'; +import { flow, makeAutoObservable } from 'mobx'; + +import { Store } from 'Store'; + +export default class Install { + rootStore: Store; + + addresses: AddressesInfoBeta | null; + + constructor(rootStore: Store) { + this.rootStore = rootStore; + this.addresses = null; + + makeAutoObservable(this, { + rootStore: false, + getAddresses: flow, + }); + this.getAddresses(); + } + + * getAddresses() { + const response = yield InstallApi.installGetAddressesBeta(); + const { result } = errorChecker(response); + if (result) { + this.addresses = new AddressesInfoBeta(result); + } + } + + static async checkConfig(config: ICheckConfigRequestBeta) { + const response = await InstallApi.installCheckConfigBeta(config); + const { result } = errorChecker(response); + if (result) { + return new CheckConfigResponse(result); + } + } + + static async configure(config: IInitialConfigurationBeta) { + const response = await InstallApi.installConfigureBeta(config); + const { result } = errorChecker(response); + if (result) { + return true; + } + } +} diff --git a/client2/src/store/stores/ui.ts b/client2/src/store/stores/ui.ts new file mode 100644 index 00000000..648127a7 --- /dev/null +++ b/client2/src/store/stores/ui.ts @@ -0,0 +1,25 @@ +import { makeAutoObservable, observable } from 'mobx'; + +import Translator, { DEFAULT_LOCALE, messages, Locale, reactFormater } from 'Localization'; +import { Store } from 'Store'; + +export default class UI { + rootStore: Store; + + currentLang = DEFAULT_LOCALE; + + intl = new Translator(Locale.en, messages, DEFAULT_LOCALE, reactFormater); + + constructor(rootStore: Store) { + this.rootStore = rootStore; + makeAutoObservable(this, { + intl: observable.struct, + rootStore: false, + }); + } + + updateLang = (lang: Locale) => { + this.currentLang = lang; + this.intl = this.intl.updateTranslator(lang); + }; +} diff --git a/client2/tsconfig.json b/client2/tsconfig.json new file mode 100644 index 00000000..5c70b792 --- /dev/null +++ b/client2/tsconfig.json @@ -0,0 +1,39 @@ +{ + "compilerOptions": { + "allowSyntheticDefaultImports": true, + "alwaysStrict": true, + "target": "es6", + "module": "ESNext", + "moduleResolution": "node", + "noResolve": false, + "noImplicitAny": true, + "strict": true, + "removeComments": true, + "sourceMap": true, + "jsx": "react", + "resolveJsonModule": true, + "baseUrl": "src", + "paths": { + "Apis/*": ["lib/apis/*"], + "Common": ["components/common/index"], + "Common/*": ["components/common/*"], + "Components/*": ["components/*"], + "Consts/*": ["lib/consts/*"], + "Entities/*": ["lib/entities/*"], + "Hooks": ["lib/hooks"], + "Helpers/*": ["lib/helpers/*"], + "Lib/*": ["lib/*"], + "Localization": ["localization/index"], + "Routes": ["lib/routes"], + "Store": ["store/store"], + "Store/*": ["store/*"] + }, + "types": [ + "react", + "react-dom", + "node" + ] + }, + "include": ["src/**/*", "./declaration.d.ts"], + "exclude": ["./node_modules/**/*", "./scripts/generator/**/*"] +} diff --git a/client2/yarn.lock b/client2/yarn.lock new file mode 100644 index 00000000..a0891ba1 --- /dev/null +++ b/client2/yarn.lock @@ -0,0 +1,8713 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@ant-design/colors@^3.1.0": + version "3.2.2" + resolved "https://registry.yarnpkg.com/@ant-design/colors/-/colors-3.2.2.tgz#5ad43d619e911f3488ebac303d606e66a8423903" + integrity sha512-YKgNbG2dlzqMhA9NtI3/pbY16m3Yl/EeWBRa+lB1X1YaYxHrxNexiQYCLTWO/uDvAjLFMEDU+zR901waBtMtjQ== + dependencies: + tinycolor2 "^1.4.1" + +"@ant-design/colors@^4.0.5": + version "4.0.5" + resolved "https://registry.yarnpkg.com/@ant-design/colors/-/colors-4.0.5.tgz#d7d100d7545cca8f624954604a6892fc48ba5aae" + integrity sha512-3mnuX2prnWOWvpFTS2WH2LoouWlOgtnIpc6IarWN6GOzzLF8dW/U8UctuvIPhoboETehZfJ61XP+CGakBEPJ3Q== + dependencies: + tinycolor2 "^1.4.1" + +"@ant-design/css-animation@^1.7.2": + version "1.7.3" + resolved "https://registry.yarnpkg.com/@ant-design/css-animation/-/css-animation-1.7.3.tgz#60a1c970014e86b28f940510d69e503e428f1136" + integrity sha512-LrX0OGZtW+W6iLnTAqnTaoIsRelYeuLZWsrmBJFUXDALQphPsN8cE5DCsmoSlL0QYb94BQxINiuS70Ar/8BNgA== + +"@ant-design/icons-svg@^4.0.0": + version "4.1.0" + resolved "https://registry.yarnpkg.com/@ant-design/icons-svg/-/icons-svg-4.1.0.tgz#480b025f4b20ef7fe8f47d4a4846e4fee84ea06c" + integrity sha512-Fi03PfuUqRs76aI3UWYpP864lkrfPo0hluwGqh7NJdLhvH4iRDc3jbJqZIvRDLHKbXrvAfPPV3+zjUccfFvWOQ== + +"@ant-design/icons@^4.2.1", "@ant-design/icons@^4.2.2": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@ant-design/icons/-/icons-4.2.2.tgz#6533c5a02aec49238ec4748074845ad7d85a4f5e" + integrity sha512-DrVV+wcupnHS7PehJ6KiTcJtAR5c25UMgjGECCc6pUT9rsvw0AuYG+a4HDjfxEQuDqKTHwW+oX/nIvCymyLE8Q== + dependencies: + "@ant-design/colors" "^3.1.0" + "@ant-design/icons-svg" "^4.0.0" + "@babel/runtime" "^7.10.4" + classnames "^2.2.6" + insert-css "^2.0.0" + rc-util "^5.0.1" + +"@ant-design/react-slick@~0.27.0": + version "0.27.11" + resolved "https://registry.yarnpkg.com/@ant-design/react-slick/-/react-slick-0.27.11.tgz#ce788312ed8e64fcba2f7bb4556f47486b407c6e" + integrity sha512-KPJ1lleHW11bameFauI77Lb9N7O/4ulT1kplVdRQykWLv3oKVSGKVaekC3DM/Z0MYmKfCXCucpFnfgGMEHNM+w== + dependencies: + "@babel/runtime" "^7.10.4" + classnames "^2.2.5" + json2mq "^0.2.0" + lodash "^4.17.15" + resize-observer-polyfill "^1.5.0" + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a" + integrity sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg== + dependencies: + "@babel/highlight" "^7.10.4" + +"@babel/core@>=7.9.0": + version "7.12.3" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.12.3.tgz#1b436884e1e3bff6fb1328dc02b208759de92ad8" + integrity sha512-0qXcZYKZp3/6N2jKYVxZv0aNCsxTSVCiK72DTiTYZAu7sjg73W0/aynWjMbiGd87EQL4WyA8reiJVh92AVla9g== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/generator" "^7.12.1" + "@babel/helper-module-transforms" "^7.12.1" + "@babel/helpers" "^7.12.1" + "@babel/parser" "^7.12.3" + "@babel/template" "^7.10.4" + "@babel/traverse" "^7.12.1" + "@babel/types" "^7.12.1" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.1" + json5 "^2.1.2" + lodash "^4.17.19" + resolve "^1.3.2" + semver "^5.4.1" + source-map "^0.5.0" + +"@babel/generator@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.12.1.tgz#0d70be32bdaa03d7c51c8597dda76e0df1f15468" + integrity sha512-DB+6rafIdc9o72Yc3/Ph5h+6hUjeOp66pF0naQBgUFFuPqzQwIlPTm3xZR7YNvduIMtkDIj2t21LSQwnbCrXvg== + dependencies: + "@babel/types" "^7.12.1" + jsesc "^2.5.1" + source-map "^0.5.0" + +"@babel/helper-function-name@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz#d2d3b20c59ad8c47112fa7d2a94bc09d5ef82f1a" + integrity sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ== + dependencies: + "@babel/helper-get-function-arity" "^7.10.4" + "@babel/template" "^7.10.4" + "@babel/types" "^7.10.4" + +"@babel/helper-get-function-arity@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz#98c1cbea0e2332f33f9a4661b8ce1505b2c19ba2" + integrity sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A== + dependencies: + "@babel/types" "^7.10.4" + +"@babel/helper-member-expression-to-functions@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.12.1.tgz#fba0f2fcff3fba00e6ecb664bb5e6e26e2d6165c" + integrity sha512-k0CIe3tXUKTRSoEx1LQEPFU9vRQfqHtl+kf8eNnDqb4AUJEy5pz6aIiog+YWtVm2jpggjS1laH68bPsR+KWWPQ== + dependencies: + "@babel/types" "^7.12.1" + +"@babel/helper-module-imports@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.12.1.tgz#1644c01591a15a2f084dd6d092d9430eb1d1216c" + integrity sha512-ZeC1TlMSvikvJNy1v/wPIazCu3NdOwgYZLIkmIyAsGhqkNpiDoQQRmaCK8YP4Pq3GPTLPV9WXaPCJKvx06JxKA== + dependencies: + "@babel/types" "^7.12.1" + +"@babel/helper-module-transforms@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.12.1.tgz#7954fec71f5b32c48e4b303b437c34453fd7247c" + integrity sha512-QQzehgFAZ2bbISiCpmVGfiGux8YVFXQ0abBic2Envhej22DVXV9nCFaS5hIQbkyo1AdGb+gNME2TSh3hYJVV/w== + dependencies: + "@babel/helper-module-imports" "^7.12.1" + "@babel/helper-replace-supers" "^7.12.1" + "@babel/helper-simple-access" "^7.12.1" + "@babel/helper-split-export-declaration" "^7.11.0" + "@babel/helper-validator-identifier" "^7.10.4" + "@babel/template" "^7.10.4" + "@babel/traverse" "^7.12.1" + "@babel/types" "^7.12.1" + lodash "^4.17.19" + +"@babel/helper-optimise-call-expression@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz#50dc96413d594f995a77905905b05893cd779673" + integrity sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg== + dependencies: + "@babel/types" "^7.10.4" + +"@babel/helper-replace-supers@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.12.1.tgz#f15c9cc897439281891e11d5ce12562ac0cf3fa9" + integrity sha512-zJjTvtNJnCFsCXVi5rUInstLd/EIVNmIKA1Q9ynESmMBWPWd+7sdR+G4/wdu+Mppfep0XLyG2m7EBPvjCeFyrw== + dependencies: + "@babel/helper-member-expression-to-functions" "^7.12.1" + "@babel/helper-optimise-call-expression" "^7.10.4" + "@babel/traverse" "^7.12.1" + "@babel/types" "^7.12.1" + +"@babel/helper-simple-access@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.12.1.tgz#32427e5aa61547d38eb1e6eaf5fd1426fdad9136" + integrity sha512-OxBp7pMrjVewSSC8fXDFrHrBcJATOOFssZwv16F3/6Xtc138GHybBfPbm9kfiqQHKhYQrlamWILwlDCeyMFEaA== + dependencies: + "@babel/types" "^7.12.1" + +"@babel/helper-split-export-declaration@^7.11.0": + version "7.11.0" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz#f8a491244acf6a676158ac42072911ba83ad099f" + integrity sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg== + dependencies: + "@babel/types" "^7.11.0" + +"@babel/helper-validator-identifier@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz#a78c7a7251e01f616512d31b10adcf52ada5e0d2" + integrity sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw== + +"@babel/helpers@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.12.1.tgz#8a8261c1d438ec18cb890434df4ec768734c1e79" + integrity sha512-9JoDSBGoWtmbay98efmT2+mySkwjzeFeAL9BuWNoVQpkPFQF8SIIFUfY5os9u8wVzglzoiPRSW7cuJmBDUt43g== + dependencies: + "@babel/template" "^7.10.4" + "@babel/traverse" "^7.12.1" + "@babel/types" "^7.12.1" + +"@babel/highlight@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.10.4.tgz#7d1bdfd65753538fabe6c38596cdb76d9ac60143" + integrity sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA== + dependencies: + "@babel/helper-validator-identifier" "^7.10.4" + chalk "^2.0.0" + js-tokens "^4.0.0" + +"@babel/parser@^7.10.4", "@babel/parser@^7.12.1", "@babel/parser@^7.12.3": + version "7.12.3" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.12.3.tgz#a305415ebe7a6c7023b40b5122a0662d928334cd" + integrity sha512-kFsOS0IbsuhO5ojF8Hc8z/8vEIOkylVBrjiZUbLTE3XFe0Qi+uu6HjzQixkFaqr0ZPAMZcBVxEwmsnsLPZ2Xsw== + +"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.1", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.4", "@babel/runtime@^7.11.1", "@babel/runtime@^7.11.2", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.1.tgz#b4116a6b6711d010b2dad3b7b6e43bf1b9954740" + integrity sha512-J5AIf3vPj3UwXaAzb5j1xM4WAQDX3EMgemF8rjCP3SoW09LfRKAXQKt6CoVYl230P6iWdRcBbnLDDdnqWxZSCA== + dependencies: + regenerator-runtime "^0.13.4" + +"@babel/template@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.4.tgz#3251996c4200ebc71d1a8fc405fba940f36ba278" + integrity sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/parser" "^7.10.4" + "@babel/types" "^7.10.4" + +"@babel/traverse@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.12.1.tgz#941395e0c5cc86d5d3e75caa095d3924526f0c1e" + integrity sha512-MA3WPoRt1ZHo2ZmoGKNqi20YnPt0B1S0GTZEPhhd+hw2KGUzBlHuVunj6K4sNuK+reEvyiPwtp0cpaqLzJDmAw== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/generator" "^7.12.1" + "@babel/helper-function-name" "^7.10.4" + "@babel/helper-split-export-declaration" "^7.11.0" + "@babel/parser" "^7.12.1" + "@babel/types" "^7.12.1" + debug "^4.1.0" + globals "^11.1.0" + lodash "^4.17.19" + +"@babel/types@^7.10.4", "@babel/types@^7.11.0", "@babel/types@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.12.1.tgz#e109d9ab99a8de735be287ee3d6a9947a190c4ae" + integrity sha512-BzSY3NJBKM4kyatSOWh3D/JJ2O3CVzBybHWxtgxnggaxEuaSTTDqeiSb/xk9lrkw2Tbqyivw5ZU4rT+EfznQsA== + dependencies: + "@babel/helper-validator-identifier" "^7.10.4" + lodash "^4.17.19" + to-fast-properties "^2.0.0" + +"@csstools/convert-colors@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@csstools/convert-colors/-/convert-colors-1.4.0.tgz#ad495dc41b12e75d588c6db8b9834f08fa131eb7" + integrity sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw== + +"@dsherret/to-absolute-glob@^2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@dsherret/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz#1f6475dc8bd974cea07a2daf3864b317b1dd332c" + integrity sha1-H2R13IvZdM6gei2vOGSzF7HdMyw= + dependencies: + is-absolute "^1.0.0" + is-negated-glob "^1.0.0" + +"@eslint/eslintrc@^0.1.3": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.1.3.tgz#7d1a2b2358552cc04834c0979bd4275362e37085" + integrity sha512-4YVwPkANLeNtRjMekzux1ci8hIaH5eGKktGqR0d3LWsKNn5B2X/1Z6Trxy7jQXl9EBGE6Yj02O+t09FMeRllaA== + dependencies: + ajv "^6.12.4" + debug "^4.1.1" + espree "^7.3.0" + globals "^12.1.0" + ignore "^4.0.6" + import-fresh "^3.2.1" + js-yaml "^3.13.1" + lodash "^4.17.19" + minimatch "^3.0.4" + strip-json-comments "^3.1.1" + +"@nodelib/fs.scandir@2.1.3": + version "2.1.3" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz#3a582bdb53804c6ba6d146579c46e52130cf4a3b" + integrity sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw== + dependencies: + "@nodelib/fs.stat" "2.0.3" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.3", "@nodelib/fs.stat@^2.0.2": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz#34dc5f4cabbc720f4e60f75a747e7ecd6c175bd3" + integrity sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA== + +"@nodelib/fs.walk@^1.2.3": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz#011b9202a70a6366e436ca5c065844528ab04976" + integrity sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ== + dependencies: + "@nodelib/fs.scandir" "2.1.3" + fastq "^1.6.0" + +"@npmcli/move-file@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-1.0.1.tgz#de103070dac0f48ce49cf6693c23af59c0f70464" + integrity sha512-Uv6h1sT+0DrblvIrolFtbvM1FgWm+/sy4B3pvLp67Zys+thcukzS5ekn7HsZFGpWP4Q3fYJCljbWQE/XivMRLw== + dependencies: + mkdirp "^1.0.4" + +"@sentry/browser@5.27.0": + version "5.27.0" + resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-5.27.0.tgz#35b77f076fb5f08c91eff23f3c067ee15df0ab90" + integrity sha512-AByUVSy5YctTxWGA6HaeTkZXDCmHbeurqLkR6U9h4HzEHZq3laxrYQ1HiWcaW1IgFDqZcEmD14kDOVY4GhF3QQ== + dependencies: + "@sentry/core" "5.27.0" + "@sentry/types" "5.27.0" + "@sentry/utils" "5.27.0" + tslib "^1.9.3" + +"@sentry/core@5.27.0": + version "5.27.0" + resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.27.0.tgz#661b2fd1beecaa37c013a6c364330fa29c847b3c" + integrity sha512-ddvAxVszsHzFzGedii1NxfKU3GxAEGJV5eXNlA2hqS0/OKl+IOjuI6aJjg55LMTEEejqr9djXqDMk6y5av6UKg== + dependencies: + "@sentry/hub" "5.27.0" + "@sentry/minimal" "5.27.0" + "@sentry/types" "5.27.0" + "@sentry/utils" "5.27.0" + tslib "^1.9.3" + +"@sentry/hub@5.27.0": + version "5.27.0" + resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-5.27.0.tgz#dcd7b36d216997f0283bd3334cbce8004d56ef89" + integrity sha512-Qe4nndgDEY8n3kKEWJTw5M201dgsoB9ZQ10483cVpGCtOfZZuzXEr4EaLG3BefH8YFvlgUP3YlxD7XFoJioRjg== + dependencies: + "@sentry/types" "5.27.0" + "@sentry/utils" "5.27.0" + tslib "^1.9.3" + +"@sentry/minimal@5.27.0": + version "5.27.0" + resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-5.27.0.tgz#8c2fdcf9cd1e59d8ff1848a7905bac304f8e206b" + integrity sha512-KidWjo2jNd8IwPhEIDC0YddjwuIdVxTEsmpRkZ6afuiR5rMQsiqA0EwsndWiAjs67qxQRj/VD/1Xghxe0nHzXQ== + dependencies: + "@sentry/hub" "5.27.0" + "@sentry/types" "5.27.0" + tslib "^1.9.3" + +"@sentry/react@^5.27.0": + version "5.27.0" + resolved "https://registry.yarnpkg.com/@sentry/react/-/react-5.27.0.tgz#ef503b0c28b03088f1de7693f75276ccc96c5e14" + integrity sha512-7dYKhQ6tgDgAed1neXjID9mEviX9IzL/OkG+hU8zffUqcUtMziVHtvozf3ePz75ReRR/Bumc6fGnSMlnEcwbKg== + dependencies: + "@sentry/browser" "5.27.0" + "@sentry/minimal" "5.27.0" + "@sentry/types" "5.27.0" + "@sentry/utils" "5.27.0" + hoist-non-react-statics "^3.3.2" + tslib "^1.9.3" + +"@sentry/types@5.27.0": + version "5.27.0" + resolved "https://registry.yarnpkg.com/@sentry/types/-/types-5.27.0.tgz#cea288d02c727ef83541768b8738e6a829dfc831" + integrity sha512-coB2bMDxmzTwIWcXbzbnE2JtEqDkvmK9+KyZZNI/Mk3wwabFYqL7hOnqXB45/+hx+6l9/siWmB1l5um3tzqdOw== + +"@sentry/utils@5.27.0": + version "5.27.0" + resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-5.27.0.tgz#21c15401b43041b1208521465c09c64eafc2e0ff" + integrity sha512-XrdoxOsjqF9AVmeCefNgY0r3lvZBj34bzsG3TI8Z1bjQKB3iF/2yAI/bdo+sUqAiJiiPSk5p6SiPkyeTsSdBhg== + dependencies: + "@sentry/types" "5.27.0" + tslib "^1.9.3" + +"@stylelint/postcss-css-in-js@^0.37.2": + version "0.37.2" + resolved "https://registry.yarnpkg.com/@stylelint/postcss-css-in-js/-/postcss-css-in-js-0.37.2.tgz#7e5a84ad181f4234a2480803422a47b8749af3d2" + integrity sha512-nEhsFoJurt8oUmieT8qy4nk81WRHmJynmVwn/Vts08PL9fhgIsMhk1GId5yAN643OzqEEb5S/6At2TZW7pqPDA== + dependencies: + "@babel/core" ">=7.9.0" + +"@stylelint/postcss-markdown@^0.36.1": + version "0.36.1" + resolved "https://registry.yarnpkg.com/@stylelint/postcss-markdown/-/postcss-markdown-0.36.1.tgz#829b87e6c0f108014533d9d7b987dc9efb6632e8" + integrity sha512-iDxMBWk9nB2BPi1VFQ+Dc5+XpvODBHw2n3tYpaBZuEAFQlbtF9If0Qh5LTTwSi/XwdbJ2jt+0dis3i8omyggpw== + dependencies: + remark "^12.0.0" + unist-util-find-all-after "^3.0.1" + +"@ts-morph/common@~0.6.0": + version "0.6.0" + resolved "https://registry.yarnpkg.com/@ts-morph/common/-/common-0.6.0.tgz#cbd4ee57c5ef971511b9c5778e0bb8eb27de4783" + integrity sha512-pI35nZz5bs3tL3btSVX2cWkAE8rc80F+Fn4TwSC6bQvn7fgn9IyLXVcAfpG6X6NBY5wN9TkSWXn/QYUkBvR/Fw== + dependencies: + "@dsherret/to-absolute-glob" "^2.0.2" + fast-glob "^3.2.4" + fs-extra "^9.0.1" + is-negated-glob "^1.0.0" + multimatch "^4.0.0" + typescript "~4.0.2" + +"@types/anymatch@*": + version "1.3.1" + resolved "https://registry.yarnpkg.com/@types/anymatch/-/anymatch-1.3.1.tgz#336badc1beecb9dacc38bea2cf32adf627a8421a" + integrity sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA== + +"@types/classnames@^2.2.10": + version "2.2.10" + resolved "https://registry.yarnpkg.com/@types/classnames/-/classnames-2.2.10.tgz#cc658ca319b6355399efc1f5b9e818f1a24bf999" + integrity sha512-1UzDldn9GfYYEsWWnn/P4wkTlkZDH7lDb0wBMGbtIQc9zXEQq7FlKBdZUn6OBqD8sKZZ2RQO2mAjGpXiDGoRmQ== + +"@types/eslint-scope@^3.7.0": + version "3.7.0" + resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.0.tgz#4792816e31119ebd506902a482caec4951fabd86" + integrity sha512-O/ql2+rrCUe2W2rs7wMR+GqPRcgB6UiqN5RhrR5xruFlY7l9YLMn0ZkDzjoHLeiFkR8MCQZVudUuuvQ2BLC9Qw== + dependencies: + "@types/eslint" "*" + "@types/estree" "*" + +"@types/eslint@*": + version "7.2.6" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-7.2.6.tgz#5e9aff555a975596c03a98b59ecd103decc70c3c" + integrity sha512-I+1sYH+NPQ3/tVqCeUSBwTE/0heyvtXqpIopUUArlBm0Kpocb8FbMa3AZ/ASKIFpN3rnEx932TTXDbt9OXsNDw== + dependencies: + "@types/estree" "*" + "@types/json-schema" "*" + +"@types/estree@*", "@types/estree@^0.0.45": + version "0.0.45" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.45.tgz#e9387572998e5ecdac221950dab3e8c3b16af884" + integrity sha512-jnqIUKDUqJbDIUxm0Uj7bnlMnRm1T/eZ9N+AVMqhPgzrba2GhGG5o/jCTwmdPK709nEZsGoMzXEDUjcXHa3W0g== + +"@types/glob@^7.1.1": + version "7.1.3" + resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.3.tgz#e6ba80f36b7daad2c685acd9266382e68985c183" + integrity sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w== + dependencies: + "@types/minimatch" "*" + "@types/node" "*" + +"@types/history@*": + version "4.7.8" + resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.8.tgz#49348387983075705fe8f4e02fb67f7daaec4934" + integrity sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA== + +"@types/hoist-non-react-statics@^3.3.0": + version "3.3.1" + resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f" + integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA== + dependencies: + "@types/react" "*" + hoist-non-react-statics "^3.3.0" + +"@types/html-minifier-terser@^5.0.0": + version "5.1.1" + resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz#3c9ee980f1a10d6021ae6632ca3e79ca2ec4fb50" + integrity sha512-giAlZwstKbmvMk1OO7WXSj4OZ0keXAcl2TQq4LWHiiPH2ByaH7WeUzng+Qej8UPxxv+8lRTuouo0iaNDBuzIBA== + +"@types/http-proxy@^1.17.4": + version "1.17.4" + resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.4.tgz#e7c92e3dbe3e13aa799440ff42e6d3a17a9d045b" + integrity sha512-IrSHl2u6AWXduUaDLqYpt45tLVCtYv7o4Z0s1KghBCDgIIS9oW5K1H8mZG/A2CfeLdEa7rTd1ACOiHBc1EMT2Q== + dependencies: + "@types/node" "*" + +"@types/json-schema@*", "@types/json-schema@^7.0.3", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.6": + version "7.0.6" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.6.tgz#f4c7ec43e81b319a9815115031709f26987891f0" + integrity sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw== + +"@types/json5@^0.0.29": + version "0.0.29" + resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" + integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= + +"@types/minimatch@*", "@types/minimatch@^3.0.3": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" + integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== + +"@types/minimist@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.0.tgz#69a23a3ad29caf0097f06eda59b361ee2f0639f6" + integrity sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY= + +"@types/node@*": + version "14.14.0" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.0.tgz#f1091b6ad5de18e8e91bdbd43ec63f13de372538" + integrity sha512-BfbIHP9IapdupGhq/hc+jT5dyiBVZ2DdeC5WwJWQWDb0GijQlzUFAeIQn/2GtvZcd2HVUU7An8felIICFTC2qg== + +"@types/normalize-package-data@^2.4.0": + version "2.4.0" + resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e" + integrity sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA== + +"@types/parse-json@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" + integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== + +"@types/prop-types@*": + version "15.7.3" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7" + integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw== + +"@types/q@^1.5.1": + version "1.5.4" + resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.4.tgz#15925414e0ad2cd765bfef58842f7e26a7accb24" + integrity sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug== + +"@types/qs@^6.9.5": + version "6.9.5" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.5.tgz#434711bdd49eb5ee69d90c1d67c354a9a8ecb18b" + integrity sha512-/JHkVHtx/REVG0VVToGRGH2+23hsYLHdyG+GrvoUGlGAd0ErauXDyvHtRI/7H7mzLm+tBCKA7pfcpkQ1lf58iQ== + +"@types/react-dom@^16.9.8": + version "16.9.8" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.8.tgz#fe4c1e11dfc67155733dfa6aa65108b4971cb423" + integrity sha512-ykkPQ+5nFknnlU6lDd947WbQ6TE3NNzbQAkInC2EKY1qeYdTKp7onFusmYZb+ityzx2YviqT6BXSu+LyWWJwcA== + dependencies: + "@types/react" "*" + +"@types/react-redux@^7.1.9": + version "7.1.9" + resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.9.tgz#280c13565c9f13ceb727ec21e767abe0e9b4aec3" + integrity sha512-mpC0jqxhP4mhmOl3P4ipRsgTgbNofMRXJb08Ms6gekViLj61v1hOZEKWDCyWsdONr6EjEA6ZHXC446wdywDe0w== + dependencies: + "@types/hoist-non-react-statics" "^3.3.0" + "@types/react" "*" + hoist-non-react-statics "^3.3.0" + redux "^4.0.0" + +"@types/react-router-dom@^5.1.6": + version "5.1.6" + resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.1.6.tgz#07b14e7ab1893a837c8565634960dc398564b1fb" + integrity sha512-gjrxYqxz37zWEdMVvQtWPFMFj1dRDb4TGOcgyOfSXTrEXdF92L00WE3C471O3TV/RF1oskcStkXsOU0Ete4s/g== + dependencies: + "@types/history" "*" + "@types/react" "*" + "@types/react-router" "*" + +"@types/react-router@*": + version "5.1.8" + resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.8.tgz#4614e5ba7559657438e17766bb95ef6ed6acc3fa" + integrity sha512-HzOyJb+wFmyEhyfp4D4NYrumi+LQgQL/68HvJO+q6XtuHSDvw6Aqov7sCAhjbNq3bUPgPqbdvjXC5HeB2oEAPg== + dependencies: + "@types/history" "*" + "@types/react" "*" + +"@types/react@*", "@types/react@^16.9.53": + version "16.9.53" + resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.53.tgz#40cd4f8b8d6b9528aedd1fff8fcffe7a112a3d23" + integrity sha512-4nW60Sd4L7+WMXH1D6jCdVftuW7j4Za6zdp6tJ33Rqv0nk1ZAmQKML9ZLD4H0dehA3FZxXR/GM8gXplf82oNGw== + dependencies: + "@types/prop-types" "*" + csstype "^3.0.2" + +"@types/source-list-map@*": + version "0.1.2" + resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9" + integrity sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA== + +"@types/tapable@*", "@types/tapable@^1.0.5": + version "1.0.6" + resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.6.tgz#a9ca4b70a18b270ccb2bc0aaafefd1d486b7ea74" + integrity sha512-W+bw9ds02rAQaMvaLYxAbJ6cvguW/iJXNT6lTssS1ps6QdrMKttqEAMEG/b5CR8TZl3/L7/lH0ZV5nNR1LXikA== + +"@types/uglify-js@*": + version "3.11.0" + resolved "https://registry.yarnpkg.com/@types/uglify-js/-/uglify-js-3.11.0.tgz#2868d405cc45cd9dc3069179052103032c33afbc" + integrity sha512-I0Yd8TUELTbgRHq2K65j8rnDPAzAP+DiaF/syLem7yXwYLsHZhPd+AM2iXsWmf9P2F2NlFCgl5erZPQx9IbM9Q== + dependencies: + source-map "^0.6.1" + +"@types/unist@^2.0.0", "@types/unist@^2.0.2": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.3.tgz#9c088679876f374eb5983f150d4787aa6fb32d7e" + integrity sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ== + +"@types/webpack-sources@*": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@types/webpack-sources/-/webpack-sources-2.0.0.tgz#08216ab9be2be2e1499beaebc4d469cec81e82a7" + integrity sha512-a5kPx98CNFRKQ+wqawroFunvFqv7GHm/3KOI52NY9xWADgc8smu4R6prt4EU/M4QfVjvgBkMqU4fBhw3QfMVkg== + dependencies: + "@types/node" "*" + "@types/source-list-map" "*" + source-map "^0.7.3" + +"@types/webpack@^4.41.8": + version "4.41.23" + resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-4.41.23.tgz#1925f42a7325be4ae0fce38329f1cc27768fcda7" + integrity sha512-ojA4CupZg8RCzVJLugWlvqrHpT59GWhqFxbinlsnvk10MjQCWB+ot7XDACctbWhnhtdhYK7+HOH1JxkVLiZhMg== + dependencies: + "@types/anymatch" "*" + "@types/node" "*" + "@types/tapable" "*" + "@types/uglify-js" "*" + "@types/webpack-sources" "*" + source-map "^0.6.0" + +"@typescript-eslint/eslint-plugin@^4.5.0": + version "4.5.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.5.0.tgz#4ff9c1d8535ae832e239f0ef6d7210592d9b0b07" + integrity sha512-mjb/gwNcmDKNt+6mb7Aj/TjKzIJjOPcoCJpjBQC9ZnTRnBt1p4q5dJSSmIqAtsZ/Pff5N+hJlbiPc5bl6QN4OQ== + dependencies: + "@typescript-eslint/experimental-utils" "4.5.0" + "@typescript-eslint/scope-manager" "4.5.0" + debug "^4.1.1" + functional-red-black-tree "^1.0.1" + regexpp "^3.0.0" + semver "^7.3.2" + tsutils "^3.17.1" + +"@typescript-eslint/experimental-utils@4.5.0": + version "4.5.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.5.0.tgz#547fe1158609143ce60645383aa1d6f83ada28df" + integrity sha512-bW9IpSAKYvkqDGRZzayBXIgPsj2xmmVHLJ+flGSoN0fF98pGoKFhbunIol0VF2Crka7z984EEhFi623Rl7e6gg== + dependencies: + "@types/json-schema" "^7.0.3" + "@typescript-eslint/scope-manager" "4.5.0" + "@typescript-eslint/types" "4.5.0" + "@typescript-eslint/typescript-estree" "4.5.0" + eslint-scope "^5.0.0" + eslint-utils "^2.0.0" + +"@typescript-eslint/parser@4.4.1": + version "4.4.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.4.1.tgz#25fde9c080611f303f2f33cedb145d2c59915b80" + integrity sha512-S0fuX5lDku28Au9REYUsV+hdJpW/rNW0gWlc4SXzF/kdrRaAVX9YCxKpziH7djeWT/HFAjLZcnY7NJD8xTeUEg== + dependencies: + "@typescript-eslint/scope-manager" "4.4.1" + "@typescript-eslint/types" "4.4.1" + "@typescript-eslint/typescript-estree" "4.4.1" + debug "^4.1.1" + +"@typescript-eslint/parser@^4.5.0": + version "4.5.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.5.0.tgz#b2d659f25eec0041c7bc5660b91db1eefe8d7122" + integrity sha512-xb+gmyhQcnDWe+5+xxaQk5iCw6KqXd8VQxGiTeELTMoYeRjpocZYYRP1gFVM2C8Yl0SpUvLa1lhprwqZ00w3Iw== + dependencies: + "@typescript-eslint/scope-manager" "4.5.0" + "@typescript-eslint/types" "4.5.0" + "@typescript-eslint/typescript-estree" "4.5.0" + debug "^4.1.1" + +"@typescript-eslint/scope-manager@4.4.1": + version "4.4.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.4.1.tgz#d19447e60db2ce9c425898d62fa03b2cce8ea3f9" + integrity sha512-2oD/ZqD4Gj41UdFeWZxegH3cVEEH/Z6Bhr/XvwTtGv66737XkR4C9IqEkebCuqArqBJQSj4AgNHHiN1okzD/wQ== + dependencies: + "@typescript-eslint/types" "4.4.1" + "@typescript-eslint/visitor-keys" "4.4.1" + +"@typescript-eslint/scope-manager@4.5.0": + version "4.5.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.5.0.tgz#8dfd53c3256d4357e7d66c2fc8956835f4d239be" + integrity sha512-C0cEO0cTMPJ/w4RA/KVe4LFFkkSh9VHoFzKmyaaDWAnPYIEzVCtJ+Un8GZoJhcvq+mPFXEsXa01lcZDHDG6Www== + dependencies: + "@typescript-eslint/types" "4.5.0" + "@typescript-eslint/visitor-keys" "4.5.0" + +"@typescript-eslint/types@4.4.1": + version "4.4.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.4.1.tgz#c507b35cf523bc7ba00aae5f75ee9b810cdabbc1" + integrity sha512-KNDfH2bCyax5db+KKIZT4rfA8rEk5N0EJ8P0T5AJjo5xrV26UAzaiqoJCxeaibqc0c/IvZxp7v2g3difn2Pn3w== + +"@typescript-eslint/types@4.5.0": + version "4.5.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.5.0.tgz#98256e07bad1c8d15d0c9627ebec82fd971bb3c3" + integrity sha512-n2uQoXnyWNk0Les9MtF0gCK3JiWd987JQi97dMSxBOzVoLZXCNtxFckVqt1h8xuI1ix01t+iMY4h4rFMj/303g== + +"@typescript-eslint/typescript-estree@4.4.1": + version "4.4.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.4.1.tgz#598f6de488106c2587d47ca2462c60f6e2797cb8" + integrity sha512-wP/V7ScKzgSdtcY1a0pZYBoCxrCstLrgRQ2O9MmCUZDtmgxCO/TCqOTGRVwpP4/2hVfqMz/Vw1ZYrG8cVxvN3g== + dependencies: + "@typescript-eslint/types" "4.4.1" + "@typescript-eslint/visitor-keys" "4.4.1" + debug "^4.1.1" + globby "^11.0.1" + is-glob "^4.0.1" + lodash "^4.17.15" + semver "^7.3.2" + tsutils "^3.17.1" + +"@typescript-eslint/typescript-estree@4.5.0": + version "4.5.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.5.0.tgz#d50cf91ae3a89878401111031eb6fb6d03554f64" + integrity sha512-gN1mffq3zwRAjlYWzb5DanarOPdajQwx5MEWkWCk0XvqC8JpafDTeioDoow2L4CA/RkYZu7xEsGZRhqrTsAG8w== + dependencies: + "@typescript-eslint/types" "4.5.0" + "@typescript-eslint/visitor-keys" "4.5.0" + debug "^4.1.1" + globby "^11.0.1" + is-glob "^4.0.1" + lodash "^4.17.15" + semver "^7.3.2" + tsutils "^3.17.1" + +"@typescript-eslint/visitor-keys@4.4.1": + version "4.4.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.4.1.tgz#1769dc7a9e2d7d2cfd3318b77ed8249187aed5c3" + integrity sha512-H2JMWhLaJNeaylSnMSQFEhT/S/FsJbebQALmoJxMPMxLtlVAMy2uJP/Z543n9IizhjRayLSqoInehCeNW9rWcw== + dependencies: + "@typescript-eslint/types" "4.4.1" + eslint-visitor-keys "^2.0.0" + +"@typescript-eslint/visitor-keys@4.5.0": + version "4.5.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.5.0.tgz#b59f26213ac597efe87f6b13cf2aabee70542af0" + integrity sha512-UHq4FSa55NDZqscRU//O5ROFhHa9Hqn9KWTEvJGTArtTQp5GKv9Zqf6d/Q3YXXcFv4woyBml7fJQlQ+OuqRcHA== + dependencies: + "@typescript-eslint/types" "4.5.0" + eslint-visitor-keys "^2.0.0" + +"@webassemblyjs/ast@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.9.0.tgz#bd850604b4042459a5a41cd7d338cbed695ed964" + integrity sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA== + dependencies: + "@webassemblyjs/helper-module-context" "1.9.0" + "@webassemblyjs/helper-wasm-bytecode" "1.9.0" + "@webassemblyjs/wast-parser" "1.9.0" + +"@webassemblyjs/floating-point-hex-parser@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz#3c3d3b271bddfc84deb00f71344438311d52ffb4" + integrity sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA== + +"@webassemblyjs/helper-api-error@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz#203f676e333b96c9da2eeab3ccef33c45928b6a2" + integrity sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw== + +"@webassemblyjs/helper-buffer@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz#a1442d269c5feb23fcbc9ef759dac3547f29de00" + integrity sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA== + +"@webassemblyjs/helper-code-frame@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.0.tgz#647f8892cd2043a82ac0c8c5e75c36f1d9159f27" + integrity sha512-ERCYdJBkD9Vu4vtjUYe8LZruWuNIToYq/ME22igL+2vj2dQ2OOujIZr3MEFvfEaqKoVqpsFKAGsRdBSBjrIvZA== + dependencies: + "@webassemblyjs/wast-printer" "1.9.0" + +"@webassemblyjs/helper-fsm@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.0.tgz#c05256b71244214671f4b08ec108ad63b70eddb8" + integrity sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw== + +"@webassemblyjs/helper-module-context@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.0.tgz#25d8884b76839871a08a6c6f806c3979ef712f07" + integrity sha512-MJCW8iGC08tMk2enck1aPW+BE5Cw8/7ph/VGZxwyvGbJwjktKkDK7vy7gAmMDx88D7mhDTCNKAW5tED+gZ0W8g== + dependencies: + "@webassemblyjs/ast" "1.9.0" + +"@webassemblyjs/helper-wasm-bytecode@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz#4fed8beac9b8c14f8c58b70d124d549dd1fe5790" + integrity sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw== + +"@webassemblyjs/helper-wasm-section@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz#5a4138d5a6292ba18b04c5ae49717e4167965346" + integrity sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/helper-buffer" "1.9.0" + "@webassemblyjs/helper-wasm-bytecode" "1.9.0" + "@webassemblyjs/wasm-gen" "1.9.0" + +"@webassemblyjs/ieee754@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz#15c7a0fbaae83fb26143bbacf6d6df1702ad39e4" + integrity sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg== + dependencies: + "@xtuc/ieee754" "^1.2.0" + +"@webassemblyjs/leb128@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.9.0.tgz#f19ca0b76a6dc55623a09cffa769e838fa1e1c95" + integrity sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw== + dependencies: + "@xtuc/long" "4.2.2" + +"@webassemblyjs/utf8@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.9.0.tgz#04d33b636f78e6a6813227e82402f7637b6229ab" + integrity sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w== + +"@webassemblyjs/wasm-edit@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz#3fe6d79d3f0f922183aa86002c42dd256cfee9cf" + integrity sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/helper-buffer" "1.9.0" + "@webassemblyjs/helper-wasm-bytecode" "1.9.0" + "@webassemblyjs/helper-wasm-section" "1.9.0" + "@webassemblyjs/wasm-gen" "1.9.0" + "@webassemblyjs/wasm-opt" "1.9.0" + "@webassemblyjs/wasm-parser" "1.9.0" + "@webassemblyjs/wast-printer" "1.9.0" + +"@webassemblyjs/wasm-gen@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz#50bc70ec68ded8e2763b01a1418bf43491a7a49c" + integrity sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/helper-wasm-bytecode" "1.9.0" + "@webassemblyjs/ieee754" "1.9.0" + "@webassemblyjs/leb128" "1.9.0" + "@webassemblyjs/utf8" "1.9.0" + +"@webassemblyjs/wasm-opt@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz#2211181e5b31326443cc8112eb9f0b9028721a61" + integrity sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/helper-buffer" "1.9.0" + "@webassemblyjs/wasm-gen" "1.9.0" + "@webassemblyjs/wasm-parser" "1.9.0" + +"@webassemblyjs/wasm-parser@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz#9d48e44826df4a6598294aa6c87469d642fff65e" + integrity sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/helper-api-error" "1.9.0" + "@webassemblyjs/helper-wasm-bytecode" "1.9.0" + "@webassemblyjs/ieee754" "1.9.0" + "@webassemblyjs/leb128" "1.9.0" + "@webassemblyjs/utf8" "1.9.0" + +"@webassemblyjs/wast-parser@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-parser/-/wast-parser-1.9.0.tgz#3031115d79ac5bd261556cecc3fa90a3ef451914" + integrity sha512-qsqSAP3QQ3LyZjNC/0jBJ/ToSxfYJ8kYyuiGvtn/8MK89VrNEfwj7BPQzJVHi0jGTRK2dGdJ5PRqhtjzoww+bw== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/floating-point-hex-parser" "1.9.0" + "@webassemblyjs/helper-api-error" "1.9.0" + "@webassemblyjs/helper-code-frame" "1.9.0" + "@webassemblyjs/helper-fsm" "1.9.0" + "@xtuc/long" "4.2.2" + +"@webassemblyjs/wast-printer@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz#4935d54c85fef637b00ce9f52377451d00d47899" + integrity sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/wast-parser" "1.9.0" + "@xtuc/long" "4.2.2" + +"@webpack-cli/info@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@webpack-cli/info/-/info-1.1.0.tgz#c596d5bc48418b39df00c5ed7341bf0f102dbff1" + integrity sha512-uNWSdaYHc+f3LdIZNwhdhkjjLDDl3jP2+XBqAq9H8DjrJUvlOKdP8TNruy1yEaDfgpAIgbSAN7pye4FEHg9tYQ== + dependencies: + envinfo "^7.7.3" + +"@webpack-cli/serve@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-1.1.0.tgz#13ad38f89b6e53d1133bac0006a128217a6ebf92" + integrity sha512-7RfnMXCpJ/NThrhq4gYQYILB18xWyoQcBey81oIyVbmgbc6m5ZHHyFK+DyH7pLHJf0p14MxL4mTsoPAgBSTpIg== + +"@xtuc/ieee754@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" + integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== + +"@xtuc/long@4.2.2": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" + integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== + +accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7: + version "1.3.7" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" + integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA== + dependencies: + mime-types "~2.1.24" + negotiator "0.6.2" + +acorn-jsx@^5.2.0: + version "5.3.1" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.1.tgz#fc8661e11b7ac1539c47dbfea2e72b3af34d267b" + integrity sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng== + +acorn@^7.4.0: + version "7.4.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" + integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== + +acorn@^8.0.4: + version "8.0.4" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.0.4.tgz#7a3ae4191466a6984eee0fe3407a4f3aa9db8354" + integrity sha512-XNP0PqF1XD19ZlLKvB7cMmnZswW4C/03pRHgirB30uSJTaS3A3V1/P4sS3HPvFmjoriPCJQs+JDSbm4bL1TxGQ== + +add-dom-event-listener@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/add-dom-event-listener/-/add-dom-event-listener-1.1.0.tgz#6a92db3a0dd0abc254e095c0f1dc14acbbaae310" + integrity sha512-WCxx1ixHT0GQU9hb0KI/mhgRQhnU+U3GvwY6ZvVjYq8rsihIGoaIOUbY0yMPBxLH5MDtr0kz3fisWGNcbWW7Jw== + dependencies: + object-assign "4.x" + +aggregate-error@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" + integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== + dependencies: + clean-stack "^2.0.0" + indent-string "^4.0.0" + +ajv-errors@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d" + integrity sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ== + +ajv-keywords@^3.1.0, ajv-keywords@^3.5.2: + version "3.5.2" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" + integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== + +ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.4, ajv@^6.12.5: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +alphanum-sort@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" + integrity sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM= + +ansi-colors@^3.0.0: + version "3.2.4" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf" + integrity sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA== + +ansi-colors@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" + integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== + +ansi-html@0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.7.tgz#813584021962a9e9e6fd039f940d12f56ca7859e" + integrity sha1-gTWEAhliqenm/QOflA0S9WynhZ4= + +ansi-regex@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= + +ansi-regex@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" + integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== + +ansi-regex@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" + integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== + +ansi-styles@^3.2.0, ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +antd-dayjs-webpack-plugin@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/antd-dayjs-webpack-plugin/-/antd-dayjs-webpack-plugin-1.0.1.tgz#cc31cc05c9bd69652a4bddab19eb49726bebe0d5" + integrity sha512-1UWGAnv856s5r/2bi6KJvWMeevDEHwFFdHaymZElW5kbH06A/xP7JcZ75UdYoxqny3scD22DWErbGg+vQ9RNdg== + +antd@^4.7.2: + version "4.7.2" + resolved "https://registry.yarnpkg.com/antd/-/antd-4.7.2.tgz#6be4c34de141786bbc5425c743d0a3b990042641" + integrity sha512-baMyvvNRB0rqhUxi4cSaH4AG9Cd2W7TjAJnOrVTow8y5E45g3JU31+dAVUHWvtht6LTiWh4BLiKfCdZrSYBeEA== + dependencies: + "@ant-design/colors" "^4.0.5" + "@ant-design/css-animation" "^1.7.2" + "@ant-design/icons" "^4.2.1" + "@ant-design/react-slick" "~0.27.0" + "@babel/runtime" "^7.11.2" + array-tree-filter "^2.1.0" + classnames "^2.2.6" + copy-to-clipboard "^3.2.0" + lodash "^4.17.20" + moment "^2.25.3" + omit.js "^2.0.2" + raf "^3.4.1" + rc-animate "~3.1.0" + rc-cascader "~1.4.0" + rc-checkbox "~2.3.0" + rc-collapse "~2.0.0" + rc-dialog "~8.4.0" + rc-drawer "~4.1.0" + rc-dropdown "~3.2.0" + rc-field-form "~1.12.0" + rc-image "~3.2.1" + rc-input-number "~6.1.0" + rc-mentions "~1.5.0" + rc-menu "~8.8.2" + rc-motion "^2.2.0" + rc-notification "~4.5.2" + rc-pagination "~3.1.0" + rc-picker "~2.3.0" + rc-progress "~3.1.0" + rc-rate "~2.8.2" + rc-resize-observer "^0.2.3" + rc-select "~11.4.0" + rc-slider "~9.5.2" + rc-steps "~4.1.0" + rc-switch "~3.2.0" + rc-table "~7.10.0" + rc-tabs "~11.7.0" + rc-textarea "~0.3.0" + rc-tooltip "~5.0.0" + rc-tree "~3.10.0" + rc-tree-select "~4.1.1" + rc-trigger "~5.0.3" + rc-upload "~3.3.1" + rc-util "^5.1.0" + scroll-into-view-if-needed "^2.2.25" + warning "^4.0.3" + +anymatch@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" + integrity sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw== + dependencies: + micromatch "^3.1.4" + normalize-path "^2.1.1" + +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +arr-diff@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" + integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA= + +arr-flatten@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" + integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== + +arr-union@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" + integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= + +array-back@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/array-back/-/array-back-4.0.1.tgz#9b80312935a52062e1a233a9c7abeb5481b30e90" + integrity sha512-Z/JnaVEXv+A9xabHzN43FiiiWEE7gPCRXMrVmRm00tWbjZRul1iHm7ECzlyNq1p4a4ATXz+G9FJ3GqGOkOV3fg== + +array-differ@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-3.0.0.tgz#3cbb3d0f316810eafcc47624734237d6aee4ae6b" + integrity sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg== + +array-flatten@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" + integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= + +array-flatten@^2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.2.tgz#24ef80a28c1a893617e2149b0c6d0d788293b099" + integrity sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ== + +array-includes@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.1.tgz#cdd67e6852bdf9c1215460786732255ed2459348" + integrity sha512-c2VXaCHl7zPsvpkFsw4nxvFie4fh1ur9bpcgsVkIjqn0H/Xwdg+7fv3n2r/isyS8EBj5b06M9kHyZuIr4El6WQ== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0" + is-string "^1.0.5" + +array-tree-filter@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-tree-filter/-/array-tree-filter-2.1.0.tgz#873ac00fec83749f255ac8dd083814b4f6329190" + integrity sha512-4ROwICNlNw/Hqa9v+rk5h22KjmzB1JGTMVKP2AKJBOCgb0yL0ASf0+YvCcLNNwquOHNX48jkeZIJ3a+oOQqKcw== + +array-union@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" + integrity sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk= + dependencies: + array-uniq "^1.0.1" + +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + +array-uniq@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" + integrity sha1-r2rId6Jcx/dOBYiUdThY39sk/bY= + +array-unique@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" + integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= + +array.prototype.flat@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.3.tgz#0de82b426b0318dbfdb940089e38b043d37f6c7b" + integrity sha512-gBlRZV0VSmfPIeWfuuy56XZMvbVfbEUnOXUvt3F/eUUUSyzlgLxhEX4YAEpxNAogRGehPSnfXyPtYyKAhkzQhQ== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + +array.prototype.flatmap@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.2.3.tgz#1c13f84a178566042dd63de4414440db9222e443" + integrity sha512-OOEk+lkePcg+ODXIpvuU9PAryCikCJyo7GlDG1upleEpQRx6mzL9puEBkozQ5iAx20KV0l3DbyQwqciJtqe5Pg== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + function-bind "^1.1.1" + +arrify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" + integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0= + +arrify@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa" + integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug== + +assign-symbols@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" + integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= + +astral-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" + integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== + +astral-regex@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" + integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== + +async-each@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" + integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== + +async-limiter@~1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" + integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== + +async-validator@^3.0.3: + version "3.4.0" + resolved "https://registry.yarnpkg.com/async-validator/-/async-validator-3.4.0.tgz#871b3e594124bf4c4eb7bcd1a9e78b44f3b09cae" + integrity sha512-VrFk4eYiJAWKskEz115iiuCf9O0ftnMMPXrOFMqyzGH2KxO7YwncKyn/FgOOP+0MDHMfXL7gLExagCutaZGigA== + +async@^2.6.2: + version "2.6.3" + resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" + integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg== + dependencies: + lodash "^4.17.14" + +at-least-node@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" + integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== + +atob@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" + integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== + +autoprefixer@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.0.1.tgz#e2d9000f84ebd98d77b7bc16f8adb2ff1f7bb946" + integrity sha512-aQo2BDIsoOdemXUAOBpFv4ZQa2DrOtEufarYhtFsK1088Ca0TUwu/aQWf0M3mrILXZ3mTIVn1lR3hPW8acacsw== + dependencies: + browserslist "^4.14.5" + caniuse-lite "^1.0.30001137" + colorette "^1.2.1" + normalize-range "^0.1.2" + num2fraction "^1.2.2" + postcss-value-parser "^4.1.0" + +autoprefixer@^9.6.1, autoprefixer@^9.8.6: + version "9.8.6" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.8.6.tgz#3b73594ca1bf9266320c5acf1588d74dea74210f" + integrity sha512-XrvP4VVHdRBCdX1S3WXVD8+RyG9qeb1D5Sn1DeLiG2xfSpzellk5k54xbUERJ3M5DggQxes39UGOTP8CFrEGbg== + dependencies: + browserslist "^4.12.0" + caniuse-lite "^1.0.30001109" + colorette "^1.2.1" + normalize-range "^0.1.2" + num2fraction "^1.2.2" + postcss "^7.0.32" + postcss-value-parser "^4.1.0" + +bail@^1.0.0: + version "1.0.5" + resolved "https://registry.yarnpkg.com/bail/-/bail-1.0.5.tgz#b6fa133404a392cbc1f8c4bf63f5953351e7a776" + integrity sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ== + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + +base@^0.11.1: + version "0.11.2" + resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" + integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg== + dependencies: + cache-base "^1.0.1" + class-utils "^0.3.5" + component-emitter "^1.2.1" + define-property "^1.0.0" + isobject "^3.0.1" + mixin-deep "^1.2.0" + pascalcase "^0.1.1" + +batch@0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16" + integrity sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY= + +big.js@^5.2.2: + version "5.2.2" + resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" + integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== + +binary-extensions@^1.0.0: + version "1.13.1" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" + integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw== + +bindings@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" + integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== + dependencies: + file-uri-to-path "1.0.0" + +body-parser@1.19.0: + version "1.19.0" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" + integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw== + dependencies: + bytes "3.1.0" + content-type "~1.0.4" + debug "2.6.9" + depd "~1.1.2" + http-errors "1.7.2" + iconv-lite "0.4.24" + on-finished "~2.3.0" + qs "6.7.0" + raw-body "2.4.0" + type-is "~1.6.17" + +bonjour@^3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/bonjour/-/bonjour-3.5.0.tgz#8e890a183d8ee9a2393b3844c691a42bcf7bc9f5" + integrity sha1-jokKGD2O6aI5OzhExpGkK897yfU= + dependencies: + array-flatten "^2.1.0" + deep-equal "^1.0.1" + dns-equal "^1.0.0" + dns-txt "^2.0.2" + multicast-dns "^6.0.1" + multicast-dns-service-types "^1.1.0" + +boolbase@^1.0.0, boolbase@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" + integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@^2.3.1, braces@^2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" + integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== + dependencies: + arr-flatten "^1.1.0" + array-unique "^0.3.2" + extend-shallow "^2.0.1" + fill-range "^4.0.0" + isobject "^3.0.1" + repeat-element "^1.1.2" + snapdragon "^0.8.1" + snapdragon-node "^2.0.1" + split-string "^3.0.2" + to-regex "^3.0.1" + +braces@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.14.5, browserslist@^4.6.4: + version "4.14.5" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.14.5.tgz#1c751461a102ddc60e40993639b709be7f2c4015" + integrity sha512-Z+vsCZIvCBvqLoYkBFTwEYH3v5MCQbsAjp50ERycpOjnPmolg1Gjy4+KaWWpm8QOJt9GHkhdqAl14NpCX73CWA== + dependencies: + caniuse-lite "^1.0.30001135" + electron-to-chromium "^1.3.571" + escalade "^3.1.0" + node-releases "^1.1.61" + +buffer-from@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" + integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== + +buffer-indexof@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/buffer-indexof/-/buffer-indexof-1.1.1.tgz#52fabcc6a606d1a00302802648ef68f639da268c" + integrity sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g== + +bytes@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" + integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg= + +bytes@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" + integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== + +cacache@^15.0.5: + version "15.0.5" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-15.0.5.tgz#69162833da29170d6732334643c60e005f5f17d0" + integrity sha512-lloiL22n7sOjEEXdL8NAjTgv9a1u43xICE9/203qonkZUCj5X1UEWIdf2/Y0d6QcCtMzbKQyhrcDbdvlZTs/+A== + dependencies: + "@npmcli/move-file" "^1.0.1" + chownr "^2.0.0" + fs-minipass "^2.0.0" + glob "^7.1.4" + infer-owner "^1.0.4" + lru-cache "^6.0.0" + minipass "^3.1.1" + minipass-collect "^1.0.2" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.2" + mkdirp "^1.0.3" + p-map "^4.0.0" + promise-inflight "^1.0.1" + rimraf "^3.0.2" + ssri "^8.0.0" + tar "^6.0.2" + unique-filename "^1.1.1" + +cache-base@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" + integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ== + dependencies: + collection-visit "^1.0.0" + component-emitter "^1.2.1" + get-value "^2.0.6" + has-value "^1.0.0" + isobject "^3.0.1" + set-value "^2.0.0" + to-object-path "^0.3.0" + union-value "^1.0.0" + unset-value "^1.0.0" + +call-bind@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.0.tgz#24127054bb3f9bdcb4b1fb82418186072f77b8ce" + integrity sha512-AEXsYIyyDY3MCzbwdhzG3Jx1R0J2wetQyUynn6dYHAO+bg8l1k7jwZtRv4ryryFs7EP+NDlikJlVe59jr0cM2w== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.0" + +caller-callsite@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/caller-callsite/-/caller-callsite-2.0.0.tgz#847e0fce0a223750a9a027c54b33731ad3154134" + integrity sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ= + dependencies: + callsites "^2.0.0" + +caller-path@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-2.0.0.tgz#468f83044e369ab2010fac5f06ceee15bb2cb1f4" + integrity sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ= + dependencies: + caller-callsite "^2.0.0" + +callsites@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50" + integrity sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA= + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +camel-case@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-4.1.1.tgz#1fc41c854f00e2f7d0139dfeba1542d6896fe547" + integrity sha512-7fa2WcG4fYFkclIvEmxBbTvmibwF2/agfEBc6q3lOpVu0A13ltLsA+Hr/8Hp6kp5f+G7hKi6t8lys6XxP+1K6Q== + dependencies: + pascal-case "^3.1.1" + tslib "^1.10.0" + +camelcase-css@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5" + integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== + +camelcase-keys@^6.2.2: + version "6.2.2" + resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-6.2.2.tgz#5e755d6ba51aa223ec7d3d52f25778210f9dc3c0" + integrity sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg== + dependencies: + camelcase "^5.3.1" + map-obj "^4.0.0" + quick-lru "^4.0.1" + +camelcase@^5.0.0, camelcase@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +camelcase@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.1.0.tgz#27dc176173725fb0adf8a48b647f4d7871944d78" + integrity sha512-WCMml9ivU60+8rEJgELlFp1gxFcEGxwYleE3bziHEDeqsqAWGHdimB7beBFGjLzVNgPGyDsfgXLQEYMpmIFnVQ== + +caniuse-api@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0" + integrity sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw== + dependencies: + browserslist "^4.0.0" + caniuse-lite "^1.0.0" + lodash.memoize "^4.1.2" + lodash.uniq "^4.5.0" + +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001135, caniuse-lite@^1.0.30001137: + version "1.0.30001150" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001150.tgz#6d0d829da654b0b233576de00335586bc2004df1" + integrity sha512-kiNKvihW0m36UhAFnl7bOAv0i1K1f6wpfVtTF5O5O82XzgtBnb05V0XeV3oZ968vfg2sRNChsHw8ASH2hDfoYQ== + +ccount@^1.0.0: + version "1.0.5" + resolved "https://registry.yarnpkg.com/ccount/-/ccount-1.0.5.tgz#ac82a944905a65ce204eb03023157edf29425c17" + integrity sha512-MOli1W+nfbPLlKEhInaxhRdp7KVLFxLN5ykwzHgLsLI3H3gs5jjFAK4Eoj3OzzcxCtumDaI8onoVDeQyWaNTkw== + +chalk@^2.0.0, chalk@^2.3.0, chalk@^2.4.1, chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^4.0.0, chalk@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" + integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +character-entities-html4@^1.0.0: + version "1.1.4" + resolved "https://registry.yarnpkg.com/character-entities-html4/-/character-entities-html4-1.1.4.tgz#0e64b0a3753ddbf1fdc044c5fd01d0199a02e125" + integrity sha512-HRcDxZuZqMx3/a+qrzxdBKBPUpxWEq9xw2OPZ3a/174ihfrQKVsFhqtthBInFy1zZ9GgZyFXOatNujm8M+El3g== + +character-entities-legacy@^1.0.0: + version "1.1.4" + resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz#94bc1845dce70a5bb9d2ecc748725661293d8fc1" + integrity sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA== + +character-entities@^1.0.0: + version "1.2.4" + resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-1.2.4.tgz#e12c3939b7eaf4e5b15e7ad4c5e28e1d48c5b16b" + integrity sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw== + +character-reference-invalid@^1.0.0: + version "1.1.4" + resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz#083329cda0eae272ab3dbbf37e9a382c13af1560" + integrity sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg== + +chokidar@^2.1.8: + version "2.1.8" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" + integrity sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg== + dependencies: + anymatch "^2.0.0" + async-each "^1.0.1" + braces "^2.3.2" + glob-parent "^3.1.0" + inherits "^2.0.3" + is-binary-path "^1.0.0" + is-glob "^4.0.0" + normalize-path "^3.0.0" + path-is-absolute "^1.0.0" + readdirp "^2.2.1" + upath "^1.1.1" + optionalDependencies: + fsevents "^1.2.7" + +chownr@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" + integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== + +chrome-trace-event@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz#234090ee97c7d4ad1a2c4beae27505deffc608a4" + integrity sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ== + dependencies: + tslib "^1.9.0" + +ci-info@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" + integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== + +class-utils@^0.3.5: + version "0.3.6" + resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" + integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg== + dependencies: + arr-union "^3.1.0" + define-property "^0.2.5" + isobject "^3.0.0" + static-extend "^0.1.1" + +classnames@2.x, classnames@^2.2.1, classnames@^2.2.3, classnames@^2.2.5, classnames@^2.2.6: + version "2.2.6" + resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce" + integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q== + +clean-css@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.2.3.tgz#507b5de7d97b48ee53d84adb0160ff6216380f78" + integrity sha512-VcMWDN54ZN/DS+g58HYL5/n4Zrqe8vHJpGA8KdgUXFU4fuP/aHNw8eld9SyEIyabIMJX/0RaY/fplOo5hYLSFA== + dependencies: + source-map "~0.6.0" + +clean-stack@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" + integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== + +cliui@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" + integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA== + dependencies: + string-width "^3.1.0" + strip-ansi "^5.2.0" + wrap-ansi "^5.1.0" + +clone-deep@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" + integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== + dependencies: + is-plain-object "^2.0.4" + kind-of "^6.0.2" + shallow-clone "^3.0.0" + +clone-regexp@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/clone-regexp/-/clone-regexp-2.2.0.tgz#7d65e00885cd8796405c35a737e7a86b7429e36f" + integrity sha512-beMpP7BOtTipFuW8hrJvREQ2DrRu3BE7by0ZpibtfBA+qfHYvMGTc2Yb1JMYPKg/JUw0CHYvpg796aNTSW9z7Q== + dependencies: + is-regexp "^2.0.0" + +clone@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" + integrity sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18= + +coa@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/coa/-/coa-2.0.2.tgz#43f6c21151b4ef2bf57187db0d73de229e3e7ec3" + integrity sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA== + dependencies: + "@types/q" "^1.5.1" + chalk "^2.4.1" + q "^1.1.2" + +code-block-writer@^10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/code-block-writer/-/code-block-writer-10.1.0.tgz#54fc410ebef2af836d9c2314ac40af7d7b37eee9" + integrity sha512-RG9hpXtWFeUWhuUav1YuP/vGcyncW+t90yJLk9fNZs1De2OuHTHKAKThVCokt29PYq5RoJ0QSZaIZ+rvPO23hA== + +collapse-white-space@^1.0.2: + version "1.0.6" + resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-1.0.6.tgz#e63629c0016665792060dbbeb79c42239d2c5287" + integrity sha512-jEovNnrhMuqyCcjfEJA56v0Xq8SkIoPKDyaHahwo3POf4qcSXqMYuwNcOTzp74vTsR9Tn08z4MxWqAhcekogkQ== + +collection-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" + integrity sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA= + dependencies: + map-visit "^1.0.0" + object-visit "^1.0.0" + +color-convert@^1.9.0, color-convert@^1.9.1: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + +color-name@^1.0.0, color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +color-string@^1.5.4: + version "1.5.4" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.4.tgz#dd51cd25cfee953d138fe4002372cc3d0e504cb6" + integrity sha512-57yF5yt8Xa3czSEW1jfQDE79Idk0+AkN/4KWad6tbdxUmAs3MvjxlWSWD4deYytcRfoZ9nhKyFl1kj5tBvidbw== + dependencies: + color-name "^1.0.0" + simple-swizzle "^0.2.2" + +color@^3.0.0: + version "3.1.3" + resolved "https://registry.yarnpkg.com/color/-/color-3.1.3.tgz#ca67fb4e7b97d611dcde39eceed422067d91596e" + integrity sha512-xgXAcTHa2HeFCGLE9Xs/R82hujGtu9Jd9x4NW3T34+OMs7VoPsjwzRczKHvTAHeJwWFwX5j15+MgAppE8ztObQ== + dependencies: + color-convert "^1.9.1" + color-string "^1.5.4" + +colorette@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.1.tgz#4d0b921325c14faf92633086a536db6e89564b1b" + integrity sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw== + +command-line-usage@^6.1.0: + version "6.1.1" + resolved "https://registry.yarnpkg.com/command-line-usage/-/command-line-usage-6.1.1.tgz#c908e28686108917758a49f45efb4f02f76bc03f" + integrity sha512-F59pEuAR9o1SF/bD0dQBDluhpT4jJQNWUHEuVBqpDmCUo6gPjCi+m9fCWnWZVR/oG6cMTUms4h+3NPl74wGXvA== + dependencies: + array-back "^4.0.1" + chalk "^2.4.2" + table-layout "^1.0.1" + typical "^5.2.0" + +commander@^2.20.0: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + +commander@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" + integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== + +commander@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.0.tgz#b990bfb8ac030aedc6d11bc04d1488ffef56db75" + integrity sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q== + +commondir@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" + integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= + +compare-versions@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.6.0.tgz#1a5689913685e5a87637b8d3ffca75514ec41d62" + integrity sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA== + +component-emitter@^1.2.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" + integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== + +compressible@~2.0.16: + version "2.0.18" + resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" + integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg== + dependencies: + mime-db ">= 1.43.0 < 2" + +compression@^1.7.4: + version "1.7.4" + resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f" + integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ== + dependencies: + accepts "~1.3.5" + bytes "3.0.0" + compressible "~2.0.16" + debug "2.6.9" + on-headers "~1.0.2" + safe-buffer "5.1.2" + vary "~1.1.2" + +compute-scroll-into-view@^1.0.16: + version "1.0.16" + resolved "https://registry.yarnpkg.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.16.tgz#5b7bf4f7127ea2c19b750353d7ce6776a90ee088" + integrity sha512-a85LHKY81oQnikatZYA90pufpZ6sQx++BoCxOEMsjpZx+ZnaKGQnCyCehTRr/1p9GBIAHTjcU9k71kSYWloLiQ== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +confusing-browser-globals@^1.0.9: + version "1.0.9" + resolved "https://registry.yarnpkg.com/confusing-browser-globals/-/confusing-browser-globals-1.0.9.tgz#72bc13b483c0276801681871d4898516f8f54fdd" + integrity sha512-KbS1Y0jMtyPgIxjO7ZzMAuUpAKMt1SzCL9fsrKsX6b0zJPTaT0SiSPmewwVZg9UAO83HVIlEhZF84LIjZ0lmAw== + +connect-history-api-fallback@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz#8b32089359308d111115d81cad3fceab888f97bc" + integrity sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg== + +contains-path@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a" + integrity sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo= + +content-disposition@0.5.3: + version "0.5.3" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" + integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g== + dependencies: + safe-buffer "5.1.2" + +content-type@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" + integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== + +convert-source-map@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442" + integrity sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA== + dependencies: + safe-buffer "~5.1.1" + +cookie-signature@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" + integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= + +cookie@0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" + integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== + +copy-descriptor@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" + integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= + +copy-to-clipboard@^3.2.0: + version "3.3.1" + resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.3.1.tgz#115aa1a9998ffab6196f93076ad6da3b913662ae" + integrity sha512-i13qo6kIHTTpCm8/Wup+0b1mVWETvu2kIMzKoK8FpkLkFxlt0znUAHcMzox+T8sPlqtZXq3CulEjQHsYiGFJUw== + dependencies: + toggle-selection "^1.0.6" + +copy-webpack-plugin@^6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-6.2.1.tgz#8015e4d5c5e637ab7b39c76daa9e03c7a4bf1ae5" + integrity sha512-VH2ZTMIBsx4p++Lmpg77adZ0KUyM5gFR/9cuTrbneNnJlcQXUFvsNariPqq2dq2kV3F2skHiDGPQCyKWy1+U0Q== + dependencies: + cacache "^15.0.5" + fast-glob "^3.2.4" + find-cache-dir "^3.3.1" + glob-parent "^5.1.1" + globby "^11.0.1" + loader-utils "^2.0.0" + normalize-path "^3.0.0" + p-limit "^3.0.2" + schema-utils "^3.0.0" + serialize-javascript "^5.0.1" + webpack-sources "^1.4.3" + +core-util-is@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= + +cosmiconfig@^5.0.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.2.1.tgz#040f726809c591e77a17c0a3626ca45b4f168b1a" + integrity sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA== + dependencies: + import-fresh "^2.0.0" + is-directory "^0.3.1" + js-yaml "^3.13.1" + parse-json "^4.0.0" + +cosmiconfig@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.0.tgz#ef9b44d773959cae63ddecd122de23853b60f8d3" + integrity sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA== + dependencies: + "@types/parse-json" "^4.0.0" + import-fresh "^3.2.1" + parse-json "^5.0.0" + path-type "^4.0.0" + yaml "^1.10.0" + +cross-spawn@^6.0.0: + version "6.0.5" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" + integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== + dependencies: + nice-try "^1.0.4" + path-key "^2.0.1" + semver "^5.5.0" + shebang-command "^1.2.0" + which "^1.2.9" + +cross-spawn@^7.0.0, cross-spawn@^7.0.2: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +css-blank-pseudo@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/css-blank-pseudo/-/css-blank-pseudo-0.1.4.tgz#dfdefd3254bf8a82027993674ccf35483bfcb3c5" + integrity sha512-LHz35Hr83dnFeipc7oqFDmsjHdljj3TQtxGGiNWSOsTLIAubSm4TEz8qCaKFpk7idaQ1GfWscF4E6mgpBysA1w== + dependencies: + postcss "^7.0.5" + +css-color-names@0.0.4, css-color-names@^0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0" + integrity sha1-gIrcLnnPhHOAabZGyyDsJ762KeA= + +css-declaration-sorter@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-4.0.1.tgz#c198940f63a76d7e36c1e71018b001721054cb22" + integrity sha512-BcxQSKTSEEQUftYpBVnsH4SF05NTuBokb19/sBt6asXGKZ/6VP7PLG1CBCkFDYOnhXhPh0jMhO6xZ71oYHXHBA== + dependencies: + postcss "^7.0.1" + timsort "^0.3.0" + +css-has-pseudo@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/css-has-pseudo/-/css-has-pseudo-0.10.0.tgz#3c642ab34ca242c59c41a125df9105841f6966ee" + integrity sha512-Z8hnfsZu4o/kt+AuFzeGpLVhFOGO9mluyHBaA2bA8aCGTwah5sT3WV/fTHH8UNZUytOIImuGPrl/prlb4oX4qQ== + dependencies: + postcss "^7.0.6" + postcss-selector-parser "^5.0.0-rc.4" + +css-loader@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-5.0.0.tgz#f0a48dfacc3ab9936a05ee16a09e7f313872e117" + integrity sha512-9g35eXRBgjvswyJWoqq/seWp+BOxvUl8IinVNTsUBFFxtwfEYvlmEn6ciyn0liXGbGh5HyJjPGCuobDSfqMIVg== + dependencies: + camelcase "^6.1.0" + cssesc "^3.0.0" + icss-utils "^5.0.0" + loader-utils "^2.0.0" + postcss "^8.1.1" + postcss-modules-extract-imports "^3.0.0" + postcss-modules-local-by-default "^4.0.0" + postcss-modules-scope "^3.0.0" + postcss-modules-values "^4.0.0" + postcss-value-parser "^4.1.0" + schema-utils "^3.0.0" + semver "^7.3.2" + +css-prefers-color-scheme@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/css-prefers-color-scheme/-/css-prefers-color-scheme-3.1.1.tgz#6f830a2714199d4f0d0d0bb8a27916ed65cff1f4" + integrity sha512-MTu6+tMs9S3EUqzmqLXEcgNRbNkkD/TGFvowpeoWJn5Vfq7FMgsmRQs9X5NXAURiOBmOxm/lLjsDNXDE6k9bhg== + dependencies: + postcss "^7.0.5" + +css-select-base-adapter@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz#3b2ff4972cc362ab88561507a95408a1432135d7" + integrity sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w== + +css-select@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858" + integrity sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg= + dependencies: + boolbase "~1.0.0" + css-what "2.1" + domutils "1.5.1" + nth-check "~1.0.1" + +css-select@^2.0.0, css-select@^2.0.2: + version "2.1.0" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-2.1.0.tgz#6a34653356635934a81baca68d0255432105dbef" + integrity sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ== + dependencies: + boolbase "^1.0.0" + css-what "^3.2.1" + domutils "^1.7.0" + nth-check "^1.0.2" + +css-tree@1.0.0-alpha.37: + version "1.0.0-alpha.37" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.37.tgz#98bebd62c4c1d9f960ec340cf9f7522e30709a22" + integrity sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg== + dependencies: + mdn-data "2.0.4" + source-map "^0.6.1" + +css-tree@1.0.0-alpha.39: + version "1.0.0-alpha.39" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.39.tgz#2bff3ffe1bb3f776cf7eefd91ee5cba77a149eeb" + integrity sha512-7UvkEYgBAHRG9Nt980lYxjsTrCyHFN53ky3wVsDkiMdVqylqRt+Zc+jm5qw7/qyOvN2dHSYtX0e4MbCCExSvnA== + dependencies: + mdn-data "2.0.6" + source-map "^0.6.1" + +css-what@2.1: + version "2.1.3" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2" + integrity sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg== + +css-what@^3.2.1: + version "3.4.2" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-3.4.2.tgz#ea7026fcb01777edbde52124e21f327e7ae950e4" + integrity sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ== + +cssdb@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/cssdb/-/cssdb-4.4.0.tgz#3bf2f2a68c10f5c6a08abd92378331ee803cddb0" + integrity sha512-LsTAR1JPEM9TpGhl/0p3nQecC2LJ0kD8X5YARu1hk/9I1gril5vDtMZyNxcEpxxDj34YNck/ucjuoUd66K03oQ== + +cssesc@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-2.0.0.tgz#3b13bd1bb1cb36e1bcb5a4dcd27f54c5dcb35703" + integrity sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg== + +cssesc@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" + integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== + +cssnano-preset-default@^4.0.7: + version "4.0.7" + resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-4.0.7.tgz#51ec662ccfca0f88b396dcd9679cdb931be17f76" + integrity sha512-x0YHHx2h6p0fCl1zY9L9roD7rnlltugGu7zXSKQx6k2rYw0Hi3IqxcoAGF7u9Q5w1nt7vK0ulxV8Lo+EvllGsA== + dependencies: + css-declaration-sorter "^4.0.1" + cssnano-util-raw-cache "^4.0.1" + postcss "^7.0.0" + postcss-calc "^7.0.1" + postcss-colormin "^4.0.3" + postcss-convert-values "^4.0.1" + postcss-discard-comments "^4.0.2" + postcss-discard-duplicates "^4.0.2" + postcss-discard-empty "^4.0.1" + postcss-discard-overridden "^4.0.1" + postcss-merge-longhand "^4.0.11" + postcss-merge-rules "^4.0.3" + postcss-minify-font-values "^4.0.2" + postcss-minify-gradients "^4.0.2" + postcss-minify-params "^4.0.2" + postcss-minify-selectors "^4.0.2" + postcss-normalize-charset "^4.0.1" + postcss-normalize-display-values "^4.0.2" + postcss-normalize-positions "^4.0.2" + postcss-normalize-repeat-style "^4.0.2" + postcss-normalize-string "^4.0.2" + postcss-normalize-timing-functions "^4.0.2" + postcss-normalize-unicode "^4.0.1" + postcss-normalize-url "^4.0.1" + postcss-normalize-whitespace "^4.0.2" + postcss-ordered-values "^4.1.2" + postcss-reduce-initial "^4.0.3" + postcss-reduce-transforms "^4.0.2" + postcss-svgo "^4.0.2" + postcss-unique-selectors "^4.0.1" + +cssnano-util-get-arguments@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/cssnano-util-get-arguments/-/cssnano-util-get-arguments-4.0.0.tgz#ed3a08299f21d75741b20f3b81f194ed49cc150f" + integrity sha1-7ToIKZ8h11dBsg87gfGU7UnMFQ8= + +cssnano-util-get-match@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/cssnano-util-get-match/-/cssnano-util-get-match-4.0.0.tgz#c0e4ca07f5386bb17ec5e52250b4f5961365156d" + integrity sha1-wOTKB/U4a7F+xeUiULT1lhNlFW0= + +cssnano-util-raw-cache@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/cssnano-util-raw-cache/-/cssnano-util-raw-cache-4.0.1.tgz#b26d5fd5f72a11dfe7a7846fb4c67260f96bf282" + integrity sha512-qLuYtWK2b2Dy55I8ZX3ky1Z16WYsx544Q0UWViebptpwn/xDBmog2TLg4f+DBMg1rJ6JDWtn96WHbOKDWt1WQA== + dependencies: + postcss "^7.0.0" + +cssnano-util-same-parent@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz#574082fb2859d2db433855835d9a8456ea18bbf3" + integrity sha512-WcKx5OY+KoSIAxBW6UBBRay1U6vkYheCdjyVNDm85zt5K9mHoGOfsOsqIszfAqrQQFIIKgjh2+FDgIj/zsl21Q== + +cssnano@^4.1.10: + version "4.1.10" + resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-4.1.10.tgz#0ac41f0b13d13d465487e111b778d42da631b8b2" + integrity sha512-5wny+F6H4/8RgNlaqab4ktc3e0/blKutmq8yNlBFXA//nSFFAqAngjNVRzUvCgYROULmZZUoosL/KSoZo5aUaQ== + dependencies: + cosmiconfig "^5.0.0" + cssnano-preset-default "^4.0.7" + is-resolvable "^1.0.0" + postcss "^7.0.0" + +csso@^4.0.2: + version "4.0.3" + resolved "https://registry.yarnpkg.com/csso/-/csso-4.0.3.tgz#0d9985dc852c7cc2b2cacfbbe1079014d1a8e903" + integrity sha512-NL3spysxUkcrOgnpsT4Xdl2aiEiBG6bXswAABQVHcMrfjjBisFOKwLDOmf4wf32aPdcJws1zds2B0Rg+jqMyHQ== + dependencies: + css-tree "1.0.0-alpha.39" + +csstype@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.3.tgz#2b410bbeba38ba9633353aff34b05d9755d065f8" + integrity sha512-jPl+wbWPOWJ7SXsWyqGRk3lGecbar0Cb0OvZF/r/ZU011R4YqiRehgkQ9p4eQfo9DSDLqLL3wHwfxeJiuIsNag== + +date-fns@^2.15.0: + version "2.16.1" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.16.1.tgz#05775792c3f3331da812af253e1a935851d3834b" + integrity sha512-sAJVKx/FqrLYHAQeN7VpJrPhagZc9R4ImZIWYRFZaaohR3KzmuK88touwsSwSVT8Qcbd4zoDsnGfX4GFB4imyQ== + +dayjs@^1.8.30, dayjs@^1.9.3: + version "1.9.3" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.9.3.tgz#b7f94b22ad2a136a4ca02a01ab68ae893fe1a268" + integrity sha512-V+1SyIvkS+HmNbN1G7A9+ERbFTV9KTXu6Oor98v2xHmzzpp52OIJhQuJSTywWuBY5pyAEmlwbCi1Me87n/SLOw== + +debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@^3.1.1, debug@^3.2.5: + version "3.2.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== + dependencies: + ms "^2.1.1" + +debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: + version "4.2.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.2.0.tgz#7f150f93920e94c58f5574c2fd01a3110effe7f1" + integrity sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg== + dependencies: + ms "2.1.2" + +decamelize-keys@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.0.tgz#d171a87933252807eb3cb61dc1c1445d078df2d9" + integrity sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk= + dependencies: + decamelize "^1.1.0" + map-obj "^1.0.0" + +decamelize@^1.1.0, decamelize@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= + +decode-uri-component@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" + integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= + +deep-equal@^1.0.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.1.tgz#b5c98c942ceffaf7cb051e24e1434a25a2e6076a" + integrity sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g== + dependencies: + is-arguments "^1.0.4" + is-date-object "^1.0.1" + is-regex "^1.0.4" + object-is "^1.0.1" + object-keys "^1.1.1" + regexp.prototype.flags "^1.2.0" + +deep-extend@~0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== + +deep-is@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" + integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= + +deepmerge@^2.1.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-2.2.1.tgz#5d3ff22a01c00f645405a2fbc17d0778a1801170" + integrity sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA== + +default-gateway@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-4.2.0.tgz#167104c7500c2115f6dd69b0a536bb8ed720552b" + integrity sha512-h6sMrVB1VMWVrW13mSc6ia/DwYYw5MN6+exNu1OaJeFac5aSAvwM7lZ0NVfTABuSkQelr4h5oebg3KB1XPdjgA== + dependencies: + execa "^1.0.0" + ip-regex "^2.1.0" + +define-properties@^1.1.2, define-properties@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" + integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== + dependencies: + object-keys "^1.0.12" + +define-property@^0.2.5: + version "0.2.5" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" + integrity sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY= + dependencies: + is-descriptor "^0.1.0" + +define-property@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" + integrity sha1-dp66rz9KY6rTr56NMEybvnm/sOY= + dependencies: + is-descriptor "^1.0.0" + +define-property@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" + integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ== + dependencies: + is-descriptor "^1.0.2" + isobject "^3.0.1" + +del@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/del/-/del-4.1.1.tgz#9e8f117222ea44a31ff3a156c049b99052a9f0b4" + integrity sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ== + dependencies: + "@types/glob" "^7.1.1" + globby "^6.1.0" + is-path-cwd "^2.0.0" + is-path-in-cwd "^2.0.0" + p-map "^2.0.0" + pify "^4.0.1" + rimraf "^2.6.3" + +depd@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" + integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= + +destroy@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" + integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= + +detect-node@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c" + integrity sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw== + +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + +dns-equal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d" + integrity sha1-s55/HabrCnW6nBcySzR1PEfgZU0= + +dns-packet@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-1.3.1.tgz#12aa426981075be500b910eedcd0b47dd7deda5a" + integrity sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg== + dependencies: + ip "^1.1.0" + safe-buffer "^5.0.1" + +dns-txt@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/dns-txt/-/dns-txt-2.0.2.tgz#b91d806f5d27188e4ab3e7d107d881a1cc4642b6" + integrity sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY= + dependencies: + buffer-indexof "^1.0.0" + +doctrine@1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa" + integrity sha1-N53Ocw9hZvds76TmcHoVmwLFpvo= + dependencies: + esutils "^2.0.2" + isarray "^1.0.0" + +doctrine@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" + integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== + dependencies: + esutils "^2.0.2" + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +dom-align@^1.7.0: + version "1.12.0" + resolved "https://registry.yarnpkg.com/dom-align/-/dom-align-1.12.0.tgz#56fb7156df0b91099830364d2d48f88963f5a29c" + integrity sha512-YkoezQuhp3SLFGdOlr5xkqZ640iXrnHAwVYcDg8ZKRUtO7mSzSC2BA5V0VuyAwPSJA4CLIc6EDDJh4bEsD2+zA== + +dom-converter@^0.2: + version "0.2.0" + resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768" + integrity sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA== + dependencies: + utila "~0.4" + +dom-serializer@0: + version "0.2.2" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51" + integrity sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g== + dependencies: + domelementtype "^2.0.1" + entities "^2.0.0" + +dom-serializer@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.1.tgz#1ec4059e284babed36eec2941d4a970a189ce7c0" + integrity sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA== + dependencies: + domelementtype "^1.3.0" + entities "^1.1.1" + +domelementtype@1, domelementtype@^1.3.0, domelementtype@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" + integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== + +domelementtype@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.0.2.tgz#f3b6e549201e46f588b59463dd77187131fe6971" + integrity sha512-wFwTwCVebUrMgGeAwRL/NhZtHAUyT9n9yg4IMDwf10+6iCMxSkVq9MGCVEH+QZWo1nNidy8kNvwmv4zWHDTqvA== + +domhandler@^2.3.0: + version "2.4.2" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803" + integrity sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA== + dependencies: + domelementtype "1" + +domutils@1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf" + integrity sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8= + dependencies: + dom-serializer "0" + domelementtype "1" + +domutils@^1.5.1, domutils@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a" + integrity sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg== + dependencies: + dom-serializer "0" + domelementtype "1" + +dot-case@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.3.tgz#21d3b52efaaba2ea5fda875bb1aa8124521cf4aa" + integrity sha512-7hwEmg6RiSQfm/GwPL4AAWXKy3YNNZA3oFv2Pdiey0mwkRCPZ9x6SZbkLcn8Ma5PYeVokzoD4Twv2n7LKp5WeA== + dependencies: + no-case "^3.0.3" + tslib "^1.10.0" + +dot-prop@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88" + integrity sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q== + dependencies: + is-obj "^2.0.0" + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= + +electron-to-chromium@^1.3.571: + version "1.3.582" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.582.tgz#1adfac5affce84d85b3d7b3dfbc4ade293a6ffc4" + integrity sha512-0nCJ7cSqnkMC+kUuPs0YgklFHraWGl/xHqtZWWtOeVtyi+YqkoAOMGuZQad43DscXCQI/yizcTa3u6B5r+BLww== + +emoji-regex@^7.0.1: + version "7.0.3" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" + integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +emojis-list@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" + integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== + +encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= + +end-of-stream@^1.1.0: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + +enhanced-resolve@^4.0.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.3.0.tgz#3b806f3bfafc1ec7de69551ef93cca46c1704126" + integrity sha512-3e87LvavsdxyoCfGusJnrZ5G8SLPOFeHSNpZI/ATL9a5leXo2k0w6MKnbqhdBad9qTobSfB20Ld7UmgoNbAZkQ== + dependencies: + graceful-fs "^4.1.2" + memory-fs "^0.5.0" + tapable "^1.0.0" + +enhanced-resolve@^5.3.1: + version "5.4.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.4.0.tgz#a8bcf23b00affac9455cf71efd80844f4054f4dc" + integrity sha512-ZmqfWURB2lConOBM1JdCVfPyMRv5RdKWktLXO6123p97ovVm2CLBgw9t5MBj3jJWA6eHyOeIws9iJQoGFR4euQ== + dependencies: + graceful-fs "^4.2.4" + tapable "^2.0.0" + +enquirer@^2.3.5, enquirer@^2.3.6: + version "2.3.6" + resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" + integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== + dependencies: + ansi-colors "^4.1.1" + +entities@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" + integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w== + +entities@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5" + integrity sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w== + +envinfo@^7.7.3: + version "7.7.3" + resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.7.3.tgz#4b2d8622e3e7366afb8091b23ed95569ea0208cc" + integrity sha512-46+j5QxbPWza0PB1i15nZx0xQ4I/EfQxg9J8Had3b408SV63nEtor2e+oiY63amTo9KTuh2a3XLObNwduxYwwA== + +errno@^0.1.1, errno@^0.1.3: + version "0.1.7" + resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618" + integrity sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg== + dependencies: + prr "~1.0.1" + +error-ex@^1.2.0, error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +es-abstract@^1.17.0, es-abstract@^1.17.0-next.1, es-abstract@^1.17.2, es-abstract@^1.17.5: + version "1.17.7" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.7.tgz#a4de61b2f66989fc7421676c1cb9787573ace54c" + integrity sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g== + dependencies: + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + is-callable "^1.2.2" + is-regex "^1.1.1" + object-inspect "^1.8.0" + object-keys "^1.1.1" + object.assign "^4.1.1" + string.prototype.trimend "^1.0.1" + string.prototype.trimstart "^1.0.1" + +es-abstract@^1.18.0-next.0, es-abstract@^1.18.0-next.1: + version "1.18.0-next.1" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.0-next.1.tgz#6e3a0a4bda717e5023ab3b8e90bec36108d22c68" + integrity sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA== + dependencies: + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + is-callable "^1.2.2" + is-negative-zero "^2.0.0" + is-regex "^1.1.1" + object-inspect "^1.8.0" + object-keys "^1.1.1" + object.assign "^4.1.1" + string.prototype.trimend "^1.0.1" + string.prototype.trimstart "^1.0.1" + +es-to-primitive@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + +escalade@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + +escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= + +escape-string-regexp@^1.0.3, escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + +eslint-config-airbnb-base@14.2.0, eslint-config-airbnb-base@^14.2.0: + version "14.2.0" + resolved "https://registry.yarnpkg.com/eslint-config-airbnb-base/-/eslint-config-airbnb-base-14.2.0.tgz#fe89c24b3f9dc8008c9c0d0d88c28f95ed65e9c4" + integrity sha512-Snswd5oC6nJaevs3nZoLSTvGJBvzTfnBqOIArkf3cbyTyq9UD79wOk8s+RiL6bhca0p/eRO6veczhf6A/7Jy8Q== + dependencies: + confusing-browser-globals "^1.0.9" + object.assign "^4.1.0" + object.entries "^1.1.2" + +eslint-config-airbnb-typescript@^12.0.0: + version "12.0.0" + resolved "https://registry.yarnpkg.com/eslint-config-airbnb-typescript/-/eslint-config-airbnb-typescript-12.0.0.tgz#4bb6b4b72b1cfc45ef1fa0607735679ceb9a3814" + integrity sha512-TUCVru1Z09eKnVAX5i3XoNzjcCOU3nDQz2/jQGkg1jVYm+25fKClveziSl16celfCq+npU0MBPW/ZnXdGFZ9lw== + dependencies: + "@typescript-eslint/parser" "4.4.1" + eslint-config-airbnb "18.2.0" + eslint-config-airbnb-base "14.2.0" + +eslint-config-airbnb@18.2.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/eslint-config-airbnb/-/eslint-config-airbnb-18.2.0.tgz#8a82168713effce8fc08e10896a63f1235499dcd" + integrity sha512-Fz4JIUKkrhO0du2cg5opdyPKQXOI2MvF8KUvN2710nJMT6jaRUpRE2swrJftAjVGL7T1otLM5ieo5RqS1v9Udg== + dependencies: + eslint-config-airbnb-base "^14.2.0" + object.assign "^4.1.0" + object.entries "^1.1.2" + +eslint-import-resolver-node@^0.3.4: + version "0.3.4" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz#85ffa81942c25012d8231096ddf679c03042c717" + integrity sha512-ogtf+5AB/O+nM6DIeBUNr2fuT7ot9Qg/1harBfBtaP13ekEWFQEEMP94BCB7zaNW3gyY+8SHYF00rnqYwXKWOA== + dependencies: + debug "^2.6.9" + resolve "^1.13.1" + +eslint-import-resolver-typescript@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-2.3.0.tgz#0870988098bc6c6419c87705e6b42bee89425445" + integrity sha512-MHSXvmj5e0SGOOBhBbt7C+fWj1bJbtSYFAD85Xeg8nvUtuooTod2HQb8bfhE9f5QyyNxEfgzqOYFCvmdDIcCuw== + dependencies: + debug "^4.1.1" + glob "^7.1.6" + is-glob "^4.0.1" + resolve "^1.17.0" + tsconfig-paths "^3.9.0" + +eslint-loader@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/eslint-loader/-/eslint-loader-4.0.2.tgz#386a1e21bcb613b3cf2d252a3b708023ccfb41ec" + integrity sha512-EDpXor6lsjtTzZpLUn7KmXs02+nIjGcgees9BYjNkWra3jVq5vVa8IoCKgzT2M7dNNeoMBtaSG83Bd40N3poLw== + dependencies: + find-cache-dir "^3.3.1" + fs-extra "^8.1.0" + loader-utils "^2.0.0" + object-hash "^2.0.3" + schema-utils "^2.6.5" + +eslint-module-utils@^2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.6.0.tgz#579ebd094f56af7797d19c9866c9c9486629bfa6" + integrity sha512-6j9xxegbqe8/kZY8cYpcp0xhbK0EgJlg3g9mib3/miLaExuuwc3n5UEfSnU6hWMbT0FAYVvDbL9RrRgpUeQIvA== + dependencies: + debug "^2.6.9" + pkg-dir "^2.0.0" + +eslint-plugin-import@^2.22.1: + version "2.22.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.22.1.tgz#0896c7e6a0cf44109a2d97b95903c2bb689d7702" + integrity sha512-8K7JjINHOpH64ozkAhpT3sd+FswIZTfMZTjdx052pnWrgRCVfp8op9tbjpAk3DdUeI/Ba4C8OjdC0r90erHEOw== + dependencies: + array-includes "^3.1.1" + array.prototype.flat "^1.2.3" + contains-path "^0.1.0" + debug "^2.6.9" + doctrine "1.5.0" + eslint-import-resolver-node "^0.3.4" + eslint-module-utils "^2.6.0" + has "^1.0.3" + minimatch "^3.0.4" + object.values "^1.1.1" + read-pkg-up "^2.0.0" + resolve "^1.17.0" + tsconfig-paths "^3.9.0" + +eslint-plugin-react-hooks@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.2.0.tgz#8c229c268d468956334c943bb45fc860280f5556" + integrity sha512-623WEiZJqxR7VdxFCKLI6d6LLpwJkGPYKODnkH3D7WpOG5KM8yWueBd8TLsNAetEJNF5iJmolaAKO3F8yzyVBQ== + +eslint-plugin-react@^7.21.5: + version "7.21.5" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.21.5.tgz#50b21a412b9574bfe05b21db176e8b7b3b15bff3" + integrity sha512-8MaEggC2et0wSF6bUeywF7qQ46ER81irOdWS4QWxnnlAEsnzeBevk1sWh7fhpCghPpXb+8Ks7hvaft6L/xsR6g== + dependencies: + array-includes "^3.1.1" + array.prototype.flatmap "^1.2.3" + doctrine "^2.1.0" + has "^1.0.3" + jsx-ast-utils "^2.4.1 || ^3.0.0" + object.entries "^1.1.2" + object.fromentries "^2.0.2" + object.values "^1.1.1" + prop-types "^15.7.2" + resolve "^1.18.1" + string.prototype.matchall "^4.0.2" + +eslint-scope@^5.0.0, eslint-scope@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + +eslint-utils@^2.0.0, eslint-utils@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" + integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== + dependencies: + eslint-visitor-keys "^1.1.0" + +eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" + integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== + +eslint-visitor-keys@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz#21fdc8fbcd9c795cc0321f0563702095751511a8" + integrity sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ== + +eslint@^7.11.0: + version "7.11.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.11.0.tgz#aaf2d23a0b5f1d652a08edacea0c19f7fadc0b3b" + integrity sha512-G9+qtYVCHaDi1ZuWzBsOWo2wSwd70TXnU6UHA3cTYHp7gCTXZcpggWFoUVAMRarg68qtPoNfFbzPh+VdOgmwmw== + dependencies: + "@babel/code-frame" "^7.0.0" + "@eslint/eslintrc" "^0.1.3" + ajv "^6.10.0" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.0.1" + doctrine "^3.0.0" + enquirer "^2.3.5" + eslint-scope "^5.1.1" + eslint-utils "^2.1.0" + eslint-visitor-keys "^2.0.0" + espree "^7.3.0" + esquery "^1.2.0" + esutils "^2.0.2" + file-entry-cache "^5.0.1" + functional-red-black-tree "^1.0.1" + glob-parent "^5.0.0" + globals "^12.1.0" + ignore "^4.0.6" + import-fresh "^3.0.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + js-yaml "^3.13.1" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash "^4.17.19" + minimatch "^3.0.4" + natural-compare "^1.4.0" + optionator "^0.9.1" + progress "^2.0.0" + regexpp "^3.1.0" + semver "^7.2.1" + strip-ansi "^6.0.0" + strip-json-comments "^3.1.0" + table "^5.2.3" + text-table "^0.2.0" + v8-compile-cache "^2.0.3" + +espree@^7.3.0: + version "7.3.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.0.tgz#dc30437cf67947cf576121ebd780f15eeac72348" + integrity sha512-dksIWsvKCixn1yrEXO8UosNSxaDoSYpq9reEjZSbHLpT5hpaCAKTLBwq0RHtLrIr+c0ByiYzWT8KTMRzoRCNlw== + dependencies: + acorn "^7.4.0" + acorn-jsx "^5.2.0" + eslint-visitor-keys "^1.3.0" + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esquery@^1.2.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.3.1.tgz#b78b5828aa8e214e29fb74c4d5b752e1c033da57" + integrity sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^4.1.1: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.2.0.tgz#307df42547e6cc7324d3cf03c155d5cdb8c53880" + integrity sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= + +eventemitter3@^4.0.0: + version "4.0.7" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" + integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== + +events@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.2.0.tgz#93b87c18f8efcd4202a461aec4dfc0556b639379" + integrity sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg== + +eventsource@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-1.0.7.tgz#8fbc72c93fcd34088090bc0a4e64f4b5cee6d8d0" + integrity sha512-4Ln17+vVT0k8aWq+t/bF5arcS3EpT9gYtW66EPacdj/mAFevznsnyoHLPy2BA8gbIQeIHoPsvwmfBftfcG//BQ== + dependencies: + original "^1.0.0" + +execa@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" + integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA== + dependencies: + cross-spawn "^6.0.0" + get-stream "^4.0.0" + is-stream "^1.1.0" + npm-run-path "^2.0.0" + p-finally "^1.0.0" + signal-exit "^3.0.0" + strip-eof "^1.0.0" + +execa@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a" + integrity sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA== + dependencies: + cross-spawn "^7.0.0" + get-stream "^5.0.0" + human-signals "^1.1.1" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.0" + onetime "^5.1.0" + signal-exit "^3.0.2" + strip-final-newline "^2.0.0" + +execall@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/execall/-/execall-2.0.0.tgz#16a06b5fe5099df7d00be5d9c06eecded1663b45" + integrity sha512-0FU2hZ5Hh6iQnarpRtQurM/aAvp3RIbfvgLHrcqJYzhXyV2KFruhuChf9NC6waAhiUR7FFtlugkI4p7f2Fqlow== + dependencies: + clone-regexp "^2.1.0" + +expand-brackets@^2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" + integrity sha1-t3c14xXOMPa27/D4OwQVGiJEliI= + dependencies: + debug "^2.3.3" + define-property "^0.2.5" + extend-shallow "^2.0.1" + posix-character-classes "^0.1.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +express@^4.17.1: + version "4.17.1" + resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" + integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g== + dependencies: + accepts "~1.3.7" + array-flatten "1.1.1" + body-parser "1.19.0" + content-disposition "0.5.3" + content-type "~1.0.4" + cookie "0.4.0" + cookie-signature "1.0.6" + debug "2.6.9" + depd "~1.1.2" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "~1.1.2" + fresh "0.5.2" + merge-descriptors "1.0.1" + methods "~1.1.2" + on-finished "~2.3.0" + parseurl "~1.3.3" + path-to-regexp "0.1.7" + proxy-addr "~2.0.5" + qs "6.7.0" + range-parser "~1.2.1" + safe-buffer "5.1.2" + send "0.17.1" + serve-static "1.14.1" + setprototypeof "1.1.1" + statuses "~1.5.0" + type-is "~1.6.18" + utils-merge "1.0.1" + vary "~1.1.2" + +extend-shallow@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" + integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8= + dependencies: + is-extendable "^0.1.0" + +extend-shallow@^3.0.0, extend-shallow@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" + integrity sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg= + dependencies: + assign-symbols "^1.0.0" + is-extendable "^1.0.1" + +extend@^3.0.0, extend@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + +extglob@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" + integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw== + dependencies: + array-unique "^0.3.2" + define-property "^1.0.0" + expand-brackets "^2.1.4" + extend-shallow "^2.0.1" + fragment-cache "^0.2.1" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +fast-deep-equal@^3.1.1: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-glob@^3.1.1, fast-glob@^3.2.4: + version "3.2.4" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.4.tgz#d20aefbf99579383e7f3cc66529158c9b98554d3" + integrity sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.0" + merge2 "^1.3.0" + micromatch "^4.0.2" + picomatch "^2.2.1" + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= + +fastest-levenshtein@^1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz#9990f7d3a88cc5a9ffd1f1745745251700d497e2" + integrity sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow== + +fastq@^1.6.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.8.0.tgz#550e1f9f59bbc65fe185cb6a9b4d95357107f481" + integrity sha512-SMIZoZdLh/fgofivvIkmknUXyPnvxRE3DhtZ5Me3Mrsk5gyPL42F0xr51TdRXskBxHfMp+07bcYzfsYEsSQA9Q== + dependencies: + reusify "^1.0.4" + +faye-websocket@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.10.0.tgz#4e492f8d04dfb6f89003507f6edbf2d501e7c6f4" + integrity sha1-TkkvjQTftviQA1B/btvy1QHnxvQ= + dependencies: + websocket-driver ">=0.5.1" + +faye-websocket@~0.11.1: + version "0.11.3" + resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.3.tgz#5c0e9a8968e8912c286639fde977a8b209f2508e" + integrity sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA== + dependencies: + websocket-driver ">=0.5.1" + +file-entry-cache@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz#ca0f6efa6dd3d561333fb14515065c2fafdf439c" + integrity sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g== + dependencies: + flat-cache "^2.0.1" + +file-loader@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.1.1.tgz#a6f29dfb3f5933a1c350b2dbaa20ac5be0539baa" + integrity sha512-Klt8C4BjWSXYQAfhpYYkG4qHNTna4toMHEbWrI5IuVoxbU6uiDKeKAP99R8mmbJi3lvewn/jQBOgU4+NS3tDQw== + dependencies: + loader-utils "^2.0.0" + schema-utils "^3.0.0" + +file-uri-to-path@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" + integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== + +fill-range@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" + integrity sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc= + dependencies: + extend-shallow "^2.0.1" + is-number "^3.0.0" + repeat-string "^1.6.1" + to-regex-range "^2.1.0" + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +finalhandler@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" + integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA== + dependencies: + debug "2.6.9" + encodeurl "~1.0.2" + escape-html "~1.0.3" + on-finished "~2.3.0" + parseurl "~1.3.3" + statuses "~1.5.0" + unpipe "~1.0.0" + +find-cache-dir@^3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.1.tgz#89b33fad4a4670daa94f855f7fbe31d6d84fe880" + integrity sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ== + dependencies: + commondir "^1.0.1" + make-dir "^3.0.2" + pkg-dir "^4.1.0" + +find-up@^2.0.0, find-up@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" + integrity sha1-RdG35QbHF93UgndaK3eSCjwMV6c= + dependencies: + locate-path "^2.0.0" + +find-up@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" + integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== + dependencies: + locate-path "^3.0.0" + +find-up@^4.0.0, find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +find-versions@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/find-versions/-/find-versions-3.2.0.tgz#10297f98030a786829681690545ef659ed1d254e" + integrity sha512-P8WRou2S+oe222TOCHitLy8zj+SIsVJh52VP4lvXkaFVnOFFdoWv1H1Jjvel1aI6NCFOAaeAVm8qrI0odiLcww== + dependencies: + semver-regex "^2.0.0" + +flat-cache@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0" + integrity sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA== + dependencies: + flatted "^2.0.0" + rimraf "2.6.3" + write "1.0.3" + +flatted@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138" + integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA== + +flatten@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.3.tgz#c1283ac9f27b368abc1e36d1ff7b04501a30356b" + integrity sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg== + +follow-redirects@^1.0.0: + version "1.13.0" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.0.tgz#b42e8d93a2a7eea5ed88633676d6597bc8e384db" + integrity sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA== + +for-in@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" + integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= + +formik@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/formik/-/formik-2.2.0.tgz#04c067f6b832aa15cae055f6df6f4aa485b5be35" + integrity sha512-l47RfvejhfHNh8rTRVaCaPfx8nyeYDSTLaEqRvLX4qkWnrrq9ByGVCWggVR+0TVtzc5Ub1gLUuVu9UKuGwfhjA== + dependencies: + deepmerge "^2.1.1" + hoist-non-react-statics "^3.3.0" + lodash "^4.17.14" + lodash-es "^4.17.14" + react-fast-compare "^2.0.1" + scheduler "^0.18.0" + tiny-warning "^1.0.2" + tslib "^1.10.0" + +forwarded@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" + integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= + +fragment-cache@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" + integrity sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk= + dependencies: + map-cache "^0.2.2" + +fresh@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= + +fs-extra@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" + integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^4.0.0" + universalify "^0.1.0" + +fs-extra@^9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.0.1.tgz#910da0062437ba4c39fedd863f1675ccfefcb9fc" + integrity sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ== + dependencies: + at-least-node "^1.0.0" + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^1.0.0" + +fs-minipass@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" + integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== + dependencies: + minipass "^3.0.0" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +fsevents@^1.2.7: + version "1.2.13" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.13.tgz#f325cb0455592428bcf11b383370ef70e3bfcc38" + integrity sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw== + dependencies: + bindings "^1.5.0" + nan "^2.12.1" + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +functional-red-black-tree@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" + integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= + +generic-names@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/generic-names/-/generic-names-2.0.1.tgz#f8a378ead2ccaa7a34f0317b05554832ae41b872" + integrity sha512-kPCHWa1m9wGG/OwQpeweTwM/PYiQLrUIxXbt/P4Nic3LbGjCP0YwrALHW1uNLKZ0LIMg+RF+XRlj2ekT9ZlZAQ== + dependencies: + loader-utils "^1.1.0" + +gensync@^1.0.0-beta.1: + version "1.0.0-beta.1" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.1.tgz#58f4361ff987e5ff6e1e7a210827aa371eaac269" + integrity sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg== + +get-caller-file@^2.0.1: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-intrinsic@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.0.1.tgz#94a9768fcbdd0595a1c9273aacf4c89d075631be" + integrity sha512-ZnWP+AmS1VUaLgTRy47+zKtjTxz+0xMpx3I52i+aalBK1QP19ggLF3Db89KJX7kjfOfP2eoa01qc++GwPgufPg== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + +get-stdin@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-8.0.0.tgz#cbad6a73feb75f6eeb22ba9e01f89aa28aa97a53" + integrity sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg== + +get-stream@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" + integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== + dependencies: + pump "^3.0.0" + +get-stream@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" + integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== + dependencies: + pump "^3.0.0" + +get-value@^2.0.3, get-value@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" + integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= + +glob-parent@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" + integrity sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4= + dependencies: + is-glob "^3.1.0" + path-dirname "^1.0.0" + +glob-parent@^5.0.0, glob-parent@^5.1.0, glob-parent@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229" + integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ== + dependencies: + is-glob "^4.0.1" + +glob-to-regexp@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" + integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== + +glob@^7.0.3, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: + version "7.1.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +global-modules@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780" + integrity sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A== + dependencies: + global-prefix "^3.0.0" + +global-prefix@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-3.0.0.tgz#fc85f73064df69f50421f47f883fe5b913ba9b97" + integrity sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg== + dependencies: + ini "^1.3.5" + kind-of "^6.0.2" + which "^1.3.1" + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +globals@^12.1.0: + version "12.4.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-12.4.0.tgz#a18813576a41b00a24a97e7f815918c2e19925f8" + integrity sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg== + dependencies: + type-fest "^0.8.1" + +globby@^11.0.1: + version "11.0.1" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.1.tgz#9a2bf107a068f3ffeabc49ad702c79ede8cfd357" + integrity sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.1.1" + ignore "^5.1.4" + merge2 "^1.3.0" + slash "^3.0.0" + +globby@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c" + integrity sha1-9abXDoOV4hyFj7BInWTfAkJNUGw= + dependencies: + array-union "^1.0.1" + glob "^7.0.3" + object-assign "^4.0.1" + pify "^2.0.0" + pinkie-promise "^2.0.0" + +globjoin@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/globjoin/-/globjoin-0.1.4.tgz#2f4494ac8919e3767c5cbb691e9f463324285d43" + integrity sha1-L0SUrIkZ43Z8XLtpHp9GMyQoXUM= + +gonzales-pe@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/gonzales-pe/-/gonzales-pe-4.3.0.tgz#fe9dec5f3c557eead09ff868c65826be54d067b3" + integrity sha512-otgSPpUmdWJ43VXyiNgEYE4luzHCL2pz4wQ0OnDluC6Eg4Ko3Vexy/SrSynglw/eR+OhkzmqFCZa/OFa/RgAOQ== + dependencies: + minimist "^1.2.5" + +graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4: + version "4.2.4" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" + integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== + +handle-thing@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e" + integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg== + +hard-rejection@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/hard-rejection/-/hard-rejection-2.1.0.tgz#1c6eda5c1685c63942766d79bb40ae773cecd883" + integrity sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-symbols@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" + integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== + +has-value@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" + integrity sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8= + dependencies: + get-value "^2.0.3" + has-values "^0.1.4" + isobject "^2.0.0" + +has-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" + integrity sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc= + dependencies: + get-value "^2.0.6" + has-values "^1.0.0" + isobject "^3.0.0" + +has-values@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" + integrity sha1-bWHeldkd/Km5oCCJrThL/49it3E= + +has-values@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" + integrity sha1-lbC2P+whRmGab+V/51Yo1aOe/k8= + dependencies: + is-number "^3.0.0" + kind-of "^4.0.0" + +has@^1.0.0, has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +he@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" + integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== + +hex-color-regex@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e" + integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ== + +history@^4.9.0: + version "4.10.1" + resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3" + integrity sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew== + dependencies: + "@babel/runtime" "^7.1.2" + loose-envify "^1.2.0" + resolve-pathname "^3.0.0" + tiny-invariant "^1.0.2" + tiny-warning "^1.0.0" + value-equal "^1.0.1" + +hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" + integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== + dependencies: + react-is "^16.7.0" + +hosted-git-info@^2.1.4: + version "2.8.8" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488" + integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg== + +hpack.js@^2.1.6: + version "2.1.6" + resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2" + integrity sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI= + dependencies: + inherits "^2.0.1" + obuf "^1.0.0" + readable-stream "^2.0.1" + wbuf "^1.1.0" + +hsl-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/hsl-regex/-/hsl-regex-1.0.0.tgz#d49330c789ed819e276a4c0d272dffa30b18fe6e" + integrity sha1-1JMwx4ntgZ4nakwNJy3/owsY/m4= + +hsla-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/hsla-regex/-/hsla-regex-1.0.0.tgz#c1ce7a3168c8c6614033a4b5f7877f3b225f9c38" + integrity sha1-wc56MWjIxmFAM6S194d/OyJfnDg= + +html-comment-regex@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.2.tgz#97d4688aeb5c81886a364faa0cad1dda14d433a7" + integrity sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ== + +html-entities@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.3.1.tgz#fb9a1a4b5b14c5daba82d3e34c6ae4fe701a0e44" + integrity sha512-rhE/4Z3hIhzHAUKbW8jVcCyuT5oJCXXqhN/6mXXVCpzTmvJnoH2HL/bt3EZ6p55jbFJBeAe1ZNpL5BugLujxNA== + +html-minifier-terser@^5.0.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz#922e96f1f3bb60832c2634b79884096389b1f054" + integrity sha512-ZPr5MNObqnV/T9akshPKbVgyOqLmy+Bxo7juKCfTfnjNniTAMdy4hz21YQqoofMBJD2kdREaqPPdThoR78Tgxg== + dependencies: + camel-case "^4.1.1" + clean-css "^4.2.3" + commander "^4.1.1" + he "^1.2.0" + param-case "^3.0.3" + relateurl "^0.2.7" + terser "^4.6.3" + +html-tags@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.1.0.tgz#7b5e6f7e665e9fb41f30007ed9e0d41e97fb2140" + integrity sha512-1qYz89hW3lFDEazhjW0yVAV87lw8lVkrJocr72XmBkMKsoSVJCQx3W8BXsC7hO2qAt8BoVjYjtAcZ9perqGnNg== + +html-webpack-plugin@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-4.5.0.tgz#625097650886b97ea5dae331c320e3238f6c121c" + integrity sha512-MouoXEYSjTzCrjIxWwg8gxL5fE2X2WZJLmBYXlaJhQUH5K/b5OrqmV7T4dB7iu0xkmJ6JlUuV6fFVtnqbPopZw== + dependencies: + "@types/html-minifier-terser" "^5.0.0" + "@types/tapable" "^1.0.5" + "@types/webpack" "^4.41.8" + html-minifier-terser "^5.0.1" + loader-utils "^1.2.3" + lodash "^4.17.15" + pretty-error "^2.1.1" + tapable "^1.1.3" + util.promisify "1.0.0" + +htmlparser2@^3.10.0, htmlparser2@^3.10.1, htmlparser2@^3.3.0: + version "3.10.1" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f" + integrity sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ== + dependencies: + domelementtype "^1.3.1" + domhandler "^2.3.0" + domutils "^1.5.1" + entities "^1.1.1" + inherits "^2.0.1" + readable-stream "^3.1.1" + +http-deceiver@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" + integrity sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc= + +http-errors@1.7.2: + version "1.7.2" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f" + integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg== + dependencies: + depd "~1.1.2" + inherits "2.0.3" + setprototypeof "1.1.1" + statuses ">= 1.5.0 < 2" + toidentifier "1.0.0" + +http-errors@~1.6.2: + version "1.6.3" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" + integrity sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0= + dependencies: + depd "~1.1.2" + inherits "2.0.3" + setprototypeof "1.1.0" + statuses ">= 1.4.0 < 2" + +http-errors@~1.7.2: + version "1.7.3" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" + integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw== + dependencies: + depd "~1.1.2" + inherits "2.0.4" + setprototypeof "1.1.1" + statuses ">= 1.5.0 < 2" + toidentifier "1.0.0" + +http-parser-js@>=0.5.1: + version "0.5.2" + resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.2.tgz#da2e31d237b393aae72ace43882dd7e270a8ff77" + integrity sha512-opCO9ASqg5Wy2FNo7A0sxy71yGbbkJJXLdgMK04Tcypw9jr2MgWbyubb0+WdmDmGnFflO7fRbqbaihh/ENDlRQ== + +http-proxy-middleware@0.19.1: + version "0.19.1" + resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz#183c7dc4aa1479150306498c210cdaf96080a43a" + integrity sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q== + dependencies: + http-proxy "^1.17.0" + is-glob "^4.0.0" + lodash "^4.17.11" + micromatch "^3.1.10" + +http-proxy-middleware@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-1.0.6.tgz#0618557722f450375d3796d701a8ac5407b3b94e" + integrity sha512-NyL6ZB6cVni7pl+/IT2W0ni5ME00xR0sN27AQZZrpKn1b+qRh+mLbBxIq9Cq1oGfmTc7BUq4HB77mxwCaxAYNg== + dependencies: + "@types/http-proxy" "^1.17.4" + http-proxy "^1.18.1" + is-glob "^4.0.1" + lodash "^4.17.20" + micromatch "^4.0.2" + +http-proxy@^1.17.0, http-proxy@^1.18.1: + version "1.18.1" + resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549" + integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ== + dependencies: + eventemitter3 "^4.0.0" + follow-redirects "^1.0.0" + requires-port "^1.0.0" + +human-signals@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" + integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw== + +husky@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/husky/-/husky-4.3.0.tgz#0b2ec1d66424e9219d359e26a51c58ec5278f0de" + integrity sha512-tTMeLCLqSBqnflBZnlVDhpaIMucSGaYyX6855jM4AguGeWCeSzNdb1mfyWduTZ3pe3SJVvVWGL0jO1iKZVPfTA== + dependencies: + chalk "^4.0.0" + ci-info "^2.0.0" + compare-versions "^3.6.0" + cosmiconfig "^7.0.0" + find-versions "^3.2.0" + opencollective-postinstall "^2.0.2" + pkg-dir "^4.2.0" + please-upgrade-node "^3.2.0" + slash "^3.0.0" + which-pm-runs "^1.0.0" + +iconv-lite@0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +icss-replace-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz#06ea6f83679a7749e386cfe1fe812ae5db223ded" + integrity sha1-Bupvg2ead0njhs/h/oEq5dsiPe0= + +icss-utils@^4.0.0, icss-utils@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-4.1.1.tgz#21170b53789ee27447c2f47dd683081403f9a467" + integrity sha512-4aFq7wvWyMHKgxsH8QQtGpvbASCf+eM3wPRLI6R+MgAnTCZ6STYsRvttLvRWK0Nfif5piF394St3HeJDaljGPA== + dependencies: + postcss "^7.0.14" + +icss-utils@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.0.0.tgz#03ed56c3accd32f9caaf1752ebf64ef12347bb84" + integrity sha512-aF2Cf/CkEZrI/vsu5WI/I+akFgdbwQHVE9YRZxATrhH4PVIe6a3BIjwjEcW+z+jP/hNh+YvM3lAAn1wJQ6opSg== + +ignore@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" + integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== + +ignore@^5.1.4, ignore@^5.1.8: + version "5.1.8" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" + integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== + +image-size@~0.5.0: + version "0.5.5" + resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.5.5.tgz#09dfd4ab9d20e29eb1c3e80b8990378df9e3cb9c" + integrity sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w= + +import-fresh@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546" + integrity sha1-2BNVwVYS04bGH53dOSLUMEgipUY= + dependencies: + caller-path "^2.0.0" + resolve-from "^3.0.0" + +import-fresh@^3.0.0, import-fresh@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.2.1.tgz#633ff618506e793af5ac91bf48b72677e15cbe66" + integrity sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +import-lazy@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-4.0.0.tgz#e8eb627483a0a43da3c03f3e35548be5cb0cc153" + integrity sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw== + +import-local@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-2.0.0.tgz#55070be38a5993cf18ef6db7e961f5bee5c5a09d" + integrity sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ== + dependencies: + pkg-dir "^3.0.0" + resolve-cwd "^2.0.0" + +import-local@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.0.2.tgz#a8cfd0431d1de4a2199703d003e3e62364fa6db6" + integrity sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA== + dependencies: + pkg-dir "^4.2.0" + resolve-cwd "^3.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= + +indent-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" + integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== + +indexes-of@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" + integrity sha1-8w9xbI4r00bHtn0985FVZqfAVgc= + +infer-owner@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467" + integrity sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@2.0.4, inherits@^2.0.0, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +inherits@2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= + +ini@^1.3.5: + version "1.3.5" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" + integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== + +insert-css@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/insert-css/-/insert-css-2.0.0.tgz#eb5d1097b7542f4c79ea3060d3aee07d053880f4" + integrity sha1-610Ql7dUL0x56jBg067gfQU4gPQ= + +internal-ip@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-4.3.0.tgz#845452baad9d2ca3b69c635a137acb9a0dad0907" + integrity sha512-S1zBo1D6zcsyuC6PMmY5+55YMILQ9av8lotMx447Bq6SAgo/sDK6y6uUKmuYhW7eacnIhFfsPmCNYdDzsnnDCg== + dependencies: + default-gateway "^4.2.0" + ipaddr.js "^1.9.0" + +internal-slot@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.2.tgz#9c2e9fb3cd8e5e4256c6f45fe310067fcfa378a3" + integrity sha512-2cQNfwhAfJIkU4KZPkDI+Gj5yNNnbqi40W9Gge6dfnk4TocEVm00B3bdiL+JINrbGJil2TeHvM4rETGzk/f/0g== + dependencies: + es-abstract "^1.17.0-next.1" + has "^1.0.3" + side-channel "^1.0.2" + +interpret@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9" + integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw== + +ip-regex@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" + integrity sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk= + +ip@^1.1.0, ip@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" + integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo= + +ipaddr.js@1.9.1, ipaddr.js@^1.9.0: + version "1.9.1" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" + integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== + +is-absolute-url@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz#50530dfb84fcc9aa7dbe7852e83a37b93b9f2aa6" + integrity sha1-UFMN+4T8yap9vnhS6Do3uTufKqY= + +is-absolute-url@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-3.0.3.tgz#96c6a22b6a23929b11ea0afb1836c36ad4a5d698" + integrity sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q== + +is-absolute@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-absolute/-/is-absolute-1.0.0.tgz#395e1ae84b11f26ad1795e73c17378e48a301576" + integrity sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA== + dependencies: + is-relative "^1.0.0" + is-windows "^1.0.1" + +is-accessor-descriptor@^0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" + integrity sha1-qeEss66Nh2cn7u84Q/igiXtcmNY= + dependencies: + kind-of "^3.0.2" + +is-accessor-descriptor@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" + integrity sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ== + dependencies: + kind-of "^6.0.0" + +is-alphabetical@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-1.0.4.tgz#9e7d6b94916be22153745d184c298cbf986a686d" + integrity sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg== + +is-alphanumeric@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-alphanumeric/-/is-alphanumeric-1.0.0.tgz#4a9cef71daf4c001c1d81d63d140cf53fd6889f4" + integrity sha1-Spzvcdr0wAHB2B1j0UDPU/1oifQ= + +is-alphanumerical@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz#7eb9a2431f855f6b1ef1a78e326df515696c4dbf" + integrity sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A== + dependencies: + is-alphabetical "^1.0.0" + is-decimal "^1.0.0" + +is-arguments@^1.0.4: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.0.tgz#62353031dfbee07ceb34656a6bde59efecae8dd9" + integrity sha512-1Ij4lOMPl/xB5kBDn7I+b2ttPMKa8szhEIrXDuXQD/oe3HJLTLhqhgGspwgyGd6MOywBUqVvYicF72lkgDnIHg== + dependencies: + call-bind "^1.0.0" + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= + +is-arrayish@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" + integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== + +is-binary-path@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" + integrity sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg= + dependencies: + binary-extensions "^1.0.0" + +is-buffer@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" + integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== + +is-buffer@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.4.tgz#3e572f23c8411a5cfd9557c849e3665e0b290623" + integrity sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A== + +is-callable@^1.1.4, is-callable@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.2.tgz#c7c6715cd22d4ddb48d3e19970223aceabb080d9" + integrity sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA== + +is-color-stop@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-color-stop/-/is-color-stop-1.1.0.tgz#cfff471aee4dd5c9e158598fbe12967b5cdad345" + integrity sha1-z/9HGu5N1cnhWFmPvhKWe1za00U= + dependencies: + css-color-names "^0.0.4" + hex-color-regex "^1.1.0" + hsl-regex "^1.0.0" + hsla-regex "^1.0.0" + rgb-regex "^1.0.1" + rgba-regex "^1.0.0" + +is-core-module@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.0.0.tgz#58531b70aed1db7c0e8d4eb1a0a2d1ddd64bd12d" + integrity sha512-jq1AH6C8MuteOoBPwkxHafmByhL9j5q4OaPGdbuD+ZtQJVzH+i6E3BJDQcBA09k57i2Hh2yQbEG8yObZ0jdlWw== + dependencies: + has "^1.0.3" + +is-core-module@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.2.0.tgz#97037ef3d52224d85163f5597b2b63d9afed981a" + integrity sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ== + dependencies: + has "^1.0.3" + +is-data-descriptor@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" + integrity sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y= + dependencies: + kind-of "^3.0.2" + +is-data-descriptor@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" + integrity sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ== + dependencies: + kind-of "^6.0.0" + +is-date-object@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" + integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== + +is-decimal@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-1.0.4.tgz#65a3a5958a1c5b63a706e1b333d7cd9f630d3fa5" + integrity sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw== + +is-descriptor@^0.1.0: + version "0.1.6" + resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" + integrity sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg== + dependencies: + is-accessor-descriptor "^0.1.6" + is-data-descriptor "^0.1.4" + kind-of "^5.0.0" + +is-descriptor@^1.0.0, is-descriptor@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" + integrity sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg== + dependencies: + is-accessor-descriptor "^1.0.0" + is-data-descriptor "^1.0.0" + kind-of "^6.0.2" + +is-directory@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1" + integrity sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE= + +is-extendable@^0.1.0, is-extendable@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" + integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= + +is-extendable@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" + integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA== + dependencies: + is-plain-object "^2.0.4" + +is-extglob@^2.1.0, is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-glob@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" + integrity sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo= + dependencies: + is-extglob "^2.1.0" + +is-glob@^4.0.0, is-glob@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" + integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== + dependencies: + is-extglob "^2.1.1" + +is-hexadecimal@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz#cc35c97588da4bd49a8eedd6bc4082d44dcb23a7" + integrity sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw== + +is-negated-glob@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-negated-glob/-/is-negated-glob-1.0.0.tgz#6910bca5da8c95e784b5751b976cf5a10fee36d2" + integrity sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI= + +is-negative-zero@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.0.tgz#9553b121b0fac28869da9ed459e20c7543788461" + integrity sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE= + +is-number@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" + integrity sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU= + dependencies: + kind-of "^3.0.2" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-obj@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" + integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== + +is-path-cwd@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-2.2.0.tgz#67d43b82664a7b5191fd9119127eb300048a9fdb" + integrity sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ== + +is-path-in-cwd@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz#bfe2dca26c69f397265a4009963602935a053acb" + integrity sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ== + dependencies: + is-path-inside "^2.1.0" + +is-path-inside@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-2.1.0.tgz#7c9810587d659a40d27bcdb4d5616eab059494b2" + integrity sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg== + dependencies: + path-is-inside "^1.0.2" + +is-plain-obj@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" + integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4= + +is-plain-obj@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" + integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== + +is-plain-object@^2.0.3, is-plain-object@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" + integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== + dependencies: + isobject "^3.0.1" + +is-regex@^1.0.4, is-regex@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.1.tgz#c6f98aacc546f6cec5468a07b7b153ab564a57b9" + integrity sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg== + dependencies: + has-symbols "^1.0.1" + +is-regexp@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-2.1.0.tgz#cd734a56864e23b956bf4e7c66c396a4c0b22c2d" + integrity sha512-OZ4IlER3zmRIoB9AqNhEggVxqIH4ofDns5nRrPS6yQxXE1TPCUpFznBfRQmQa8uC+pXqjMnukiJBxCisIxiLGA== + +is-relative@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-relative/-/is-relative-1.0.0.tgz#a1bb6935ce8c5dba1e8b9754b9b2dcc020e2260d" + integrity sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA== + dependencies: + is-unc-path "^1.0.0" + +is-resolvable@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88" + integrity sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg== + +is-stream@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" + integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= + +is-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3" + integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw== + +is-string@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.5.tgz#40493ed198ef3ff477b8c7f92f644ec82a5cd3a6" + integrity sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ== + +is-svg@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-3.0.0.tgz#9321dbd29c212e5ca99c4fa9794c714bcafa2f75" + integrity sha512-gi4iHK53LR2ujhLVVj+37Ykh9GLqYHX6JOVXbLAucaG/Cqw9xwdFOjDM2qeifLs1sF1npXXFvDu0r5HNgCMrzQ== + dependencies: + html-comment-regex "^1.1.0" + +is-symbol@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" + integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ== + dependencies: + has-symbols "^1.0.1" + +is-typedarray@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= + +is-unc-path@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-unc-path/-/is-unc-path-1.0.0.tgz#d731e8898ed090a12c352ad2eaed5095ad322c9d" + integrity sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ== + dependencies: + unc-path-regex "^0.1.2" + +is-whitespace-character@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-whitespace-character/-/is-whitespace-character-1.0.4.tgz#0858edd94a95594c7c9dd0b5c174ec6e45ee4aa7" + integrity sha512-SDweEzfIZM0SJV0EUga669UTKlmL0Pq8Lno0QDQsPnvECB3IM2aP0gdx5TrU0A01MAPfViaZiI2V1QMZLaKK5w== + +is-windows@^1.0.1, is-windows@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" + integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== + +is-word-character@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-word-character/-/is-word-character-1.0.4.tgz#ce0e73216f98599060592f62ff31354ddbeb0230" + integrity sha512-5SMO8RVennx3nZrqtKwCGyyetPE9VDba5ugvKLaD4KopPG5kR4mQ7tNt/r7feL5yt5h3lpuBbIUmCOG2eSzXHA== + +is-wsl@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" + integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0= + +isarray@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" + integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= + +isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + +isobject@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" + integrity sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk= + dependencies: + isarray "1.0.0" + +isobject@^3.0.0, isobject@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" + integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= + +jest-worker@^26.5.0: + version "26.5.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.5.0.tgz#87deee86dbbc5f98d9919e0dadf2c40e3152fa30" + integrity sha512-kTw66Dn4ZX7WpjZ7T/SUDgRhapFRKWmisVAF0Rv4Fu8SLFD7eLbqpLvbxVqYhSgaWa7I+bW7pHnbyfNsH6stug== + dependencies: + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^7.0.0" + +jest-worker@^26.6.1: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed" + integrity sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ== + dependencies: + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^7.0.0" + +"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.13.1: + version "3.14.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482" + integrity sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" + integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== + +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= + +json2mq@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/json2mq/-/json2mq-0.2.0.tgz#b637bd3ba9eabe122c83e9720483aeb10d2c904a" + integrity sha1-tje9O6nqvhIsg+lyBIOusQ0skEo= + dependencies: + string-convert "^0.2.0" + +json3@^3.3.2: + version "3.3.3" + resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.3.tgz#7fc10e375fc5ae42c4705a5cc0aa6f62be305b81" + integrity sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA== + +json5@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" + integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow== + dependencies: + minimist "^1.2.0" + +json5@^2.1.2: + version "2.1.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.3.tgz#c9b0f7fa9233bfe5807fe66fcf3a5617ed597d43" + integrity sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA== + dependencies: + minimist "^1.2.5" + +jsonfile@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" + integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss= + optionalDependencies: + graceful-fs "^4.1.6" + +jsonfile@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.0.1.tgz#98966cba214378c8c84b82e085907b40bf614179" + integrity sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg== + dependencies: + universalify "^1.0.0" + optionalDependencies: + graceful-fs "^4.1.6" + +"jsx-ast-utils@^2.4.1 || ^3.0.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.1.0.tgz#642f1d7b88aa6d7eb9d8f2210e166478444fa891" + integrity sha512-d4/UOjg+mxAWxCiF0c5UTSwyqbchkbqCvK87aBovhnh8GtysTjWmgC63tY0cJx/HzGgm9qnA147jVBdpOiQ2RA== + dependencies: + array-includes "^3.1.1" + object.assign "^4.1.1" + +killable@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892" + integrity sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg== + +kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" + integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ= + dependencies: + is-buffer "^1.1.5" + +kind-of@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" + integrity sha1-IIE989cSkosgc3hpGkUGb65y3Vc= + dependencies: + is-buffer "^1.1.5" + +kind-of@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" + integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== + +kind-of@^6.0.0, kind-of@^6.0.2, kind-of@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== + +klona@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.4.tgz#7bb1e3affb0cb8624547ef7e8f6708ea2e39dfc0" + integrity sha512-ZRbnvdg/NxqzC7L9Uyqzf4psi1OM4Cuc+sJAkQPjO6XkQIJTNbfK2Rsmbw8fx1p2mkZdp2FZYo2+LwXYY/uwIA== + +known-css-properties@^0.19.0: + version "0.19.0" + resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.19.0.tgz#5d92b7fa16c72d971bda9b7fe295bdf61836ee5b" + integrity sha512-eYboRV94Vco725nKMlpkn3nV2+96p9c3gKXRsYqAJSswSENvBhN7n5L+uDhY58xQa0UukWsDMTGELzmD8Q+wTA== + +last-call-webpack-plugin@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/last-call-webpack-plugin/-/last-call-webpack-plugin-3.0.0.tgz#9742df0e10e3cf46e5c0381c2de90d3a7a2d7555" + integrity sha512-7KI2l2GIZa9p2spzPIVZBYyNKkN+e/SQPpnjlTiPhdbDW3F86tdKKELxKpzJ5sgU19wQWsACULZmpTPYHeWO5w== + dependencies: + lodash "^4.17.5" + webpack-sources "^1.1.0" + +less-loader@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/less-loader/-/less-loader-5.0.0.tgz#498dde3a6c6c4f887458ee9ed3f086a12ad1b466" + integrity sha512-bquCU89mO/yWLaUq0Clk7qCsKhsF/TZpJUzETRvJa9KSVEL9SO3ovCvdEHISBhrC81OwC8QSVX7E0bzElZj9cg== + dependencies: + clone "^2.1.1" + loader-utils "^1.1.0" + pify "^4.0.1" + +less@^3.12.2: + version "3.12.2" + resolved "https://registry.yarnpkg.com/less/-/less-3.12.2.tgz#157e6dd32a68869df8859314ad38e70211af3ab4" + integrity sha512-+1V2PCMFkL+OIj2/HrtrvZw0BC0sYLMICJfbQjuj/K8CEnlrFX6R5cKKgzzttsZDHyxQNL1jqMREjKN3ja/E3Q== + dependencies: + tslib "^1.10.0" + optionalDependencies: + errno "^0.1.1" + graceful-fs "^4.1.2" + image-size "~0.5.0" + make-dir "^2.1.0" + mime "^1.4.1" + native-request "^1.0.5" + source-map "~0.6.0" + +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +line-column@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/line-column/-/line-column-1.0.2.tgz#d25af2936b6f4849172b312e4792d1d987bc34a2" + integrity sha1-0lryk2tvSEkXKzEuR5LR2Ye8NKI= + dependencies: + isarray "^1.0.0" + isobject "^2.0.0" + +lines-and-columns@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" + integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= + +load-json-file@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8" + integrity sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg= + dependencies: + graceful-fs "^4.1.2" + parse-json "^2.2.0" + pify "^2.0.0" + strip-bom "^3.0.0" + +loader-runner@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.1.0.tgz#f70bc0c29edbabdf2043e7ee73ccc3fe1c96b42d" + integrity sha512-oR4lB4WvwFoC70ocraKhn5nkKSs23t57h9udUgw8o0iH8hMXeEoRuUgfcvgUwAJ1ZpRqBvcou4N2SMvM1DwMrA== + +loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.2.3: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613" + integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA== + dependencies: + big.js "^5.2.2" + emojis-list "^3.0.0" + json5 "^1.0.1" + +loader-utils@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.0.tgz#e4cace5b816d425a166b5f097e10cd12b36064b0" + integrity sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ== + dependencies: + big.js "^5.2.2" + emojis-list "^3.0.0" + json5 "^2.1.2" + +locate-path@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" + integrity sha1-K1aLJl7slExtnA3pw9u7ygNUzY4= + dependencies: + p-locate "^2.0.0" + path-exists "^3.0.0" + +locate-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" + integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== + dependencies: + p-locate "^3.0.0" + path-exists "^3.0.0" + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +lodash-es@^4.17.14: + version "4.17.15" + resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.15.tgz#21bd96839354412f23d7a10340e5eac6ee455d78" + integrity sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ== + +lodash._reinterpolate@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" + integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0= + +lodash.camelcase@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" + integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY= + +lodash.difference@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.difference/-/lodash.difference-4.5.0.tgz#9ccb4e505d486b91651345772885a2df27fd017c" + integrity sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw= + +lodash.forown@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.forown/-/lodash.forown-4.4.0.tgz#85115cf04f73ef966eced52511d3893cc46683af" + integrity sha1-hRFc8E9z75ZuztUlEdOJPMRmg68= + +lodash.get@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" + integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk= + +lodash.groupby@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.groupby/-/lodash.groupby-4.6.0.tgz#0b08a1dcf68397c397855c3239783832df7403d1" + integrity sha1-Cwih3PaDl8OXhVwyOXg4Mt90A9E= + +lodash.memoize@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= + +lodash.sortby@^4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" + integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg= + +lodash.template@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.5.0.tgz#f976195cf3f347d0d5f52483569fe8031ccce8ab" + integrity sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A== + dependencies: + lodash._reinterpolate "^3.0.0" + lodash.templatesettings "^4.0.0" + +lodash.templatesettings@^4.0.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz#e481310f049d3cf6d47e912ad09313b154f0fb33" + integrity sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ== + dependencies: + lodash._reinterpolate "^3.0.0" + +lodash.uniq@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" + integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= + +lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.5: + version "4.17.20" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" + integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== + +log-symbols@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.0.0.tgz#69b3cc46d20f448eccdb75ea1fa733d9e821c920" + integrity sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA== + dependencies: + chalk "^4.0.0" + +loglevel@^1.6.8: + version "1.7.1" + resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.7.1.tgz#005fde2f5e6e47068f935ff28573e125ef72f197" + integrity sha512-Hesni4s5UkWkwCGJMQGAh71PaLUmKFM60dHvq0zi/vDhhrzuk+4GgNbTXJ12YYQJn6ZKBDNIjYcuQGKudvqrIw== + +longest-streak@^2.0.1: + version "2.0.4" + resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-2.0.4.tgz#b8599957da5b5dab64dee3fe316fa774597d90e4" + integrity sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg== + +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +lower-case@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.1.tgz#39eeb36e396115cc05e29422eaea9e692c9408c7" + integrity sha512-LiWgfDLLb1dwbFQZsSglpRj+1ctGnayXz3Uv0/WO8n558JycT5fg6zkNcnW0G68Nn0aEldTFeEfmjCfmqry/rQ== + dependencies: + tslib "^1.10.0" + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +make-dir@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" + integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA== + dependencies: + pify "^4.0.1" + semver "^5.6.0" + +make-dir@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== + dependencies: + semver "^6.0.0" + +make-error@^1.1.1: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + +map-cache@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" + integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= + +map-obj@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" + integrity sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0= + +map-obj@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-4.1.0.tgz#b91221b542734b9f14256c0132c897c5d7256fd5" + integrity sha512-glc9y00wgtwcDmp7GaE/0b0OnxpNJsVf3ael/An6Fe2Q51LLwN1er6sdomLRzz5h0+yMpiYLhWYF5R7HeqVd4g== + +map-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" + integrity sha1-7Nyo8TFE5mDxtb1B8S80edmN+48= + dependencies: + object-visit "^1.0.0" + +markdown-escapes@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/markdown-escapes/-/markdown-escapes-1.0.4.tgz#c95415ef451499d7602b91095f3c8e8975f78535" + integrity sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg== + +markdown-table@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-2.0.0.tgz#194a90ced26d31fe753d8b9434430214c011865b" + integrity sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A== + dependencies: + repeat-string "^1.0.0" + +mathml-tag-names@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3" + integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg== + +mdast-util-compact@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/mdast-util-compact/-/mdast-util-compact-2.0.1.tgz#cabc69a2f43103628326f35b1acf735d55c99490" + integrity sha512-7GlnT24gEwDrdAwEHrU4Vv5lLWrEer4KOkAiKT9nYstsTad7Oc1TwqT2zIMKRdZF7cTuaf+GA1E4Kv7jJh8mPA== + dependencies: + unist-util-visit "^2.0.0" + +mdn-data@2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.4.tgz#699b3c38ac6f1d728091a64650b65d388502fd5b" + integrity sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA== + +mdn-data@2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.6.tgz#852dc60fcaa5daa2e8cf6c9189c440ed3e042978" + integrity sha512-rQvjv71olwNHgiTbfPZFkJtjNMciWgswYeciZhtvWLO8bmX3TnhyA62I6sTWOyZssWHJJjY6/KiWwqQsWWsqOA== + +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= + +memory-fs@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" + integrity sha1-OpoguEYlI+RHz7x+i7gO1me/xVI= + dependencies: + errno "^0.1.3" + readable-stream "^2.0.1" + +memory-fs@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.5.0.tgz#324c01288b88652966d161db77838720845a8e3c" + integrity sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA== + dependencies: + errno "^0.1.3" + readable-stream "^2.0.1" + +meow@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/meow/-/meow-7.1.1.tgz#7c01595e3d337fcb0ec4e8eed1666ea95903d306" + integrity sha512-GWHvA5QOcS412WCo8vwKDlTelGLsCGBVevQB5Kva961rmNfun0PCbv5+xta2kUMFJyR8/oWnn7ddeKdosbAPbA== + dependencies: + "@types/minimist" "^1.2.0" + camelcase-keys "^6.2.2" + decamelize-keys "^1.1.0" + hard-rejection "^2.1.0" + minimist-options "4.1.0" + normalize-package-data "^2.5.0" + read-pkg-up "^7.0.1" + redent "^3.0.0" + trim-newlines "^3.0.0" + type-fest "^0.13.1" + yargs-parser "^18.1.3" + +merge-descriptors@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" + integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +merge2@^1.3.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +methods@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= + +micromatch@^3.1.10, micromatch@^3.1.4: + version "3.1.10" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" + integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + braces "^2.3.1" + define-property "^2.0.2" + extend-shallow "^3.0.2" + extglob "^2.0.4" + fragment-cache "^0.2.1" + kind-of "^6.0.2" + nanomatch "^1.2.9" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.2" + +micromatch@^4.0.0, micromatch@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" + integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q== + dependencies: + braces "^3.0.1" + picomatch "^2.0.5" + +mime-db@1.44.0: + version "1.44.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" + integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg== + +"mime-db@>= 1.43.0 < 2": + version "1.45.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.45.0.tgz#cceeda21ccd7c3a745eba2decd55d4b73e7879ea" + integrity sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w== + +mime-types@^2.1.27, mime-types@~2.1.17, mime-types@~2.1.24: + version "2.1.27" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f" + integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w== + dependencies: + mime-db "1.44.0" + +mime@1.6.0, mime@^1.4.1: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + +mime@^2.4.4: + version "2.4.6" + resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.6.tgz#e5b407c90db442f2beb5b162373d07b69affa4d1" + integrity sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA== + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +min-indent@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" + integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== + +mini-create-react-context@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/mini-create-react-context/-/mini-create-react-context-0.4.0.tgz#df60501c83151db69e28eac0ef08b4002efab040" + integrity sha512-b0TytUgFSbgFJGzJqXPKCFCBWigAjpjo+Fl7Vf7ZbKRDptszpppKxXH6DRXEABZ/gcEQczeb0iZ7JvL8e8jjCA== + dependencies: + "@babel/runtime" "^7.5.5" + tiny-warning "^1.0.3" + +mini-css-extract-plugin@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-1.1.1.tgz#4ab9948b7be38cf3a730cb4c9a881eb7f62baadb" + integrity sha512-pzlnOi/lMkwIkdb7zoRQvbkW18AFCQffouSBpxy+e3pnKTKMC5IuMVHYndexKZmacfsOZS2LXCe8gIgkrC+yqg== + dependencies: + loader-utils "^2.0.0" + schema-utils "^3.0.0" + webpack-sources "^1.1.0" + +mini-store@^3.0.1: + version "3.0.6" + resolved "https://registry.yarnpkg.com/mini-store/-/mini-store-3.0.6.tgz#44b86be5b2877271224ce0689b3a35a2dffb1ca9" + integrity sha512-YzffKHbYsMQGUWQRKdsearR79QsMzzJcDDmZKlJBqt5JNkqpyJHYlK6gP61O36X+sLf76sO9G6mhKBe83gIZIQ== + dependencies: + hoist-non-react-statics "^3.3.2" + shallowequal "^1.0.2" + +minimalistic-assert@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" + integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== + +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +minimist-options@4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-4.1.0.tgz#c0655713c53a8a2ebd77ffa247d342c40f010619" + integrity sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A== + dependencies: + arrify "^1.0.1" + is-plain-obj "^1.1.0" + kind-of "^6.0.3" + +minimist@^1.2.0, minimist@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" + integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== + +minipass-collect@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/minipass-collect/-/minipass-collect-1.0.2.tgz#22b813bf745dc6edba2576b940022ad6edc8c617" + integrity sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA== + dependencies: + minipass "^3.0.0" + +minipass-flush@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/minipass-flush/-/minipass-flush-1.0.5.tgz#82e7135d7e89a50ffe64610a787953c4c4cbb373" + integrity sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw== + dependencies: + minipass "^3.0.0" + +minipass-pipeline@^1.2.2: + version "1.2.4" + resolved "https://registry.yarnpkg.com/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz#68472f79711c084657c067c5c6ad93cddea8214c" + integrity sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A== + dependencies: + minipass "^3.0.0" + +minipass@^3.0.0, minipass@^3.1.1: + version "3.1.3" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.3.tgz#7d42ff1f39635482e15f9cdb53184deebd5815fd" + integrity sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg== + dependencies: + yallist "^4.0.0" + +minizlib@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" + integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== + dependencies: + minipass "^3.0.0" + yallist "^4.0.0" + +mixin-deep@^1.2.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" + integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA== + dependencies: + for-in "^1.0.2" + is-extendable "^1.0.1" + +mkdirp@^0.5.1, mkdirp@^0.5.5, mkdirp@~0.5.1: + version "0.5.5" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" + integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== + dependencies: + minimist "^1.2.5" + +mkdirp@^1.0.3, mkdirp@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + +mobx-react-lite@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/mobx-react-lite/-/mobx-react-lite-3.0.1.tgz#417f54a819d1e3e00c073077f29373399c95b005" + integrity sha512-Ue8uGgT5iOjMyNf5ptoFW7BTvyLIwggzIkoFpwORrqf73TPqu47iLpz/DNvaba3v40kSsEpp050qYroMNuA1xw== + +mobx@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/mobx/-/mobx-6.0.1.tgz#ec23520848527bf7834d7b4c0b54b2a8eb6e7c14" + integrity sha512-Pk6uJXZ34yqd661yRmS6z/9avm4FOGXpFpVjnEfiYYOsZXnAxv1fpYjxTCEZ9tuwk0Xe1qnUUlgm+rJtGe0YJA== + +moment@^2.24.0, moment@^2.25.3: + version "2.29.1" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3" + integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ== + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + +ms@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" + integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== + +ms@2.1.2, ms@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +multicast-dns-service-types@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz#899f11d9686e5e05cb91b35d5f0e63b773cfc901" + integrity sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE= + +multicast-dns@^6.0.1: + version "6.2.3" + resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-6.2.3.tgz#a0ec7bd9055c4282f790c3c82f4e28db3b31b229" + integrity sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g== + dependencies: + dns-packet "^1.3.1" + thunky "^1.0.2" + +multimatch@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/multimatch/-/multimatch-4.0.0.tgz#8c3c0f6e3e8449ada0af3dd29efb491a375191b3" + integrity sha512-lDmx79y1z6i7RNx0ZGCPq1bzJ6ZoDDKbvh7jxr9SJcWLkShMzXrHbYVpTdnhNM5MXpDUxCQ4DgqVttVXlBgiBQ== + dependencies: + "@types/minimatch" "^3.0.3" + array-differ "^3.0.0" + array-union "^2.1.0" + arrify "^2.0.1" + minimatch "^3.0.4" + +nan@^2.12.1: + version "2.14.2" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19" + integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ== + +nanoid@^3.1.12: + version "3.1.12" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.12.tgz#6f7736c62e8d39421601e4a0c77623a97ea69654" + integrity sha512-1qstj9z5+x491jfiC4Nelk+f8XBad7LN20PmyWINJEMRSf3wcAjAWysw1qaA8z6NSKe2sjq1hRSDpBH5paCb6A== + +nanomatch@^1.2.9: + version "1.2.13" + resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" + integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA== + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + define-property "^2.0.2" + extend-shallow "^3.0.2" + fragment-cache "^0.2.1" + is-windows "^1.0.2" + kind-of "^6.0.2" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +native-request@^1.0.5: + version "1.0.7" + resolved "https://registry.yarnpkg.com/native-request/-/native-request-1.0.7.tgz#ff742dc555b4c8f2f1c14b548639ba174e573856" + integrity sha512-9nRjinI9bmz+S7dgNtf4A70+/vPhnd+2krGpy4SUlADuOuSa24IDkNaZ+R/QT1wQ6S8jBdi6wE7fLekFZNfUpQ== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= + +negotiator@0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" + integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== + +neo-async@^2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + +nice-try@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" + integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== + +no-case@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.3.tgz#c21b434c1ffe48b39087e86cfb4d2582e9df18f8" + integrity sha512-ehY/mVQCf9BL0gKfsJBvFJen+1V//U+0HQMPrWct40ixE4jnv0bfvxDbWtAHL9EcaPEOJHVVYKoQn1TlZUB8Tw== + dependencies: + lower-case "^2.0.1" + tslib "^1.10.0" + +node-forge@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3" + integrity sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA== + +node-releases@^1.1.61: + version "1.1.64" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.64.tgz#71b4ae988e9b1dd7c1ffce58dd9e561752dfebc5" + integrity sha512-Iec8O9166/x2HRMJyLLLWkd0sFFLrFNy+Xf+JQfSQsdBJzPcHpNl3JQ9gD4j+aJxmCa25jNsIbM4bmACtSbkSg== + +normalize-package-data@^2.3.2, normalize-package-data@^2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" + integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== + dependencies: + hosted-git-info "^2.1.4" + resolve "^1.10.0" + semver "2 || 3 || 4 || 5" + validate-npm-package-license "^3.0.1" + +normalize-path@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" + integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk= + dependencies: + remove-trailing-separator "^1.0.1" + +normalize-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +normalize-range@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" + integrity sha1-LRDAa9/TEuqXd2laTShDlFa3WUI= + +normalize-selector@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/normalize-selector/-/normalize-selector-0.2.0.tgz#d0b145eb691189c63a78d201dc4fdb1293ef0c03" + integrity sha1-0LFF62kRicY6eNIB3E/bEpPvDAM= + +normalize-url@^3.0.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559" + integrity sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg== + +npm-run-path@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" + integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8= + dependencies: + path-key "^2.0.0" + +npm-run-path@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + +nth-check@^1.0.2, nth-check@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c" + integrity sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg== + dependencies: + boolbase "~1.0.0" + +num2fraction@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede" + integrity sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4= + +object-assign@4.x, object-assign@^4.0.1, object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= + +object-copy@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" + integrity sha1-fn2Fi3gb18mRpBupde04EnVOmYw= + dependencies: + copy-descriptor "^0.1.0" + define-property "^0.2.5" + kind-of "^3.0.3" + +object-hash@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.0.3.tgz#d12db044e03cd2ca3d77c0570d87225b02e1e6ea" + integrity sha512-JPKn0GMu+Fa3zt3Bmr66JhokJU5BaNBIh4ZeTlaCBzrBsOeXzwcKKAK1tbLiPKgvwmPXsDvvLHoWh5Bm7ofIYg== + +object-inspect@^1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.8.0.tgz#df807e5ecf53a609cc6bfe93eac3cc7be5b3a9d0" + integrity sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA== + +object-is@^1.0.1: + version "1.1.4" + resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.4.tgz#63d6c83c00a43f4cbc9434eb9757c8a5b8565068" + integrity sha512-1ZvAZ4wlF7IyPVOcE1Omikt7UpaFlOQq0HlSti+ZvDH3UiD2brwGMwDbyV43jao2bKJ+4+WdPJHSd7kgzKYVqg== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + +object-keys@^1.0.12, object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object-visit@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" + integrity sha1-95xEk68MU3e1n+OdOV5BBC3QRbs= + dependencies: + isobject "^3.0.0" + +object.assign@^4.1.0, object.assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.1.tgz#303867a666cdd41936ecdedfb1f8f3e32a478cdd" + integrity sha512-VT/cxmx5yaoHSOTSyrCygIDFco+RsibY2NM0a4RdEeY/4KgqezwFtK1yr3U67xYhqJSlASm2pKhLVzPj2lr4bA== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.18.0-next.0" + has-symbols "^1.0.1" + object-keys "^1.1.1" + +object.entries@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.2.tgz#bc73f00acb6b6bb16c203434b10f9a7e797d3add" + integrity sha512-BQdB9qKmb/HyNdMNWVr7O3+z5MUIx3aiegEIJqjMBbBf0YT9RRxTJSim4mzFqtyr7PDAHigq0N9dO0m0tRakQA== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.5" + has "^1.0.3" + +object.fromentries@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.2.tgz#4a09c9b9bb3843dd0f89acdb517a794d4f355ac9" + integrity sha512-r3ZiBH7MQppDJVLx6fhD618GKNG40CZYH9wgwdhKxBDDbQgjeWGGd4AtkZad84d291YxvWe7bJGuE65Anh0dxQ== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + function-bind "^1.1.1" + has "^1.0.3" + +object.getownpropertydescriptors@^2.0.3, object.getownpropertydescriptors@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz#369bf1f9592d8ab89d712dced5cb81c7c5352649" + integrity sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + +object.pick@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" + integrity sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c= + dependencies: + isobject "^3.0.1" + +object.values@^1.1.0, object.values@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.1.tgz#68a99ecde356b7e9295a3c5e0ce31dc8c953de5e" + integrity sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + function-bind "^1.1.1" + has "^1.0.3" + +obuf@^1.0.0, obuf@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" + integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== + +omit.js@^2.0.0, omit.js@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/omit.js/-/omit.js-2.0.2.tgz#dd9b8436fab947a5f3ff214cb2538631e313ec2f" + integrity sha512-hJmu9D+bNB40YpL9jYebQl4lsTW6yEHRTroJzNLqQJYHm7c+NQnJGfZmIWh8S3q3KoaxV1aLhV6B3+0N0/kyJg== + +on-finished@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" + integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= + dependencies: + ee-first "1.1.1" + +on-headers@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" + integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== + +once@^1.3.0, once@^1.3.1, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +onetime@^5.1.0: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +opencollective-postinstall@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz#7a0fff978f6dbfa4d006238fbac98ed4198c3259" + integrity sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q== + +opn@^5.5.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/opn/-/opn-5.5.0.tgz#fc7164fab56d235904c51c3b27da6758ca3b9bfc" + integrity sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA== + dependencies: + is-wsl "^1.1.0" + +optimize-css-assets-webpack-plugin@^5.0.4: + version "5.0.4" + resolved "https://registry.yarnpkg.com/optimize-css-assets-webpack-plugin/-/optimize-css-assets-webpack-plugin-5.0.4.tgz#85883c6528aaa02e30bbad9908c92926bb52dc90" + integrity sha512-wqd6FdI2a5/FdoiCNNkEvLeA//lHHfG24Ln2Xm2qqdIk4aOlsR18jwpyOihqQ8849W3qu2DX8fOYxpvTMj+93A== + dependencies: + cssnano "^4.1.10" + last-call-webpack-plugin "^3.0.0" + +optionator@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" + integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.3" + +original@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/original/-/original-1.0.2.tgz#e442a61cffe1c5fd20a65f3261c26663b303f25f" + integrity sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg== + dependencies: + url-parse "^1.4.3" + +p-finally@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" + integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= + +p-limit@^1.1.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" + integrity sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q== + dependencies: + p-try "^1.0.0" + +p-limit@^2.0.0, p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-limit@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.0.2.tgz#1664e010af3cadc681baafd3e2a437be7b0fb5fe" + integrity sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg== + dependencies: + p-try "^2.0.0" + +p-locate@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" + integrity sha1-IKAQOyIqcMj9OcwuWAaA893l7EM= + dependencies: + p-limit "^1.1.0" + +p-locate@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" + integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== + dependencies: + p-limit "^2.0.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + +p-map@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175" + integrity sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw== + +p-map@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" + integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== + dependencies: + aggregate-error "^3.0.0" + +p-retry@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-3.0.1.tgz#316b4c8893e2c8dc1cfa891f406c4b422bebf328" + integrity sha512-XE6G4+YTTkT2a0UWb2kjZe8xNwf8bIbnqpc/IS/idOBVhyves0mK5OJgeocjx7q5pvX/6m23xuzVPYT1uGM73w== + dependencies: + retry "^0.12.0" + +p-try@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" + integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M= + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +param-case@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/param-case/-/param-case-3.0.3.tgz#4be41f8399eff621c56eebb829a5e451d9801238" + integrity sha512-VWBVyimc1+QrzappRs7waeN2YmoZFCGXWASRYX1/rGHtXqEcrGEIDm+jqIwFa2fRXNgQEwrxaYuIrX0WcAguTA== + dependencies: + dot-case "^3.0.3" + tslib "^1.10.0" + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parse-entities@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-2.0.0.tgz#53c6eb5b9314a1f4ec99fa0fdf7ce01ecda0cbe8" + integrity sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ== + dependencies: + character-entities "^1.0.0" + character-entities-legacy "^1.0.0" + character-reference-invalid "^1.0.0" + is-alphanumerical "^1.0.0" + is-decimal "^1.0.0" + is-hexadecimal "^1.0.0" + +parse-json@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" + integrity sha1-9ID0BDTvgHQfhGkJn43qGPVaTck= + dependencies: + error-ex "^1.2.0" + +parse-json@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" + integrity sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA= + dependencies: + error-ex "^1.3.1" + json-parse-better-errors "^1.0.1" + +parse-json@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.1.0.tgz#f96088cdf24a8faa9aea9a009f2d9d942c999646" + integrity sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + +parseurl@~1.3.2, parseurl@~1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + +pascal-case@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/pascal-case/-/pascal-case-3.1.1.tgz#5ac1975133ed619281e88920973d2cd1f279de5f" + integrity sha512-XIeHKqIrsquVTQL2crjq3NfJUxmdLasn3TYOU0VBM+UX2a6ztAWBlJQBePLGY7VHW8+2dRadeIPK5+KImwTxQA== + dependencies: + no-case "^3.0.3" + tslib "^1.10.0" + +pascalcase@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" + integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= + +path-dirname@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" + integrity sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA= + +path-exists@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" + integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +path-is-inside@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" + integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM= + +path-key@^2.0.0, path-key@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" + integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= + +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" + integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== + +path-to-regexp@0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" + integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= + +path-to-regexp@^1.7.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a" + integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA== + dependencies: + isarray "0.0.1" + +path-type@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73" + integrity sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM= + dependencies: + pify "^2.0.0" + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +performance-now@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" + integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= + +picomatch@^2.0.5, picomatch@^2.2.1: + version "2.2.2" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" + integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== + +pify@^2.0.0, pify@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" + integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw= + +pify@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" + integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== + +pinkie-promise@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" + integrity sha1-ITXW36ejWMBprJsXh3YogihFD/o= + dependencies: + pinkie "^2.0.0" + +pinkie@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" + integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA= + +pkg-dir@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b" + integrity sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s= + dependencies: + find-up "^2.1.0" + +pkg-dir@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3" + integrity sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw== + dependencies: + find-up "^3.0.0" + +pkg-dir@^4.1.0, pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + +pkg-dir@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-5.0.0.tgz#a02d6aebe6ba133a928f74aec20bafdfe6b8e760" + integrity sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA== + dependencies: + find-up "^5.0.0" + +please-upgrade-node@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz#aeddd3f994c933e4ad98b99d9a556efa0e2fe942" + integrity sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg== + dependencies: + semver-compare "^1.0.0" + +portfinder@^1.0.26: + version "1.0.28" + resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.28.tgz#67c4622852bd5374dd1dd900f779f53462fac778" + integrity sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA== + dependencies: + async "^2.6.2" + debug "^3.1.1" + mkdirp "^0.5.5" + +posix-character-classes@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" + integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= + +postcss-attribute-case-insensitive@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-4.0.2.tgz#d93e46b504589e94ac7277b0463226c68041a880" + integrity sha512-clkFxk/9pcdb4Vkn0hAHq3YnxBQ2p0CGD1dy24jN+reBck+EWxMbxSUqN4Yj7t0w8csl87K6p0gxBe1utkJsYA== + dependencies: + postcss "^7.0.2" + postcss-selector-parser "^6.0.2" + +postcss-calc@^7.0.1, postcss-calc@^7.0.5: + version "7.0.5" + resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-7.0.5.tgz#f8a6e99f12e619c2ebc23cf6c486fdc15860933e" + integrity sha512-1tKHutbGtLtEZF6PT4JSihCHfIVldU72mZ8SdZHIYriIZ9fh9k9aWSppaT8rHsyI3dX+KSR+W+Ix9BMY3AODrg== + dependencies: + postcss "^7.0.27" + postcss-selector-parser "^6.0.2" + postcss-value-parser "^4.0.2" + +postcss-color-functional-notation@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/postcss-color-functional-notation/-/postcss-color-functional-notation-2.0.1.tgz#5efd37a88fbabeb00a2966d1e53d98ced93f74e0" + integrity sha512-ZBARCypjEDofW4P6IdPVTLhDNXPRn8T2s1zHbZidW6rPaaZvcnCS2soYFIQJrMZSxiePJ2XIYTlcb2ztr/eT2g== + dependencies: + postcss "^7.0.2" + postcss-values-parser "^2.0.0" + +postcss-color-gray@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/postcss-color-gray/-/postcss-color-gray-5.0.0.tgz#532a31eb909f8da898ceffe296fdc1f864be8547" + integrity sha512-q6BuRnAGKM/ZRpfDascZlIZPjvwsRye7UDNalqVz3s7GDxMtqPY6+Q871liNxsonUw8oC61OG+PSaysYpl1bnw== + dependencies: + "@csstools/convert-colors" "^1.4.0" + postcss "^7.0.5" + postcss-values-parser "^2.0.0" + +postcss-color-hex-alpha@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/postcss-color-hex-alpha/-/postcss-color-hex-alpha-5.0.3.tgz#a8d9ca4c39d497c9661e374b9c51899ef0f87388" + integrity sha512-PF4GDel8q3kkreVXKLAGNpHKilXsZ6xuu+mOQMHWHLPNyjiUBOr75sp5ZKJfmv1MCus5/DWUGcK9hm6qHEnXYw== + dependencies: + postcss "^7.0.14" + postcss-values-parser "^2.0.1" + +postcss-color-mod-function@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/postcss-color-mod-function/-/postcss-color-mod-function-3.0.3.tgz#816ba145ac11cc3cb6baa905a75a49f903e4d31d" + integrity sha512-YP4VG+xufxaVtzV6ZmhEtc+/aTXH3d0JLpnYfxqTvwZPbJhWqp8bSY3nfNzNRFLgB4XSaBA82OE4VjOOKpCdVQ== + dependencies: + "@csstools/convert-colors" "^1.4.0" + postcss "^7.0.2" + postcss-values-parser "^2.0.0" + +postcss-color-rebeccapurple@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-4.0.1.tgz#c7a89be872bb74e45b1e3022bfe5748823e6de77" + integrity sha512-aAe3OhkS6qJXBbqzvZth2Au4V3KieR5sRQ4ptb2b2O8wgvB3SJBsdG+jsn2BZbbwekDG8nTfcCNKcSfe/lEy8g== + dependencies: + postcss "^7.0.2" + postcss-values-parser "^2.0.0" + +postcss-colormin@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-4.0.3.tgz#ae060bce93ed794ac71264f08132d550956bd381" + integrity sha512-WyQFAdDZpExQh32j0U0feWisZ0dmOtPl44qYmJKkq9xFWY3p+4qnRzCHeNrkeRhwPHz9bQ3mo0/yVkaply0MNw== + dependencies: + browserslist "^4.0.0" + color "^3.0.0" + has "^1.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-convert-values@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-4.0.1.tgz#ca3813ed4da0f812f9d43703584e449ebe189a7f" + integrity sha512-Kisdo1y77KUC0Jmn0OXU/COOJbzM8cImvw1ZFsBgBgMgb1iL23Zs/LXRe3r+EZqM3vGYKdQ2YJVQ5VkJI+zEJQ== + dependencies: + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-css-variables@^0.17.0: + version "0.17.0" + resolved "https://registry.yarnpkg.com/postcss-css-variables/-/postcss-css-variables-0.17.0.tgz#56cba1d9f0360609136cfbfda8bbd2c1ed2e4082" + integrity sha512-/ZpFnJgksNOrQA72b3DKhExYh+0e2P5nEc3aPZ62G7JLmdDjWRFv3k/q4LxV7uzXFnmvkhXRbdVIiH5tKgfFNA== + dependencies: + balanced-match "^1.0.0" + escape-string-regexp "^1.0.3" + extend "^3.0.1" + postcss "^6.0.8" + +postcss-custom-media@^7.0.8: + version "7.0.8" + resolved "https://registry.yarnpkg.com/postcss-custom-media/-/postcss-custom-media-7.0.8.tgz#fffd13ffeffad73621be5f387076a28b00294e0c" + integrity sha512-c9s5iX0Ge15o00HKbuRuTqNndsJUbaXdiNsksnVH8H4gdc+zbLzr/UasOwNG6CTDpLFekVY4672eWdiiWu2GUg== + dependencies: + postcss "^7.0.14" + +postcss-custom-properties@^8.0.11: + version "8.0.11" + resolved "https://registry.yarnpkg.com/postcss-custom-properties/-/postcss-custom-properties-8.0.11.tgz#2d61772d6e92f22f5e0d52602df8fae46fa30d97" + integrity sha512-nm+o0eLdYqdnJ5abAJeXp4CEU1c1k+eB2yMCvhgzsds/e0umabFrN6HoTy/8Q4K5ilxERdl/JD1LO5ANoYBeMA== + dependencies: + postcss "^7.0.17" + postcss-values-parser "^2.0.1" + +postcss-custom-selectors@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/postcss-custom-selectors/-/postcss-custom-selectors-5.1.2.tgz#64858c6eb2ecff2fb41d0b28c9dd7b3db4de7fba" + integrity sha512-DSGDhqinCqXqlS4R7KGxL1OSycd1lydugJ1ky4iRXPHdBRiozyMHrdu0H3o7qNOCiZwySZTUI5MV0T8QhCLu+w== + dependencies: + postcss "^7.0.2" + postcss-selector-parser "^5.0.0-rc.3" + +postcss-dir-pseudo-class@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-5.0.0.tgz#6e3a4177d0edb3abcc85fdb6fbb1c26dabaeaba2" + integrity sha512-3pm4oq8HYWMZePJY+5ANriPs3P07q+LW6FAdTlkFH2XqDdP4HeeJYMOzn0HYLhRSjBO3fhiqSwwU9xEULSrPgw== + dependencies: + postcss "^7.0.2" + postcss-selector-parser "^5.0.0-rc.3" + +postcss-discard-comments@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz#1fbabd2c246bff6aaad7997b2b0918f4d7af4033" + integrity sha512-RJutN259iuRf3IW7GZyLM5Sw4GLTOH8FmsXBnv8Ab/Tc2k4SR4qbV4DNbyyY4+Sjo362SyDmW2DQ7lBSChrpkg== + dependencies: + postcss "^7.0.0" + +postcss-discard-duplicates@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-4.0.2.tgz#3fe133cd3c82282e550fc9b239176a9207b784eb" + integrity sha512-ZNQfR1gPNAiXZhgENFfEglF93pciw0WxMkJeVmw8eF+JZBbMD7jp6C67GqJAXVZP2BWbOztKfbsdmMp/k8c6oQ== + dependencies: + postcss "^7.0.0" + +postcss-discard-empty@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-4.0.1.tgz#c8c951e9f73ed9428019458444a02ad90bb9f765" + integrity sha512-B9miTzbznhDjTfjvipfHoqbWKwd0Mj+/fL5s1QOz06wufguil+Xheo4XpOnc4NqKYBCNqqEzgPv2aPBIJLox0w== + dependencies: + postcss "^7.0.0" + +postcss-discard-overridden@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-4.0.1.tgz#652aef8a96726f029f5e3e00146ee7a4e755ff57" + integrity sha512-IYY2bEDD7g1XM1IDEsUT4//iEYCxAmP5oDSFMVU/JVvT7gh+l4fmjciLqGgwjdWpQIdb0Che2VX00QObS5+cTg== + dependencies: + postcss "^7.0.0" + +postcss-double-position-gradients@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/postcss-double-position-gradients/-/postcss-double-position-gradients-1.0.0.tgz#fc927d52fddc896cb3a2812ebc5df147e110522e" + integrity sha512-G+nV8EnQq25fOI8CH/B6krEohGWnF5+3A6H/+JEpOncu5dCnkS1QQ6+ct3Jkaepw1NGVqqOZH6lqrm244mCftA== + dependencies: + postcss "^7.0.5" + postcss-values-parser "^2.0.0" + +postcss-env-function@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/postcss-env-function/-/postcss-env-function-2.0.2.tgz#0f3e3d3c57f094a92c2baf4b6241f0b0da5365d7" + integrity sha512-rwac4BuZlITeUbiBq60h/xbLzXY43qOsIErngWa4l7Mt+RaSkT7QBjXVGTcBHupykkblHMDrBFh30zchYPaOUw== + dependencies: + postcss "^7.0.2" + postcss-values-parser "^2.0.0" + +postcss-focus-visible@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/postcss-focus-visible/-/postcss-focus-visible-4.0.0.tgz#477d107113ade6024b14128317ade2bd1e17046e" + integrity sha512-Z5CkWBw0+idJHSV6+Bgf2peDOFf/x4o+vX/pwcNYrWpXFrSfTkQ3JQ1ojrq9yS+upnAlNRHeg8uEwFTgorjI8g== + dependencies: + postcss "^7.0.2" + +postcss-focus-within@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-focus-within/-/postcss-focus-within-3.0.0.tgz#763b8788596cee9b874c999201cdde80659ef680" + integrity sha512-W0APui8jQeBKbCGZudW37EeMCjDeVxKgiYfIIEo8Bdh5SpB9sxds/Iq8SEuzS0Q4YFOlG7EPFulbbxujpkrV2w== + dependencies: + postcss "^7.0.2" + +postcss-font-variant@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/postcss-font-variant/-/postcss-font-variant-4.0.0.tgz#71dd3c6c10a0d846c5eda07803439617bbbabacc" + integrity sha512-M8BFYKOvCrI2aITzDad7kWuXXTm0YhGdP9Q8HanmN4EF1Hmcgs1KK5rSHylt/lUJe8yLxiSwWAHdScoEiIxztg== + dependencies: + postcss "^7.0.2" + +postcss-gap-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/postcss-gap-properties/-/postcss-gap-properties-2.0.0.tgz#431c192ab3ed96a3c3d09f2ff615960f902c1715" + integrity sha512-QZSqDaMgXCHuHTEzMsS2KfVDOq7ZFiknSpkrPJY6jmxbugUPTuSzs/vuE5I3zv0WAS+3vhrlqhijiprnuQfzmg== + dependencies: + postcss "^7.0.2" + +postcss-html@^0.36.0: + version "0.36.0" + resolved "https://registry.yarnpkg.com/postcss-html/-/postcss-html-0.36.0.tgz#b40913f94eaacc2453fd30a1327ad6ee1f88b204" + integrity sha512-HeiOxGcuwID0AFsNAL0ox3mW6MHH5cstWN1Z3Y+n6H+g12ih7LHdYxWwEA/QmrebctLjo79xz9ouK3MroHwOJw== + dependencies: + htmlparser2 "^3.10.0" + +postcss-image-set-function@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/postcss-image-set-function/-/postcss-image-set-function-3.0.1.tgz#28920a2f29945bed4c3198d7df6496d410d3f288" + integrity sha512-oPTcFFip5LZy8Y/whto91L9xdRHCWEMs3e1MdJxhgt4jy2WYXfhkng59fH5qLXSCPN8k4n94p1Czrfe5IOkKUw== + dependencies: + postcss "^7.0.2" + postcss-values-parser "^2.0.0" + +postcss-import@^13.0.0: + version "13.0.0" + resolved "https://registry.yarnpkg.com/postcss-import/-/postcss-import-13.0.0.tgz#d6960cd9e3de5464743b04dd8cd9d870662f8b8c" + integrity sha512-LPUbm3ytpYopwQQjqgUH4S3EM/Gb9QsaSPP/5vnoi+oKVy3/mIk2sc0Paqw7RL57GpScm9MdIMUypw2znWiBpg== + dependencies: + postcss-value-parser "^4.0.0" + read-cache "^1.0.0" + resolve "^1.1.7" + +postcss-initial@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/postcss-initial/-/postcss-initial-3.0.2.tgz#f018563694b3c16ae8eaabe3c585ac6319637b2d" + integrity sha512-ugA2wKonC0xeNHgirR4D3VWHs2JcU08WAi1KFLVcnb7IN89phID6Qtg2RIctWbnvp1TM2BOmDtX8GGLCKdR8YA== + dependencies: + lodash.template "^4.5.0" + postcss "^7.0.2" + +postcss-inline-svg@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/postcss-inline-svg/-/postcss-inline-svg-4.1.0.tgz#54e9199632242cc181af5b55f0f0691ad1020f95" + integrity sha512-0pYBJyoQ9/sJViYRc1cNOOTM7DYh0/rmASB0TBeRmWkG8YFK2tmgdkfjHkbRma1iFtBFKFHZFsHwRTDZTMKzSQ== + dependencies: + css-select "^2.0.2" + dom-serializer "^0.1.1" + htmlparser2 "^3.10.1" + postcss "^7.0.17" + postcss-value-parser "^4.0.0" + +postcss-js@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/postcss-js/-/postcss-js-3.0.1.tgz#e467efdce80ca02e072c60b42e0b35ad2f950a94" + integrity sha512-m1DgECmEbOK9JhGkdctaP9ZRVheJuEnkk2eb/d3K+5uN10C3S004Ng6Hat4Aha7PsLt824x0xwrT7rVwGRVLHg== + dependencies: + camelcase-css "^2.0.1" + postcss "^8.1.0" + +postcss-lab-function@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/postcss-lab-function/-/postcss-lab-function-2.0.1.tgz#bb51a6856cd12289ab4ae20db1e3821ef13d7d2e" + integrity sha512-whLy1IeZKY+3fYdqQFuDBf8Auw+qFuVnChWjmxm/UhHWqNHZx+B99EwxTvGYmUBqe3Fjxs4L1BoZTJmPu6usVg== + dependencies: + "@csstools/convert-colors" "^1.4.0" + postcss "^7.0.2" + postcss-values-parser "^2.0.0" + +postcss-less@^3.1.4: + version "3.1.4" + resolved "https://registry.yarnpkg.com/postcss-less/-/postcss-less-3.1.4.tgz#369f58642b5928ef898ffbc1a6e93c958304c5ad" + integrity sha512-7TvleQWNM2QLcHqvudt3VYjULVB49uiW6XzEUFmvwHzvsOEF5MwBrIXZDJQvJNFGjJQTzSzZnDoCJ8h/ljyGXA== + dependencies: + postcss "^7.0.14" + +postcss-loader@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-4.0.4.tgz#b2d005b52e008a44991cf8123bee207e635eb53e" + integrity sha512-pntA9zIR14drQo84yGTjQJg1m7T0DkXR4vXYHBngiRZdJtEeCrojL6lOpqUanMzG375lIJbT4Yug85zC/AJWGw== + dependencies: + cosmiconfig "^7.0.0" + klona "^2.0.4" + loader-utils "^2.0.0" + schema-utils "^3.0.0" + semver "^7.3.2" + +postcss-logical@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-logical/-/postcss-logical-3.0.0.tgz#2495d0f8b82e9f262725f75f9401b34e7b45d5b5" + integrity sha512-1SUKdJc2vuMOmeItqGuNaC+N8MzBWFWEkAnRnLpFYj1tGGa7NqyVBujfRtgNa2gXR+6RkGUiB2O5Vmh7E2RmiA== + dependencies: + postcss "^7.0.2" + +postcss-media-minmax@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/postcss-media-minmax/-/postcss-media-minmax-4.0.0.tgz#b75bb6cbc217c8ac49433e12f22048814a4f5ed5" + integrity sha512-fo9moya6qyxsjbFAYl97qKO9gyre3qvbMnkOZeZwlsW6XYFsvs2DMGDlchVLfAd8LHPZDxivu/+qW2SMQeTHBw== + dependencies: + postcss "^7.0.2" + +postcss-media-query-parser@^0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz#27b39c6f4d94f81b1a73b8f76351c609e5cef244" + integrity sha1-J7Ocb02U+Bsac7j3Y1HGCeXO8kQ= + +postcss-merge-longhand@^4.0.11: + version "4.0.11" + resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-4.0.11.tgz#62f49a13e4a0ee04e7b98f42bb16062ca2549e24" + integrity sha512-alx/zmoeXvJjp7L4mxEMjh8lxVlDFX1gqWHzaaQewwMZiVhLo42TEClKaeHbRf6J7j82ZOdTJ808RtN0ZOZwvw== + dependencies: + css-color-names "0.0.4" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + stylehacks "^4.0.0" + +postcss-merge-rules@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-4.0.3.tgz#362bea4ff5a1f98e4075a713c6cb25aefef9a650" + integrity sha512-U7e3r1SbvYzO0Jr3UT/zKBVgYYyhAz0aitvGIYOYK5CPmkNih+WDSsS5tvPrJ8YMQYlEMvsZIiqmn7HdFUaeEQ== + dependencies: + browserslist "^4.0.0" + caniuse-api "^3.0.0" + cssnano-util-same-parent "^4.0.0" + postcss "^7.0.0" + postcss-selector-parser "^3.0.0" + vendors "^1.0.0" + +postcss-minify-font-values@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-4.0.2.tgz#cd4c344cce474343fac5d82206ab2cbcb8afd5a6" + integrity sha512-j85oO6OnRU9zPf04+PZv1LYIYOprWm6IA6zkXkrJXyRveDEuQggG6tvoy8ir8ZwjLxLuGfNkCZEQG7zan+Hbtg== + dependencies: + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-minify-gradients@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-4.0.2.tgz#93b29c2ff5099c535eecda56c4aa6e665a663471" + integrity sha512-qKPfwlONdcf/AndP1U8SJ/uzIJtowHlMaSioKzebAXSG4iJthlWC9iSWznQcX4f66gIWX44RSA841HTHj3wK+Q== + dependencies: + cssnano-util-get-arguments "^4.0.0" + is-color-stop "^1.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-minify-params@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-4.0.2.tgz#6b9cef030c11e35261f95f618c90036d680db874" + integrity sha512-G7eWyzEx0xL4/wiBBJxJOz48zAKV2WG3iZOqVhPet/9geefm/Px5uo1fzlHu+DOjT+m0Mmiz3jkQzVHe6wxAWg== + dependencies: + alphanum-sort "^1.0.0" + browserslist "^4.0.0" + cssnano-util-get-arguments "^4.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + uniqs "^2.0.0" + +postcss-minify-selectors@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-4.0.2.tgz#e2e5eb40bfee500d0cd9243500f5f8ea4262fbd8" + integrity sha512-D5S1iViljXBj9kflQo4YutWnJmwm8VvIsU1GeXJGiG9j8CIg9zs4voPMdQDUmIxetUOh60VilsNzCiAFTOqu3g== + dependencies: + alphanum-sort "^1.0.0" + has "^1.0.0" + postcss "^7.0.0" + postcss-selector-parser "^3.0.0" + +postcss-mixins@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/postcss-mixins/-/postcss-mixins-7.0.1.tgz#f46a18abef1eea727b4f0b6198aada1cae20cafc" + integrity sha512-9VLSce+dkPYmeD7/MmPh5HTQch1HEO4avtkrT8x0t8F8TOd+gfUMkJXS82Kx1qV4c2pkMdhq4M5aEnZHYRJWiA== + dependencies: + globby "^11.0.1" + postcss-js "^3.0.1" + postcss-simple-vars "^6.0.1" + sugarss "^3.0.1" + +postcss-modules-extract-imports@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz#818719a1ae1da325f9832446b01136eeb493cd7e" + integrity sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ== + dependencies: + postcss "^7.0.5" + +postcss-modules-extract-imports@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz#cda1f047c0ae80c97dbe28c3e76a43b88025741d" + integrity sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw== + +postcss-modules-local-by-default@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.3.tgz#bb14e0cc78279d504dbdcbfd7e0ca28993ffbbb0" + integrity sha512-e3xDq+LotiGesympRlKNgaJ0PCzoUIdpH0dj47iWAui/kyTgh3CiAr1qP54uodmJhl6p9rN6BoNcdEDVJx9RDw== + dependencies: + icss-utils "^4.1.1" + postcss "^7.0.32" + postcss-selector-parser "^6.0.2" + postcss-value-parser "^4.1.0" + +postcss-modules-local-by-default@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz#ebbb54fae1598eecfdf691a02b3ff3b390a5a51c" + integrity sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ== + dependencies: + icss-utils "^5.0.0" + postcss-selector-parser "^6.0.2" + postcss-value-parser "^4.1.0" + +postcss-modules-scope@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-2.2.0.tgz#385cae013cc7743f5a7d7602d1073a89eaae62ee" + integrity sha512-YyEgsTMRpNd+HmyC7H/mh3y+MeFWevy7V1evVhJWewmMbjDHIbZbOXICC2y+m1xI1UVfIT1HMW/O04Hxyu9oXQ== + dependencies: + postcss "^7.0.6" + postcss-selector-parser "^6.0.0" + +postcss-modules-scope@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz#9ef3151456d3bbfa120ca44898dfca6f2fa01f06" + integrity sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg== + dependencies: + postcss-selector-parser "^6.0.4" + +postcss-modules-values@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-3.0.0.tgz#5b5000d6ebae29b4255301b4a3a54574423e7f10" + integrity sha512-1//E5jCBrZ9DmRX+zCtmQtRSV6PV42Ix7Bzj9GbwJceduuf7IqP8MgeTXuRDHOWj2m0VzZD5+roFWDuU8RQjcg== + dependencies: + icss-utils "^4.0.0" + postcss "^7.0.6" + +postcss-modules-values@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz#d7c5e7e68c3bb3c9b27cbf48ca0bb3ffb4602c9c" + integrity sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ== + dependencies: + icss-utils "^5.0.0" + +postcss-modules@^3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/postcss-modules/-/postcss-modules-3.2.2.tgz#ee390de0f9f18e761e1778dfb9be26685c02c51f" + integrity sha512-JQ8IAqHELxC0N6tyCg2UF40pACY5oiL6UpiqqcIFRWqgDYO8B0jnxzoQ0EOpPrWXvcpu6BSbQU/3vSiq7w8Nhw== + dependencies: + generic-names "^2.0.1" + icss-replace-symbols "^1.1.0" + lodash.camelcase "^4.3.0" + postcss "^7.0.32" + postcss-modules-extract-imports "^2.0.0" + postcss-modules-local-by-default "^3.0.2" + postcss-modules-scope "^2.2.0" + postcss-modules-values "^3.0.0" + string-hash "^1.1.1" + +postcss-nested@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-5.0.1.tgz#e7a77f7a806a09c8de0f2c163d8e3d09f00f3139" + integrity sha512-ZHNSAoHrMtbEzjq+Qs4R0gHijpXc6F1YUv4TGmGaz7rtfMvVJBbu5hMOH+CrhEaljQpEmx5N/P8i1pXTkbVAmg== + dependencies: + postcss-selector-parser "^6.0.4" + +postcss-nesting@^7.0.0: + version "7.0.1" + resolved "https://registry.yarnpkg.com/postcss-nesting/-/postcss-nesting-7.0.1.tgz#b50ad7b7f0173e5b5e3880c3501344703e04c052" + integrity sha512-FrorPb0H3nuVq0Sff7W2rnc3SmIcruVC6YwpcS+k687VxyxO33iE1amna7wHuRVzM8vfiYofXSBHNAZ3QhLvYg== + dependencies: + postcss "^7.0.2" + +postcss-normalize-charset@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-4.0.1.tgz#8b35add3aee83a136b0471e0d59be58a50285dd4" + integrity sha512-gMXCrrlWh6G27U0hF3vNvR3w8I1s2wOBILvA87iNXaPvSNo5uZAMYsZG7XjCUf1eVxuPfyL4TJ7++SGZLc9A3g== + dependencies: + postcss "^7.0.0" + +postcss-normalize-display-values@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.2.tgz#0dbe04a4ce9063d4667ed2be476bb830c825935a" + integrity sha512-3F2jcsaMW7+VtRMAqf/3m4cPFhPD3EFRgNs18u+k3lTJJlVe7d0YPO+bnwqo2xg8YiRpDXJI2u8A0wqJxMsQuQ== + dependencies: + cssnano-util-get-match "^4.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-normalize-positions@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-4.0.2.tgz#05f757f84f260437378368a91f8932d4b102917f" + integrity sha512-Dlf3/9AxpxE+NF1fJxYDeggi5WwV35MXGFnnoccP/9qDtFrTArZ0D0R+iKcg5WsUd8nUYMIl8yXDCtcrT8JrdA== + dependencies: + cssnano-util-get-arguments "^4.0.0" + has "^1.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-normalize-repeat-style@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-4.0.2.tgz#c4ebbc289f3991a028d44751cbdd11918b17910c" + integrity sha512-qvigdYYMpSuoFs3Is/f5nHdRLJN/ITA7huIoCyqqENJe9PvPmLhNLMu7QTjPdtnVf6OcYYO5SHonx4+fbJE1+Q== + dependencies: + cssnano-util-get-arguments "^4.0.0" + cssnano-util-get-match "^4.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-normalize-string@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-4.0.2.tgz#cd44c40ab07a0c7a36dc5e99aace1eca4ec2690c" + integrity sha512-RrERod97Dnwqq49WNz8qo66ps0swYZDSb6rM57kN2J+aoyEAJfZ6bMx0sx/F9TIEX0xthPGCmeyiam/jXif0eA== + dependencies: + has "^1.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-normalize-timing-functions@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-4.0.2.tgz#8e009ca2a3949cdaf8ad23e6b6ab99cb5e7d28d9" + integrity sha512-acwJY95edP762e++00Ehq9L4sZCEcOPyaHwoaFOhIwWCDfik6YvqsYNxckee65JHLKzuNSSmAdxwD2Cud1Z54A== + dependencies: + cssnano-util-get-match "^4.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-normalize-unicode@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-4.0.1.tgz#841bd48fdcf3019ad4baa7493a3d363b52ae1cfb" + integrity sha512-od18Uq2wCYn+vZ/qCOeutvHjB5jm57ToxRaMeNuf0nWVHaP9Hua56QyMF6fs/4FSUnVIw0CBPsU0K4LnBPwYwg== + dependencies: + browserslist "^4.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-normalize-url@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-4.0.1.tgz#10e437f86bc7c7e58f7b9652ed878daaa95faae1" + integrity sha512-p5oVaF4+IHwu7VpMan/SSpmpYxcJMtkGppYf0VbdH5B6hN8YNmVyJLuY9FmLQTzY3fag5ESUUHDqM+heid0UVA== + dependencies: + is-absolute-url "^2.0.0" + normalize-url "^3.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-normalize-whitespace@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-4.0.2.tgz#bf1d4070fe4fcea87d1348e825d8cc0c5faa7d82" + integrity sha512-tO8QIgrsI3p95r8fyqKV+ufKlSHh9hMJqACqbv2XknufqEDhDvbguXGBBqxw9nsQoXWf0qOqppziKJKHMD4GtA== + dependencies: + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-ordered-values@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-4.1.2.tgz#0cf75c820ec7d5c4d280189559e0b571ebac0eee" + integrity sha512-2fCObh5UanxvSxeXrtLtlwVThBvHn6MQcu4ksNT2tsaV2Fg76R2CV98W7wNSlX+5/pFwEyaDwKLLoEV7uRybAw== + dependencies: + cssnano-util-get-arguments "^4.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-overflow-shorthand@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/postcss-overflow-shorthand/-/postcss-overflow-shorthand-2.0.0.tgz#31ecf350e9c6f6ddc250a78f0c3e111f32dd4c30" + integrity sha512-aK0fHc9CBNx8jbzMYhshZcEv8LtYnBIRYQD5i7w/K/wS9c2+0NSR6B3OVMu5y0hBHYLcMGjfU+dmWYNKH0I85g== + dependencies: + postcss "^7.0.2" + +postcss-page-break@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/postcss-page-break/-/postcss-page-break-2.0.0.tgz#add52d0e0a528cabe6afee8b46e2abb277df46bf" + integrity sha512-tkpTSrLpfLfD9HvgOlJuigLuk39wVTbbd8RKcy8/ugV2bNBUW3xU+AIqyxhDrQr1VUj1RmyJrBn1YWrqUm9zAQ== + dependencies: + postcss "^7.0.2" + +postcss-place@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-place/-/postcss-place-4.0.1.tgz#e9f39d33d2dc584e46ee1db45adb77ca9d1dcc62" + integrity sha512-Zb6byCSLkgRKLODj/5mQugyuj9bvAAw9LqJJjgwz5cYryGeXfFZfSXoP1UfveccFmeq0b/2xxwcTEVScnqGxBg== + dependencies: + postcss "^7.0.2" + postcss-values-parser "^2.0.0" + +postcss-preset-env@^6.7.0: + version "6.7.0" + resolved "https://registry.yarnpkg.com/postcss-preset-env/-/postcss-preset-env-6.7.0.tgz#c34ddacf8f902383b35ad1e030f178f4cdf118a5" + integrity sha512-eU4/K5xzSFwUFJ8hTdTQzo2RBLbDVt83QZrAvI07TULOkmyQlnYlpwep+2yIK+K+0KlZO4BvFcleOCCcUtwchg== + dependencies: + autoprefixer "^9.6.1" + browserslist "^4.6.4" + caniuse-lite "^1.0.30000981" + css-blank-pseudo "^0.1.4" + css-has-pseudo "^0.10.0" + css-prefers-color-scheme "^3.1.1" + cssdb "^4.4.0" + postcss "^7.0.17" + postcss-attribute-case-insensitive "^4.0.1" + postcss-color-functional-notation "^2.0.1" + postcss-color-gray "^5.0.0" + postcss-color-hex-alpha "^5.0.3" + postcss-color-mod-function "^3.0.3" + postcss-color-rebeccapurple "^4.0.1" + postcss-custom-media "^7.0.8" + postcss-custom-properties "^8.0.11" + postcss-custom-selectors "^5.1.2" + postcss-dir-pseudo-class "^5.0.0" + postcss-double-position-gradients "^1.0.0" + postcss-env-function "^2.0.2" + postcss-focus-visible "^4.0.0" + postcss-focus-within "^3.0.0" + postcss-font-variant "^4.0.0" + postcss-gap-properties "^2.0.0" + postcss-image-set-function "^3.0.1" + postcss-initial "^3.0.0" + postcss-lab-function "^2.0.1" + postcss-logical "^3.0.0" + postcss-media-minmax "^4.0.0" + postcss-nesting "^7.0.0" + postcss-overflow-shorthand "^2.0.0" + postcss-page-break "^2.0.0" + postcss-place "^4.0.1" + postcss-pseudo-class-any-link "^6.0.0" + postcss-replace-overflow-wrap "^3.0.0" + postcss-selector-matches "^4.0.0" + postcss-selector-not "^4.0.0" + +postcss-pseudo-class-any-link@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-6.0.0.tgz#2ed3eed393b3702879dec4a87032b210daeb04d1" + integrity sha512-lgXW9sYJdLqtmw23otOzrtbDXofUdfYzNm4PIpNE322/swES3VU9XlXHeJS46zT2onFO7V1QFdD4Q9LiZj8mew== + dependencies: + postcss "^7.0.2" + postcss-selector-parser "^5.0.0-rc.3" + +postcss-reduce-initial@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-4.0.3.tgz#7fd42ebea5e9c814609639e2c2e84ae270ba48df" + integrity sha512-gKWmR5aUulSjbzOfD9AlJiHCGH6AEVLaM0AV+aSioxUDd16qXP1PCh8d1/BGVvpdWn8k/HiK7n6TjeoXN1F7DA== + dependencies: + browserslist "^4.0.0" + caniuse-api "^3.0.0" + has "^1.0.0" + postcss "^7.0.0" + +postcss-reduce-transforms@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-4.0.2.tgz#17efa405eacc6e07be3414a5ca2d1074681d4e29" + integrity sha512-EEVig1Q2QJ4ELpJXMZR8Vt5DQx8/mo+dGWSR7vWXqcob2gQLyQGsionYcGKATXvQzMPn6DSN1vTN7yFximdIAg== + dependencies: + cssnano-util-get-match "^4.0.0" + has "^1.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-replace-overflow-wrap@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-3.0.0.tgz#61b360ffdaedca84c7c918d2b0f0d0ea559ab01c" + integrity sha512-2T5hcEHArDT6X9+9dVSPQdo7QHzG4XKclFT8rU5TzJPDN7RIRTbO9c4drUISOVemLj03aezStHCR2AIcr8XLpw== + dependencies: + postcss "^7.0.2" + +postcss-reporter@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/postcss-reporter/-/postcss-reporter-7.0.1.tgz#463780d0d8d64f356403eba901fdeae71d988f2b" + integrity sha512-R9AK80KIqqMb+lwGRBcRkXS7r96VCTxrZvvrfibyA/dWjqctwx7leHMCC05A9HbW8PnChwOWwrmISwp5HQu5wg== + dependencies: + colorette "^1.2.1" + lodash.difference "^4.5.0" + lodash.forown "^4.4.0" + lodash.get "^4.4.2" + lodash.groupby "^4.6.0" + lodash.sortby "^4.7.0" + log-symbols "^4.0.0" + +postcss-resolve-nested-selector@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.1.tgz#29ccbc7c37dedfac304e9fff0bf1596b3f6a0e4e" + integrity sha1-Kcy8fDfe36wwTp//C/FZaz9qDk4= + +postcss-safe-parser@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-safe-parser/-/postcss-safe-parser-4.0.2.tgz#a6d4e48f0f37d9f7c11b2a581bf00f8ba4870b96" + integrity sha512-Uw6ekxSWNLCPesSv/cmqf2bY/77z11O7jZGPax3ycZMFU/oi2DMH9i89AdHc1tRwFg/arFoEwX0IS3LCUxJh1g== + dependencies: + postcss "^7.0.26" + +postcss-sass@^0.4.4: + version "0.4.4" + resolved "https://registry.yarnpkg.com/postcss-sass/-/postcss-sass-0.4.4.tgz#91f0f3447b45ce373227a98b61f8d8f0785285a3" + integrity sha512-BYxnVYx4mQooOhr+zer0qWbSPYnarAy8ZT7hAQtbxtgVf8gy+LSLT/hHGe35h14/pZDTw1DsxdbrwxBN++H+fg== + dependencies: + gonzales-pe "^4.3.0" + postcss "^7.0.21" + +postcss-scss@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/postcss-scss/-/postcss-scss-2.1.1.tgz#ec3a75fa29a55e016b90bf3269026c53c1d2b383" + integrity sha512-jQmGnj0hSGLd9RscFw9LyuSVAa5Bl1/KBPqG1NQw9w8ND55nY4ZEsdlVuYJvLPpV+y0nwTV5v/4rHPzZRihQbA== + dependencies: + postcss "^7.0.6" + +postcss-selector-matches@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/postcss-selector-matches/-/postcss-selector-matches-4.0.0.tgz#71c8248f917ba2cc93037c9637ee09c64436fcff" + integrity sha512-LgsHwQR/EsRYSqlwdGzeaPKVT0Ml7LAT6E75T8W8xLJY62CE4S/l03BWIt3jT8Taq22kXP08s2SfTSzaraoPww== + dependencies: + balanced-match "^1.0.0" + postcss "^7.0.2" + +postcss-selector-not@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/postcss-selector-not/-/postcss-selector-not-4.0.0.tgz#c68ff7ba96527499e832724a2674d65603b645c0" + integrity sha512-W+bkBZRhqJaYN8XAnbbZPLWMvZD1wKTu0UxtFKdhtGjWYmxhkUneoeOhRJKdAE5V7ZTlnbHfCR+6bNwK9e1dTQ== + dependencies: + balanced-match "^1.0.0" + postcss "^7.0.2" + +postcss-selector-parser@^3.0.0: + version "3.1.2" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz#b310f5c4c0fdaf76f94902bbaa30db6aa84f5270" + integrity sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA== + dependencies: + dot-prop "^5.2.0" + indexes-of "^1.0.1" + uniq "^1.0.1" + +postcss-selector-parser@^5.0.0-rc.3, postcss-selector-parser@^5.0.0-rc.4: + version "5.0.0" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz#249044356697b33b64f1a8f7c80922dddee7195c" + integrity sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ== + dependencies: + cssesc "^2.0.0" + indexes-of "^1.0.1" + uniq "^1.0.1" + +postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4: + version "6.0.4" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.4.tgz#56075a1380a04604c38b063ea7767a129af5c2b3" + integrity sha512-gjMeXBempyInaBqpp8gODmwZ52WaYsVOsfr4L4lDQ7n3ncD6mEyySiDtgzCT+NYC0mmeOLvtsF8iaEf0YT6dBw== + dependencies: + cssesc "^3.0.0" + indexes-of "^1.0.1" + uniq "^1.0.1" + util-deprecate "^1.0.2" + +postcss-simple-vars@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/postcss-simple-vars/-/postcss-simple-vars-6.0.1.tgz#7733415a7b57f5cb9b5a15cdd6b0546c505e7ad6" + integrity sha512-P1M+APrquBXOPKtk/LlWptuPBwTCjMgxZxv1lkpjG9RdOx8ewi8USOclPCt75xG7UT7viDUB3p3qy2ffPBefjQ== + +postcss-svgo@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-4.0.2.tgz#17b997bc711b333bab143aaed3b8d3d6e3d38258" + integrity sha512-C6wyjo3VwFm0QgBy+Fu7gCYOkCmgmClghO+pjcxvrcBKtiKt0uCF+hvbMO1fyv5BMImRK90SMb+dwUnfbGd+jw== + dependencies: + is-svg "^3.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + svgo "^1.0.0" + +postcss-syntax@^0.36.2: + version "0.36.2" + resolved "https://registry.yarnpkg.com/postcss-syntax/-/postcss-syntax-0.36.2.tgz#f08578c7d95834574e5593a82dfbfa8afae3b51c" + integrity sha512-nBRg/i7E3SOHWxF3PpF5WnJM/jQ1YpY9000OaVXlAQj6Zp/kIqJxEDWIZ67tAd7NLuk7zqN4yqe9nc0oNAOs1w== + +postcss-unique-selectors@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-4.0.1.tgz#9446911f3289bfd64c6d680f073c03b1f9ee4bac" + integrity sha512-+JanVaryLo9QwZjKrmJgkI4Fn8SBgRO6WXQBJi7KiAVPlmxikB5Jzc4EvXMT2H0/m0RjrVVm9rGNhZddm/8Spg== + dependencies: + alphanum-sort "^1.0.0" + postcss "^7.0.0" + uniqs "^2.0.0" + +postcss-value-parser@^3.0.0: + version "3.3.1" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281" + integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ== + +postcss-value-parser@^4.0.0, postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb" + integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ== + +postcss-values-parser@^2.0.0, postcss-values-parser@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/postcss-values-parser/-/postcss-values-parser-2.0.1.tgz#da8b472d901da1e205b47bdc98637b9e9e550e5f" + integrity sha512-2tLuBsA6P4rYTNKCXYG/71C7j1pU6pK503suYOmn4xYrQIzW+opD+7FAFNuGSdZC/3Qfy334QbeMu7MEb8gOxg== + dependencies: + flatten "^1.0.2" + indexes-of "^1.0.1" + uniq "^1.0.1" + +postcss-variables@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/postcss-variables/-/postcss-variables-1.1.1.tgz#f86856acd0583b0c493767f22f7258509d37f9e0" + integrity sha512-a9b2ZXoy60xl28m+jedXYvbXLdYSLPXOqvgkLUHhOUbhIRlVoSHRGhGtpMLkcgVc05lu3JUBEypLVcTYNcltMw== + dependencies: + postcss "^6.0.9" + +postcss@^6.0.8, postcss@^6.0.9: + version "6.0.23" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.23.tgz#61c82cc328ac60e677645f979054eb98bc0e3324" + integrity sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag== + dependencies: + chalk "^2.4.1" + source-map "^0.6.1" + supports-color "^5.4.0" + +postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.17, postcss@^7.0.2, postcss@^7.0.21, postcss@^7.0.26, postcss@^7.0.27, postcss@^7.0.32, postcss@^7.0.5, postcss@^7.0.6: + version "7.0.35" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.35.tgz#d2be00b998f7f211d8a276974079f2e92b970e24" + integrity sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg== + dependencies: + chalk "^2.4.2" + source-map "^0.6.1" + supports-color "^6.1.0" + +postcss@^8.1.0, postcss@^8.1.1, postcss@^8.1.2: + version "8.1.2" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.1.2.tgz#9731fcaa4f7b0bef47121821bdae9eeb609a324c" + integrity sha512-mToqEVFq8jF9TFhlIK4HhE34zknFJuNTgqtsr60vUvrWn+9TIYugCwiV1JZRxCuOrej2jjstun1bn4Bc7/1HkA== + dependencies: + colorette "^1.2.1" + line-column "^1.0.2" + nanoid "^3.1.12" + source-map "^0.6.1" + +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +pretty-error@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-2.1.2.tgz#be89f82d81b1c86ec8fdfbc385045882727f93b6" + integrity sha512-EY5oDzmsX5wvuynAByrmY0P0hcp+QpnAKbJng2A2MPjVKXCxrDSUkzghVJ4ZGPIv+JC4gX8fPUWscC0RtjsWGw== + dependencies: + lodash "^4.17.20" + renderkid "^2.0.4" + +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + +progress@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + +promise-inflight@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" + integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM= + +prop-types@^15.5.10, prop-types@^15.6.2, prop-types@^15.7.2: + version "15.7.2" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" + integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== + dependencies: + loose-envify "^1.4.0" + object-assign "^4.1.1" + react-is "^16.8.1" + +proxy-addr@~2.0.5: + version "2.0.6" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf" + integrity sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw== + dependencies: + forwarded "~0.1.2" + ipaddr.js "1.9.1" + +prr@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" + integrity sha1-0/wRS6BplaRexok/SEzrHXj19HY= + +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +punycode@1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" + integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= + +punycode@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +q@^1.1.2: + version "1.5.1" + resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" + integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc= + +qs@6.7.0: + version "6.7.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" + integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== + +qs@^6.9.4: + version "6.9.4" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.4.tgz#9090b290d1f91728d3c22e54843ca44aea5ab687" + integrity sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ== + +querystring@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" + integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= + +querystringify@^2.1.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" + integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== + +quick-lru@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f" + integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g== + +raf@^3.4.0, raf@^3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39" + integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA== + dependencies: + performance-now "^2.1.0" + +randombytes@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== + dependencies: + safe-buffer "^5.1.0" + +range-parser@^1.2.1, range-parser@~1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +raw-body@2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332" + integrity sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q== + dependencies: + bytes "3.1.0" + http-errors "1.7.2" + iconv-lite "0.4.24" + unpipe "1.0.0" + +rc-align@^4.0.0: + version "4.0.8" + resolved "https://registry.yarnpkg.com/rc-align/-/rc-align-4.0.8.tgz#276c3f5dfadf0de4bb95392cb81568c9e947a668" + integrity sha512-2sRUkmB8z4UEXzaS+lDHzXMoR8HrtKH9nn2yHlHVNyUTnaucjMFbdEoCk+hO1g7cpIgW0MphG8i0EH2scSesfw== + dependencies: + "@babel/runtime" "^7.10.1" + classnames "2.x" + dom-align "^1.7.0" + rc-util "^5.3.0" + resize-observer-polyfill "^1.5.1" + +rc-animate@3.x, rc-animate@~3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/rc-animate/-/rc-animate-3.1.1.tgz#defdd863f56816c222534e4dc68feddecd081386" + integrity sha512-8wg2Zg3EETy0k/9kYuis30NJNQg1D6/WSQwnCiz6SvyxQXNet/rVraRz3bPngwY6rcU2nlRvoShiYOorXyF7Sg== + dependencies: + "@ant-design/css-animation" "^1.7.2" + classnames "^2.2.6" + raf "^3.4.0" + rc-util "^4.15.3" + +rc-cascader@~1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/rc-cascader/-/rc-cascader-1.4.0.tgz#d731ea8e07433558627941036091a2820e895474" + integrity sha512-6kgQljDQEKjVAVRkZtvvoi+2qv4u42M6oLuvt4ZDBa16r3X9ZN8TAq3atVyC840ivbGKlHT50OcdVx/iwiHc1w== + dependencies: + array-tree-filter "^2.1.0" + rc-trigger "^5.0.4" + rc-util "^5.0.1" + warning "^4.0.1" + +rc-checkbox@~2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/rc-checkbox/-/rc-checkbox-2.3.1.tgz#2a61bc43017c783bd2e9f1a67553bf8efe7aa4d3" + integrity sha512-i290/iTqmZ0WtI2UPIryqT9rW6O99+an4KeZIyZDH3r+Jbb6YdddaWNdzq7g5m9zaNhJvgjf//wJtC4fvve2Tg== + dependencies: + "@babel/runtime" "^7.10.1" + classnames "^2.2.1" + +rc-collapse@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/rc-collapse/-/rc-collapse-2.0.1.tgz#99e7655acd9c237b72369a39dcb5c713451e1e92" + integrity sha512-sRNqwQovzQoptTh7dCwj3kfxrdor2oNXrGSBz+QJxSFS7N3Ujgf8X/KlN2ElCkwBKf7nNv36t9dwH0HEku4wJg== + dependencies: + "@ant-design/css-animation" "^1.7.2" + classnames "2.x" + rc-animate "3.x" + rc-util "^5.2.1" + shallowequal "^1.1.0" + +rc-dialog@~8.4.0: + version "8.4.3" + resolved "https://registry.yarnpkg.com/rc-dialog/-/rc-dialog-8.4.3.tgz#de8650ce7d1fcb6c1f7e065b94a6894b9a5a54a4" + integrity sha512-LHsWXb+2Cy4vEOeJcPvk9M0WSr80Gi438ov5rXt3E6XB4j+53Z+vMFRr+TagnVuOVQRCLmmzT4qutfm2U1OK6w== + dependencies: + "@babel/runtime" "^7.10.1" + classnames "^2.2.6" + rc-motion "^2.3.0" + rc-util "^5.0.1" + +rc-drawer@~4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/rc-drawer/-/rc-drawer-4.1.0.tgz#d7bf0bc030300b62d282bc04e053b9acad6b08b4" + integrity sha512-kjeQFngPjdzAFahNIV0EvEBoIKMOnvUsAxpkSPELoD/1DuR4nLafom5ryma+TIxGwkFJ92W6yjsMi1U9aiOTeQ== + dependencies: + "@babel/runtime" "^7.10.1" + classnames "^2.2.6" + rc-util "^5.0.1" + +rc-dropdown@^3.1.3, rc-dropdown@~3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/rc-dropdown/-/rc-dropdown-3.2.0.tgz#da6c2ada403842baee3a9e909a0b1a91ba3e1090" + integrity sha512-j1HSw+/QqlhxyTEF6BArVZnTmezw2LnSmRk6I9W7BCqNCKaRwleRmMMs1PHbuaG8dKHVqP6e21RQ7vPBLVnnNw== + dependencies: + "@babel/runtime" "^7.10.1" + classnames "^2.2.6" + rc-trigger "^5.0.4" + +rc-field-form@~1.12.0: + version "1.12.1" + resolved "https://registry.yarnpkg.com/rc-field-form/-/rc-field-form-1.12.1.tgz#e7f5dc5cc5a267420fd50e1e3ae3352e650c1425" + integrity sha512-c09NVEoGFtwqpTJH4Tw1D8UUitKrrTCW2UAFcJ57FHTg5zReozzgjrrv3UiKDVjbbFzikDLdYz3CzdWMlqVHXg== + dependencies: + "@babel/runtime" "^7.8.4" + async-validator "^3.0.3" + rc-util "^5.0.0" + +rc-image@~3.2.1: + version "3.2.2" + resolved "https://registry.yarnpkg.com/rc-image/-/rc-image-3.2.2.tgz#5d2b7d474dd01ea7af2cbc84fe6af3de8905b3fe" + integrity sha512-8D1pj4qTdC93IfeTPstGFBwpDRZPC565emm4VevrtyFoD9QHBF6kp9kOtzk0JAmbybLAQuX4GGNcwoc7tbZ9Zw== + dependencies: + "@ant-design/icons" "^4.2.2" + "@babel/runtime" "^7.11.2" + classnames "^2.2.6" + rc-dialog "~8.4.0" + rc-util "^5.0.6" + +rc-input-number@~6.1.0: + version "6.1.1" + resolved "https://registry.yarnpkg.com/rc-input-number/-/rc-input-number-6.1.1.tgz#818c426942d1b4dc4d6d2639d741ca67773a9118" + integrity sha512-9t2xf1G0YEism7FAXAvF1huBk7ZNABPBf6NL+3/aDL123WiT/vhhod4cldiDWTM1Yb2EDKR//ZIa546ScdsUaA== + dependencies: + "@babel/runtime" "^7.10.1" + classnames "^2.2.5" + rc-util "^5.0.1" + +rc-mentions@~1.5.0: + version "1.5.2" + resolved "https://registry.yarnpkg.com/rc-mentions/-/rc-mentions-1.5.2.tgz#94559a369de73e7cc92f343badaf94499fb410a7" + integrity sha512-GqV0tOtHY3pLpOsFCxJ2i6Ad8AVfxFmz0NlD/8rb8IG8pMpthJKcdfnXlNZRx3Fa9O4YEgJpdSY1WEbmlx2DWQ== + dependencies: + "@babel/runtime" "^7.10.1" + classnames "^2.2.6" + rc-menu "^8.0.1" + rc-textarea "^0.3.0" + rc-trigger "^5.0.4" + rc-util "^5.0.1" + +rc-menu@^8.0.1, rc-menu@^8.6.1, rc-menu@~8.8.2: + version "8.8.3" + resolved "https://registry.yarnpkg.com/rc-menu/-/rc-menu-8.8.3.tgz#feb8ba0371dd342fbf1052d4fcca7b669b0bf66a" + integrity sha512-C9sT0SBXmUbVWRUseXASousacRVPnOm5aXdyJR569WIvZwbs2IncpGNmAcft1R5ZuFE3Y+SZZ5FYvtGtbCzkIQ== + dependencies: + "@babel/runtime" "^7.10.1" + classnames "2.x" + mini-store "^3.0.1" + omit.js "^2.0.0" + rc-motion "^2.0.1" + rc-trigger "^5.0.4" + rc-util "^5.0.1" + resize-observer-polyfill "^1.5.0" + shallowequal "^1.1.0" + +rc-motion@^2.0.0, rc-motion@^2.0.1, rc-motion@^2.2.0, rc-motion@^2.3.0: + version "2.3.3" + resolved "https://registry.yarnpkg.com/rc-motion/-/rc-motion-2.3.3.tgz#308fe27f2d2b4d6e951297740294fff32d13c15b" + integrity sha512-eOpPDFz6Y+gX1Nd3/AZOhS+Cqv9CiyJ+hrfAinfemJv+fiiVLv/NkFYe2fqw0onNeGiTKJaDF5Ah4Hm006K5yw== + dependencies: + "@babel/runtime" "^7.11.1" + classnames "^2.2.1" + rc-util "^5.2.1" + +rc-notification@~4.5.2: + version "4.5.2" + resolved "https://registry.yarnpkg.com/rc-notification/-/rc-notification-4.5.2.tgz#7836fc04068e00a764fca944074101faa144d503" + integrity sha512-rIgQip4BzUbHpDXDdNc2EFgIh1gxI97UjUbhU8hzdsjytBVstIEHXH36EgHTGllMkOhL9PkQOByg+mgV+I60ZQ== + dependencies: + "@babel/runtime" "^7.10.1" + classnames "2.x" + rc-motion "^2.2.0" + rc-util "^5.0.1" + +rc-pagination@~3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/rc-pagination/-/rc-pagination-3.1.1.tgz#6a3b28f871181b7fad298a15a91f1de6f6e6304a" + integrity sha512-8chFRHXRXRhdPO2Tlmm4hnA/1FnO2hYSoop6FaukQ9/IaCugsVVcXo7OqZ03YJY+aWQLtS6tDsb+sAapCCmljw== + dependencies: + "@babel/runtime" "^7.10.1" + classnames "^2.2.1" + +rc-picker@~2.3.0: + version "2.3.3" + resolved "https://registry.yarnpkg.com/rc-picker/-/rc-picker-2.3.3.tgz#c58c4270891f92aad02d0404d1699285f9321fd9" + integrity sha512-ah4ucCnAs8ss7GgV7sF7MGgRlyfP4753z+OjnF4X7cIrntygklQqiFDBZYS02RX773vhJ+jc6AbyoR7hI4aGng== + dependencies: + "@babel/runtime" "^7.10.1" + classnames "^2.2.1" + date-fns "^2.15.0" + dayjs "^1.8.30" + moment "^2.24.0" + rc-trigger "^5.0.4" + rc-util "^5.4.0" + shallowequal "^1.1.0" + +rc-progress@~3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/rc-progress/-/rc-progress-3.1.1.tgz#f7ba5eb33cc438862513ab5a71739feaf6b3ef4f" + integrity sha512-1ns3pW7ll9bHfdXtlVLF+vngdvlxiCDtiqwXnZFEdurst11JTiPxVdeqnCNbhWx5hP4kCKkAPqG1N0FVfTSUGA== + dependencies: + "@babel/runtime" "^7.10.1" + classnames "^2.2.6" + +rc-rate@~2.8.2: + version "2.8.2" + resolved "https://registry.yarnpkg.com/rc-rate/-/rc-rate-2.8.2.tgz#d82d237d74fd4aef3e0581d2700b646cdd1cd8a2" + integrity sha512-f9T/D+ZwWQrWHkpidpQbnXpnVMGMC4eSRAkwuu88a8Qv1C/9LNc4AErazoh8tpnZBFqq19F3j0Glv+sDgkfEig== + dependencies: + "@babel/runtime" "^7.10.1" + classnames "^2.2.5" + rc-util "^5.0.1" + +rc-resize-observer@^0.2.0, rc-resize-observer@^0.2.1, rc-resize-observer@^0.2.3: + version "0.2.6" + resolved "https://registry.yarnpkg.com/rc-resize-observer/-/rc-resize-observer-0.2.6.tgz#c1b642f6d1293e34c4e3715f47f69443a167b825" + integrity sha512-YX6nYnd6fk7zbuvT6oSDMKiZjyngjHoy+fz+vL3Tez38d/G5iGdaDJa2yE7345G6sc4Mm1IGRUIwclvltddhmA== + dependencies: + "@babel/runtime" "^7.10.1" + classnames "^2.2.1" + rc-util "^5.0.0" + resize-observer-polyfill "^1.5.1" + +rc-select@^11.1.1, rc-select@~11.4.0: + version "11.4.2" + resolved "https://registry.yarnpkg.com/rc-select/-/rc-select-11.4.2.tgz#5b431ee7b2cc6e439886ca855774fc116e6fe6fb" + integrity sha512-DQHYwMcvAajnnlahKkYIW47AVTXgxpGj9CWbe+juXgvxawQRFUdd8T8L2Q05aOkMy02UTG0Qrs7EZfHmn5QHbA== + dependencies: + "@babel/runtime" "^7.10.1" + classnames "2.x" + rc-motion "^2.0.1" + rc-trigger "^5.0.4" + rc-util "^5.0.1" + rc-virtual-list "^3.2.0" + warning "^4.0.3" + +rc-slider@~9.5.2: + version "9.5.4" + resolved "https://registry.yarnpkg.com/rc-slider/-/rc-slider-9.5.4.tgz#4bbb1c7810037adad030c82a1e47e1b331405449" + integrity sha512-24goJnWhmWi0ojNZMoPSMni2wh73IPqEK0TJh7rWn10hPLLKgG8x3KRR0g4uUdCS9APHyosqxGXUIJKGydJXVg== + dependencies: + "@babel/runtime" "^7.10.1" + classnames "^2.2.5" + rc-tooltip "^5.0.1" + rc-util "^5.0.0" + shallowequal "^1.1.0" + +rc-steps@~4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/rc-steps/-/rc-steps-4.1.3.tgz#208580e22db619e3830ddb7fa41bc886c65d9803" + integrity sha512-GXrMfWQOhN3sVze3JnzNboHpQdNHcdFubOETUHyDpa/U3HEKBZC3xJ8XK4paBgF4OJ3bdUVLC+uBPc6dCxvDYA== + dependencies: + "@babel/runtime" "^7.10.2" + classnames "^2.2.3" + rc-util "^5.0.1" + +rc-switch@~3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/rc-switch/-/rc-switch-3.2.2.tgz#d001f77f12664d52595b4f6fb425dd9e66fba8e8" + integrity sha512-+gUJClsZZzvAHGy1vZfnwySxj+MjLlGRyXKXScrtCTcmiYNPzxDFOxdQ/3pK1Kt/0POvwJ/6ALOR8gwdXGhs+A== + dependencies: + "@babel/runtime" "^7.10.1" + classnames "^2.2.1" + rc-util "^5.0.1" + +rc-table@~7.10.0: + version "7.10.3" + resolved "https://registry.yarnpkg.com/rc-table/-/rc-table-7.10.3.tgz#f667e3d7d8a00c490beb83086ed9f3a04df6850f" + integrity sha512-iX96RaERJiTsmO8wljxjCHhsPMTge/0BB1dHS4I+5xegr+bud8a2KV4mX3rYcrnVjYueTqmtXH2K6EQYNhpOGw== + dependencies: + "@babel/runtime" "^7.10.1" + classnames "^2.2.5" + rc-resize-observer "^0.2.0" + rc-util "^5.4.0" + shallowequal "^1.1.0" + +rc-tabs@~11.7.0: + version "11.7.0" + resolved "https://registry.yarnpkg.com/rc-tabs/-/rc-tabs-11.7.0.tgz#e03a03fbf5b8d04a1c9f12f24a621b1d7ff6a093" + integrity sha512-nYwQcgML2drM0iau4aa6HI4qyyZSW0WpspCAtO5KGjXwHzUJcvv3qgLVuoQOWQaDDHXkI9Jj8U7Y/Hcrdyj1Kw== + dependencies: + "@babel/runtime" "^7.11.2" + classnames "2.x" + raf "^3.4.1" + rc-dropdown "^3.1.3" + rc-menu "^8.6.1" + rc-resize-observer "^0.2.1" + rc-util "^5.0.0" + +rc-textarea@^0.3.0, rc-textarea@~0.3.0: + version "0.3.1" + resolved "https://registry.yarnpkg.com/rc-textarea/-/rc-textarea-0.3.1.tgz#e81e1ef6f0e29729f9f80bb266cf6769dd7ab69a" + integrity sha512-bO5Ol5uD6A++aWI6BJ0Pa/8OZcGeacP9LxIGkUqkCwPyOG3kaLOsWb8ya4xCfrsC2P4vDTsHsJmmmG5wuXGFRg== + dependencies: + "@babel/runtime" "^7.10.1" + classnames "^2.2.1" + omit.js "^2.0.0" + rc-resize-observer "^0.2.3" + +rc-tooltip@^5.0.1, rc-tooltip@~5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/rc-tooltip/-/rc-tooltip-5.0.1.tgz#b82c4259604d2cb62ca610ed7932dd37fc6ef61d" + integrity sha512-3AnxhUS0j74xAV3khrKw8o6rg+Ima3nw09DJBezMPnX3ImQUAnayWsPSlN1mEnihjA43rcFkGM1emiKE+CXyMQ== + dependencies: + "@babel/runtime" "^7.11.2" + rc-trigger "^5.0.0" + +rc-tree-select@~4.1.1: + version "4.1.2" + resolved "https://registry.yarnpkg.com/rc-tree-select/-/rc-tree-select-4.1.2.tgz#bf012c3c32cf2e82fc7ffbdd60cb596163a290a0" + integrity sha512-2tRwZ4ChY+BarVKHoPR65kSZtopgwKCig6ngJiiTVgYfRdAhfdQp2j2+L8YW9TkosYGmwgTOhmlphlG3QNy7Pg== + dependencies: + "@babel/runtime" "^7.10.1" + classnames "2.x" + rc-select "^11.1.1" + rc-tree "^3.8.0" + rc-util "^5.0.5" + +rc-tree@^3.8.0, rc-tree@~3.10.0: + version "3.10.0" + resolved "https://registry.yarnpkg.com/rc-tree/-/rc-tree-3.10.0.tgz#897498b3756f6c84f41ad2b244ee9489abf43b7f" + integrity sha512-kf7J/f2E2T8Kfta3/1BIg65AzTmXOgOjn0KOpvD3KI/gqkfKMRKUS1ybkxW39JUPpKwdeOHFnYH+nFFMq7tkfg== + dependencies: + "@babel/runtime" "^7.10.1" + classnames "2.x" + rc-motion "^2.0.1" + rc-util "^5.0.0" + rc-virtual-list "^3.0.1" + +rc-trigger@^5.0.0, rc-trigger@^5.0.4, rc-trigger@~5.0.3: + version "5.0.7" + resolved "https://registry.yarnpkg.com/rc-trigger/-/rc-trigger-5.0.7.tgz#c2f6e988756c76f84b63ff1071e02affc56495ec" + integrity sha512-4QzwHL0IaXmSZnMfJV45dR3Cy4XgsQy2m0LySBAFiZYaH5EN3qnq2lOtg5aU4T36g4146fHpfGa7mtJpCgkwhg== + dependencies: + "@babel/runtime" "^7.11.2" + classnames "^2.2.6" + rc-align "^4.0.0" + rc-motion "^2.0.0" + rc-util "^5.3.4" + +rc-upload@~3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/rc-upload/-/rc-upload-3.3.1.tgz#ad8658b2a796031930b35d2b07ab312b7cd4c9ed" + integrity sha512-KWkJbVM9BwU8qi/2jZwmZpAcdRzDkuyfn/yAOLu+nm47dyd6//MtxzQD3XZDFkC6jQ6D5FmlKn6DhmOfV3v43w== + dependencies: + "@babel/runtime" "^7.10.1" + classnames "^2.2.5" + rc-util "^5.2.0" + +rc-util@^4.15.3: + version "4.21.1" + resolved "https://registry.yarnpkg.com/rc-util/-/rc-util-4.21.1.tgz#88602d0c3185020aa1053d9a1e70eac161becb05" + integrity sha512-Z+vlkSQVc1l8O2UjR3WQ+XdWlhj5q9BMQNLk2iOBch75CqPfrJyGtcWMcnhRlNuDu0Ndtt4kLVO8JI8BrABobg== + dependencies: + add-dom-event-listener "^1.1.0" + prop-types "^15.5.10" + react-is "^16.12.0" + react-lifecycles-compat "^3.0.4" + shallowequal "^1.1.0" + +rc-util@^5.0.0, rc-util@^5.0.1, rc-util@^5.0.5, rc-util@^5.0.6, rc-util@^5.0.7, rc-util@^5.1.0, rc-util@^5.2.0, rc-util@^5.2.1, rc-util@^5.3.0, rc-util@^5.3.4, rc-util@^5.4.0: + version "5.4.0" + resolved "https://registry.yarnpkg.com/rc-util/-/rc-util-5.4.0.tgz#688eaeecfdae9dae2bfdf10bedbe884591dba004" + integrity sha512-kXDn1JyLJTAWLBFt+fjkTcUtXhxKkipQCobQmxIEVrX62iXgo24z8YKoWehWfMxPZFPE+RXqrmEu9j5kHz/Lrg== + dependencies: + react-is "^16.12.0" + shallowequal "^1.1.0" + +rc-virtual-list@^3.0.1, rc-virtual-list@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/rc-virtual-list/-/rc-virtual-list-3.2.0.tgz#21e70f42b22f510d38ceb1ba339b89b865686fcd" + integrity sha512-NZb+Z4tGkfrCNXprVUlLJxoRVIELwLmlY5nHwiV3pj4eA9Of8thpQwtT+AomwcZjKhC7R/EDtpk2ATMJXX5s3Q== + dependencies: + classnames "^2.2.6" + rc-resize-observer "^0.2.3" + rc-util "^5.0.7" + +react-dom@^17.0.0: + version "17.0.0" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.0.tgz#f8266e4d9861584553ccbd186d596a1c7dd8dcb4" + integrity sha512-OGnFbxCjI2TMAZYMVxi4hqheJiN8rCEVVrL7XIGzCB6beNc4Am8M47HtkvxODZw9QgjmAPKpLba9FTu4fC1byA== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + scheduler "^0.20.0" + +react-fast-compare@^2.0.1: + version "2.0.4" + resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9" + integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw== + +react-is@^16.12.0, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== + +react-lifecycles-compat@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" + integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== + +react-router-dom@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.2.0.tgz#9e65a4d0c45e13289e66c7b17c7e175d0ea15662" + integrity sha512-gxAmfylo2QUjcwxI63RhQ5G85Qqt4voZpUXSEqCwykV0baaOTQDR1f0PmY8AELqIyVc0NEZUj0Gov5lNGcXgsA== + dependencies: + "@babel/runtime" "^7.1.2" + history "^4.9.0" + loose-envify "^1.3.1" + prop-types "^15.6.2" + react-router "5.2.0" + tiny-invariant "^1.0.2" + tiny-warning "^1.0.0" + +react-router@5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.2.0.tgz#424e75641ca8747fbf76e5ecca69781aa37ea293" + integrity sha512-smz1DUuFHRKdcJC0jobGo8cVbhO3x50tCL4icacOlcwDOEQPq4TMqwx3sY1TP+DvtTgz4nm3thuo7A+BK2U0Dw== + dependencies: + "@babel/runtime" "^7.1.2" + history "^4.9.0" + hoist-non-react-statics "^3.1.0" + loose-envify "^1.3.1" + mini-create-react-context "^0.4.0" + path-to-regexp "^1.7.0" + prop-types "^15.6.2" + react-is "^16.6.0" + tiny-invariant "^1.0.2" + tiny-warning "^1.0.0" + +react@^17.0.0: + version "17.0.0" + resolved "https://registry.yarnpkg.com/react/-/react-17.0.0.tgz#ad96d5fa1a33bb9b06d0cc52672f7992d84aa662" + integrity sha512-rG9bqS3LMuetoSUKHN8G3fMNuQOePKDThK6+2yXFWtoeTDLVNh/QCaxT+Jr+rNf4lwNXpx+atdn3Aa0oi8/6eQ== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + +read-cache@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774" + integrity sha1-5mTvMRYRZsl1HNvo28+GtftY93Q= + dependencies: + pify "^2.3.0" + +read-pkg-up@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be" + integrity sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4= + dependencies: + find-up "^2.0.0" + read-pkg "^2.0.0" + +read-pkg-up@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-7.0.1.tgz#f3a6135758459733ae2b95638056e1854e7ef507" + integrity sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg== + dependencies: + find-up "^4.1.0" + read-pkg "^5.2.0" + type-fest "^0.8.1" + +read-pkg@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8" + integrity sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg= + dependencies: + load-json-file "^2.0.0" + normalize-package-data "^2.3.2" + path-type "^2.0.0" + +read-pkg@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-5.2.0.tgz#7bf295438ca5a33e56cd30e053b34ee7250c93cc" + integrity sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg== + dependencies: + "@types/normalize-package-data" "^2.4.0" + normalize-package-data "^2.5.0" + parse-json "^5.0.0" + type-fest "^0.6.0" + +readable-stream@^2.0.1, readable-stream@^2.0.2: + version "2.3.7" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" + integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +readable-stream@^3.0.6, readable-stream@^3.1.1: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" + integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +readdirp@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525" + integrity sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ== + dependencies: + graceful-fs "^4.1.11" + micromatch "^3.1.10" + readable-stream "^2.0.2" + +rechoir@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.7.0.tgz#32650fd52c21ab252aa5d65b19310441c7e03aca" + integrity sha512-ADsDEH2bvbjltXEP+hTIAmeFekTFK0V2BTxMkok6qILyAJEXV0AFfoWcAq4yfll5VdIMd/RVXq0lR+wQi5ZU3Q== + dependencies: + resolve "^1.9.0" + +redent@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f" + integrity sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg== + dependencies: + indent-string "^4.0.0" + strip-indent "^3.0.0" + +reduce-flatten@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/reduce-flatten/-/reduce-flatten-2.0.0.tgz#734fd84e65f375d7ca4465c69798c25c9d10ae27" + integrity sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w== + +redux@^4.0.0: + version "4.0.5" + resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.5.tgz#4db5de5816e17891de8a80c424232d06f051d93f" + integrity sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w== + dependencies: + loose-envify "^1.4.0" + symbol-observable "^1.2.0" + +regenerator-runtime@^0.13.4: + version "0.13.7" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55" + integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew== + +regex-not@^1.0.0, regex-not@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" + integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A== + dependencies: + extend-shallow "^3.0.2" + safe-regex "^1.1.0" + +regexp.prototype.flags@^1.2.0, regexp.prototype.flags@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz#7aba89b3c13a64509dabcf3ca8d9fbb9bdf5cb75" + integrity sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + +regexpp@^3.0.0, regexpp@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.1.0.tgz#206d0ad0a5648cffbdb8ae46438f3dc51c9f78e2" + integrity sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q== + +relateurl@^0.2.7: + version "0.2.7" + resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" + integrity sha1-VNvzd+UUQKypCkzSdGANP/LYiKk= + +remark-parse@^8.0.0: + version "8.0.3" + resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-8.0.3.tgz#9c62aa3b35b79a486454c690472906075f40c7e1" + integrity sha512-E1K9+QLGgggHxCQtLt++uXltxEprmWzNfg+MxpfHsZlrddKzZ/hZyWHDbK3/Ap8HJQqYJRXP+jHczdL6q6i85Q== + dependencies: + ccount "^1.0.0" + collapse-white-space "^1.0.2" + is-alphabetical "^1.0.0" + is-decimal "^1.0.0" + is-whitespace-character "^1.0.0" + is-word-character "^1.0.0" + markdown-escapes "^1.0.0" + parse-entities "^2.0.0" + repeat-string "^1.5.4" + state-toggle "^1.0.0" + trim "0.0.1" + trim-trailing-lines "^1.0.0" + unherit "^1.0.4" + unist-util-remove-position "^2.0.0" + vfile-location "^3.0.0" + xtend "^4.0.1" + +remark-stringify@^8.0.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/remark-stringify/-/remark-stringify-8.1.1.tgz#e2a9dc7a7bf44e46a155ec78996db896780d8ce5" + integrity sha512-q4EyPZT3PcA3Eq7vPpT6bIdokXzFGp9i85igjmhRyXWmPs0Y6/d2FYwUNotKAWyLch7g0ASZJn/KHHcHZQ163A== + dependencies: + ccount "^1.0.0" + is-alphanumeric "^1.0.0" + is-decimal "^1.0.0" + is-whitespace-character "^1.0.0" + longest-streak "^2.0.1" + markdown-escapes "^1.0.0" + markdown-table "^2.0.0" + mdast-util-compact "^2.0.0" + parse-entities "^2.0.0" + repeat-string "^1.5.4" + state-toggle "^1.0.0" + stringify-entities "^3.0.0" + unherit "^1.0.4" + xtend "^4.0.1" + +remark@^12.0.0: + version "12.0.1" + resolved "https://registry.yarnpkg.com/remark/-/remark-12.0.1.tgz#f1ddf68db7be71ca2bad0a33cd3678b86b9c709f" + integrity sha512-gS7HDonkdIaHmmP/+shCPejCEEW+liMp/t/QwmF0Xt47Rpuhl32lLtDV1uKWvGoq+kxr5jSgg5oAIpGuyULjUw== + dependencies: + remark-parse "^8.0.0" + remark-stringify "^8.0.0" + unified "^9.0.0" + +remove-trailing-separator@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" + integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= + +renderkid@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/renderkid/-/renderkid-2.0.4.tgz#d325e532afb28d3f8796ffee306be8ffd6fc864c" + integrity sha512-K2eXrSOJdq+HuKzlcjOlGoOarUu5SDguDEhE7+Ah4zuOWL40j8A/oHvLlLob9PSTNvVnBd+/q0Er1QfpEuem5g== + dependencies: + css-select "^1.1.0" + dom-converter "^0.2" + htmlparser2 "^3.3.0" + lodash "^4.17.20" + strip-ansi "^3.0.0" + +repeat-element@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce" + integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g== + +repeat-string@^1.0.0, repeat-string@^1.5.4, repeat-string@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" + integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= + +replace-ext@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.0.tgz#de63128373fcbf7c3ccfa4de5a480c45a67958eb" + integrity sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs= + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= + +require-main-filename@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" + integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== + +requires-port@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" + integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= + +resize-observer-polyfill@^1.5.0, resize-observer-polyfill@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464" + integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg== + +resolve-cwd@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a" + integrity sha1-AKn3OHVW4nA46uIyyqNypqWbZlo= + dependencies: + resolve-from "^3.0.0" + +resolve-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" + integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== + dependencies: + resolve-from "^5.0.0" + +resolve-from@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" + integrity sha1-six699nWiBvItuZTM17rywoYh0g= + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +resolve-pathname@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-3.0.0.tgz#99d02224d3cf263689becbb393bc560313025dcd" + integrity sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng== + +resolve-url@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" + integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= + +resolve@^1.1.7, resolve@^1.10.0, resolve@^1.13.1, resolve@^1.17.0, resolve@^1.18.1, resolve@^1.3.2: + version "1.18.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.18.1.tgz#018fcb2c5b207d2a6424aee361c5a266da8f4130" + integrity sha512-lDfCPaMKfOJXjy0dPayzPdF1phampNWr3qFCjAu+rw/qbQmr5jWH5xN2hwh9QKfw9E5v4hwV7A+jrCmL8yjjqA== + dependencies: + is-core-module "^2.0.0" + path-parse "^1.0.6" + +resolve@^1.9.0: + version "1.19.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.19.0.tgz#1af5bf630409734a067cae29318aac7fa29a267c" + integrity sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg== + dependencies: + is-core-module "^2.1.0" + path-parse "^1.0.6" + +ret@~0.1.10: + version "0.1.15" + resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" + integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== + +retry@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" + integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs= + +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +rgb-regex@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/rgb-regex/-/rgb-regex-1.0.1.tgz#c0e0d6882df0e23be254a475e8edd41915feaeb1" + integrity sha1-wODWiC3w4jviVKR16O3UGRX+rrE= + +rgba-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/rgba-regex/-/rgba-regex-1.0.0.tgz#43374e2e2ca0968b0ef1523460b7d730ff22eeb3" + integrity sha1-QzdOLiyglosO8VI0YLfXMP8i7rM= + +rimraf@2.6.3: + version "2.6.3" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" + integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== + dependencies: + glob "^7.1.3" + +rimraf@^2.6.3: + version "2.7.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" + integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== + dependencies: + glob "^7.1.3" + +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +run-parallel@^1.1.9: + version "1.1.9" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.9.tgz#c9dd3a7cf9f4b2c4b6244e173a6ed866e61dd679" + integrity sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q== + +safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +safe-regex@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" + integrity sha1-QKNmnzsHfR6UPURinhV91IAjvy4= + dependencies: + ret "~0.1.10" + +"safer-buffer@>= 2.1.2 < 3": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +sax@~1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== + +scheduler@^0.18.0: + version "0.18.0" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.18.0.tgz#5901ad6659bc1d8f3fdaf36eb7a67b0d6746b1c4" + integrity sha512-agTSHR1Nbfi6ulI0kYNK0203joW2Y5W4po4l+v03tOoiJKpTBbxpNhWDvqc/4IcOw+KLmSiQLTasZ4cab2/UWQ== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + +scheduler@^0.20.0: + version "0.20.0" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.0.tgz#3ff543696b169613afadb09d3fb3fe998d234dd2" + integrity sha512-XegIgta1bIaz2LdaL6eg1GEcE42g0BY9qFXCqlZ/+s2MuEKfigFCW6DEGBlZzeVFlwDmVusrWEyFtBo4sbkkdA== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + +schema-utils@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770" + integrity sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g== + dependencies: + ajv "^6.1.0" + ajv-errors "^1.0.0" + ajv-keywords "^3.1.0" + +schema-utils@^2.6.5: + version "2.7.1" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.1.tgz#1ca4f32d1b24c590c203b8e7a50bf0ea4cd394d7" + integrity sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg== + dependencies: + "@types/json-schema" "^7.0.5" + ajv "^6.12.4" + ajv-keywords "^3.5.2" + +schema-utils@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.0.0.tgz#67502f6aa2b66a2d4032b4279a2944978a0913ef" + integrity sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA== + dependencies: + "@types/json-schema" "^7.0.6" + ajv "^6.12.5" + ajv-keywords "^3.5.2" + +scroll-into-view-if-needed@^2.2.25: + version "2.2.26" + resolved "https://registry.yarnpkg.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.26.tgz#e4917da0c820135ff65ad6f7e4b7d7af568c4f13" + integrity sha512-SQ6AOKfABaSchokAmmaxVnL9IArxEnLEX9j4wAZw+x4iUTb40q7irtHG3z4GtAWz5veVZcCnubXDBRyLVQaohw== + dependencies: + compute-scroll-into-view "^1.0.16" + +select-hose@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" + integrity sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo= + +selfsigned@^1.10.7: + version "1.10.8" + resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.8.tgz#0d17208b7d12c33f8eac85c41835f27fc3d81a30" + integrity sha512-2P4PtieJeEwVgTU9QEcwIRDQ/mXJLX8/+I3ur+Pg16nS8oNbrGxEso9NyYWy8NAmXiNl4dlAp5MwoNeCWzON4w== + dependencies: + node-forge "^0.10.0" + +semver-compare@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" + integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w= + +semver-regex@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/semver-regex/-/semver-regex-2.0.0.tgz#a93c2c5844539a770233379107b38c7b4ac9d338" + integrity sha512-mUdIBBvdn0PLOeP3TEkMH7HHeUP3GjsXCwKarjv/kGmUFOYg1VqEemKhoQpWMu6X2I8kHeuVdGibLGkVK+/5Qw== + +"semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.5.0, semver@^5.6.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +semver@^6.0.0, semver@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + +semver@^7.2.1, semver@^7.3.2: + version "7.3.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" + integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== + +send@0.17.1: + version "0.17.1" + resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" + integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg== + dependencies: + debug "2.6.9" + depd "~1.1.2" + destroy "~1.0.4" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "~1.7.2" + mime "1.6.0" + ms "2.1.1" + on-finished "~2.3.0" + range-parser "~1.2.1" + statuses "~1.5.0" + +serialize-javascript@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-5.0.1.tgz#7886ec848049a462467a97d3d918ebb2aaf934f4" + integrity sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA== + dependencies: + randombytes "^2.1.0" + +serve-index@^1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239" + integrity sha1-03aNabHn2C5c4FD/9bRTvqEqkjk= + dependencies: + accepts "~1.3.4" + batch "0.6.1" + debug "2.6.9" + escape-html "~1.0.3" + http-errors "~1.6.2" + mime-types "~2.1.17" + parseurl "~1.3.2" + +serve-static@1.14.1: + version "1.14.1" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9" + integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg== + dependencies: + encodeurl "~1.0.2" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "0.17.1" + +set-blocking@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= + +set-value@^2.0.0, set-value@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" + integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== + dependencies: + extend-shallow "^2.0.1" + is-extendable "^0.1.1" + is-plain-object "^2.0.3" + split-string "^3.0.1" + +setprototypeof@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" + integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ== + +setprototypeof@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" + integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== + +shallow-clone@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" + integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== + dependencies: + kind-of "^6.0.2" + +shallowequal@^1.0.2, shallowequal@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" + integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ== + +shebang-command@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= + dependencies: + shebang-regex "^1.0.0" + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +side-channel@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.3.tgz#cdc46b057550bbab63706210838df5d4c19519c3" + integrity sha512-A6+ByhlLkksFoUepsGxfj5x1gTSrs+OydsRptUxeNCabQpCFUvcwIczgOigI8vhY/OJCnPnyE9rGiwgvr9cS1g== + dependencies: + es-abstract "^1.18.0-next.0" + object-inspect "^1.8.0" + +signal-exit@^3.0.0, signal-exit@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" + integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== + +simple-swizzle@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" + integrity sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo= + dependencies: + is-arrayish "^0.3.1" + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +slice-ansi@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636" + integrity sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ== + dependencies: + ansi-styles "^3.2.0" + astral-regex "^1.0.0" + is-fullwidth-code-point "^2.0.0" + +slice-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" + integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + +snapdragon-node@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" + integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw== + dependencies: + define-property "^1.0.0" + isobject "^3.0.0" + snapdragon-util "^3.0.1" + +snapdragon-util@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" + integrity sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ== + dependencies: + kind-of "^3.2.0" + +snapdragon@^0.8.1: + version "0.8.2" + resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" + integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg== + dependencies: + base "^0.11.1" + debug "^2.2.0" + define-property "^0.2.5" + extend-shallow "^2.0.1" + map-cache "^0.2.2" + source-map "^0.5.6" + source-map-resolve "^0.5.0" + use "^3.1.0" + +sockjs-client@1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.4.0.tgz#c9f2568e19c8fd8173b4997ea3420e0bb306c7d5" + integrity sha512-5zaLyO8/nri5cua0VtOrFXBPK1jbL4+1cebT/mmKA1E1ZXOvJrII75bPu0l0k843G/+iAbhEqzyKr0w/eCCj7g== + dependencies: + debug "^3.2.5" + eventsource "^1.0.7" + faye-websocket "~0.11.1" + inherits "^2.0.3" + json3 "^3.3.2" + url-parse "^1.4.3" + +sockjs@0.3.20: + version "0.3.20" + resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.20.tgz#b26a283ec562ef8b2687b44033a4eeceac75d855" + integrity sha512-SpmVOVpdq0DJc0qArhF3E5xsxvaiqGNb73XfgBpK1y3UD5gs8DSo8aCTsuT5pX8rssdc2NDIzANwP9eCAiSdTA== + dependencies: + faye-websocket "^0.10.0" + uuid "^3.4.0" + websocket-driver "0.6.5" + +source-list-map@^2.0.0, source-list-map@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" + integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== + +source-map-resolve@^0.5.0: + version "0.5.3" + resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" + integrity sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw== + dependencies: + atob "^2.1.2" + decode-uri-component "^0.2.0" + resolve-url "^0.2.1" + source-map-url "^0.4.0" + urix "^0.1.0" + +source-map-support@^0.5.17, source-map-support@~0.5.12, source-map-support@~0.5.19: + version "0.5.19" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" + integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map-url@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" + integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM= + +source-map@^0.5.0, source-map@^0.5.6: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= + +source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +source-map@^0.7.3, source-map@~0.7.2: + version "0.7.3" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" + integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== + +spdx-correct@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" + integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w== + dependencies: + spdx-expression-parse "^3.0.0" + spdx-license-ids "^3.0.0" + +spdx-exceptions@^2.1.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" + integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== + +spdx-expression-parse@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" + integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + +spdx-license-ids@^3.0.0: + version "3.0.6" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.6.tgz#c80757383c28abf7296744998cbc106ae8b854ce" + integrity sha512-+orQK83kyMva3WyPf59k1+Y525csj5JejicWut55zeTWANuN17qSiSLUXWtzHeNWORSvT7GLDJ/E/XiIWoXBTw== + +spdy-transport@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-3.0.0.tgz#00d4863a6400ad75df93361a1608605e5dcdcf31" + integrity sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw== + dependencies: + debug "^4.1.0" + detect-node "^2.0.4" + hpack.js "^2.1.6" + obuf "^1.1.2" + readable-stream "^3.0.6" + wbuf "^1.7.3" + +spdy@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/spdy/-/spdy-4.0.2.tgz#b74f466203a3eda452c02492b91fb9e84a27677b" + integrity sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA== + dependencies: + debug "^4.1.0" + handle-thing "^2.0.0" + http-deceiver "^1.2.7" + select-hose "^2.0.0" + spdy-transport "^3.0.0" + +specificity@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/specificity/-/specificity-0.4.1.tgz#aab5e645012db08ba182e151165738d00887b019" + integrity sha512-1klA3Gi5PD1Wv9Q0wUoOQN1IWAuPu0D1U03ThXTr0cJ20+/iq2tHSDnK7Kk/0LXJ1ztUB2/1Os0wKmfyNgUQfg== + +split-string@^3.0.1, split-string@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" + integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== + dependencies: + extend-shallow "^3.0.0" + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= + +ssri@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/ssri/-/ssri-8.0.0.tgz#79ca74e21f8ceaeddfcb4b90143c458b8d988808" + integrity sha512-aq/pz989nxVYwn16Tsbj1TqFpD5LLrQxHf5zaHuieFV+R0Bbr4y8qUsOA45hXT/N4/9UNXTarBjnjVmjSOVaAA== + dependencies: + minipass "^3.1.1" + +stable@^0.1.8: + version "0.1.8" + resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" + integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== + +state-toggle@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/state-toggle/-/state-toggle-1.0.3.tgz#e123b16a88e143139b09c6852221bc9815917dfe" + integrity sha512-d/5Z4/2iiCnHw6Xzghyhb+GcmF89bxwgXG60wjIiZaxnymbyOmI8Hk4VqHXiVVp6u2ysaskFfXg3ekCj4WNftQ== + +static-extend@^0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" + integrity sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY= + dependencies: + define-property "^0.2.5" + object-copy "^0.1.0" + +"statuses@>= 1.4.0 < 2", "statuses@>= 1.5.0 < 2", statuses@~1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" + integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= + +string-convert@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/string-convert/-/string-convert-0.2.1.tgz#6982cc3049fbb4cd85f8b24568b9d9bf39eeff97" + integrity sha1-aYLMMEn7tM2F+LJFaLnZvznu/5c= + +string-hash@^1.1.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/string-hash/-/string-hash-1.1.3.tgz#e8aafc0ac1855b4666929ed7dd1275df5d6c811b" + integrity sha1-6Kr8CsGFW0Zmkp7X3RJ1311sgRs= + +string-width@^3.0.0, string-width@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" + integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== + dependencies: + emoji-regex "^7.0.1" + is-fullwidth-code-point "^2.0.0" + strip-ansi "^5.1.0" + +string-width@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5" + integrity sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.0" + +string.prototype.matchall@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.2.tgz#48bb510326fb9fdeb6a33ceaa81a6ea04ef7648e" + integrity sha512-N/jp6O5fMf9os0JU3E72Qhf590RSRZU/ungsL/qJUYVTNv7hTG0P/dbPjxINVN9jpscu3nzYwKESU3P3RY5tOg== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0" + has-symbols "^1.0.1" + internal-slot "^1.0.2" + regexp.prototype.flags "^1.3.0" + side-channel "^1.0.2" + +string.prototype.trimend@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.2.tgz#6ddd9a8796bc714b489a3ae22246a208f37bfa46" + integrity sha512-8oAG/hi14Z4nOVP0z6mdiVZ/wqjDtWSLygMigTzAb+7aPEDTleeFf+WrF+alzecxIRkckkJVn+dTlwzJXORATw== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.18.0-next.1" + +string.prototype.trimstart@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.2.tgz#22d45da81015309cd0cdd79787e8919fc5c613e7" + integrity sha512-7F6CdBTl5zyu30BJFdzSTlSlLPwODC23Od+iLoVH8X6+3fvDPPuBVVj9iaB1GOsSTSIgVfsfm27R2FGrAPznWg== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.18.0-next.1" + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +stringify-entities@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/stringify-entities/-/stringify-entities-3.1.0.tgz#b8d3feac256d9ffcc9fa1fefdcf3ca70576ee903" + integrity sha512-3FP+jGMmMV/ffZs86MoghGqAoqXAdxLrJP4GUdrDN1aIScYih5tuIO3eF4To5AJZ79KDZ8Fpdy7QJnK8SsL1Vg== + dependencies: + character-entities-html4 "^1.0.0" + character-entities-legacy "^1.0.0" + xtend "^4.0.0" + +strip-ansi@^3.0.0, strip-ansi@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= + dependencies: + ansi-regex "^2.0.0" + +strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" + integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== + dependencies: + ansi-regex "^4.1.0" + +strip-ansi@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" + integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== + dependencies: + ansi-regex "^5.0.0" + +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= + +strip-eof@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" + integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= + +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + +strip-indent@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" + integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ== + dependencies: + min-indent "^1.0.0" + +strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +style-loader@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-2.0.0.tgz#9669602fd4690740eaaec137799a03addbbc393c" + integrity sha512-Z0gYUJmzZ6ZdRUqpg1r8GsaFKypE+3xAzuFeMuoHgjc9KZv3wMyCRjQIWEbhoFSq7+7yoHXySDJyyWQaPajeiQ== + dependencies: + loader-utils "^2.0.0" + schema-utils "^3.0.0" + +style-search@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/style-search/-/style-search-0.1.0.tgz#7958c793e47e32e07d2b5cafe5c0bf8e12e77902" + integrity sha1-eVjHk+R+MuB9K1yv5cC/jhLneQI= + +stylehacks@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-4.0.3.tgz#6718fcaf4d1e07d8a1318690881e8d96726a71d5" + integrity sha512-7GlLk9JwlElY4Y6a/rmbH2MhVlTyVmiJd1PfTCqFaIBEGMYNsrO/v3SeGTdhBThLg4Z+NbOk/qFMwCa+J+3p/g== + dependencies: + browserslist "^4.0.0" + postcss "^7.0.0" + postcss-selector-parser "^3.0.0" + +stylelint-webpack-plugin@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/stylelint-webpack-plugin/-/stylelint-webpack-plugin-2.1.1.tgz#1c8ae72a4e5818f7e5925e3fff10502ad34a52a5" + integrity sha512-WHdaWCp4NANcTcltuRjZCjM7jVhdaSg7ag/sQLE22Bf84g5nQC4nBBK8FBdHAssJsho0fDRiwyrzGsIPO+b94A== + dependencies: + arrify "^2.0.1" + micromatch "^4.0.2" + schema-utils "^3.0.0" + +stylelint@^13.7.2: + version "13.7.2" + resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-13.7.2.tgz#6f3c58eea4077680ed0ceb0d064b22b100970486" + integrity sha512-mmieorkfmO+ZA6CNDu1ic9qpt4tFvH2QUB7vqXgrMVHe5ENU69q7YDq0YUg/UHLuCsZOWhUAvcMcLzLDIERzSg== + dependencies: + "@stylelint/postcss-css-in-js" "^0.37.2" + "@stylelint/postcss-markdown" "^0.36.1" + autoprefixer "^9.8.6" + balanced-match "^1.0.0" + chalk "^4.1.0" + cosmiconfig "^7.0.0" + debug "^4.1.1" + execall "^2.0.0" + fast-glob "^3.2.4" + fastest-levenshtein "^1.0.12" + file-entry-cache "^5.0.1" + get-stdin "^8.0.0" + global-modules "^2.0.0" + globby "^11.0.1" + globjoin "^0.1.4" + html-tags "^3.1.0" + ignore "^5.1.8" + import-lazy "^4.0.0" + imurmurhash "^0.1.4" + known-css-properties "^0.19.0" + lodash "^4.17.20" + log-symbols "^4.0.0" + mathml-tag-names "^2.1.3" + meow "^7.1.1" + micromatch "^4.0.2" + normalize-selector "^0.2.0" + postcss "^7.0.32" + postcss-html "^0.36.0" + postcss-less "^3.1.4" + postcss-media-query-parser "^0.2.3" + postcss-resolve-nested-selector "^0.1.1" + postcss-safe-parser "^4.0.2" + postcss-sass "^0.4.4" + postcss-scss "^2.1.1" + postcss-selector-parser "^6.0.2" + postcss-syntax "^0.36.2" + postcss-value-parser "^4.1.0" + resolve-from "^5.0.0" + slash "^3.0.0" + specificity "^0.4.1" + string-width "^4.2.0" + strip-ansi "^6.0.0" + style-search "^0.1.0" + sugarss "^2.0.0" + svg-tags "^1.0.0" + table "^6.0.1" + v8-compile-cache "^2.1.1" + write-file-atomic "^3.0.3" + +sugarss@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/sugarss/-/sugarss-2.0.0.tgz#ddd76e0124b297d40bf3cca31c8b22ecb43bc61d" + integrity sha512-WfxjozUk0UVA4jm+U1d736AUpzSrNsQcIbyOkoE364GrtWmIrFdk5lksEupgWMD4VaT/0kVx1dobpiDumSgmJQ== + dependencies: + postcss "^7.0.2" + +sugarss@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/sugarss/-/sugarss-3.0.1.tgz#1e4e315b3b321eec477ef9617c8964bcf3833b0c" + integrity sha512-xW0tTjuJdd3VSsPH2dLgNDzESka1+Ul3GYVziyhX7GyXQboOARDaeEU++IjhOZPnoKoMENsU0tvtrCKr1sJwlw== + dependencies: + postcss "^8.1.0" + +supports-color@^5.3.0, supports-color@^5.4.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" + integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.0.0, supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +svg-tags@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/svg-tags/-/svg-tags-1.0.0.tgz#58f71cee3bd519b59d4b2a843b6c7de64ac04764" + integrity sha1-WPcc7jvVGbWdSyqEO2x95krAR2Q= + +svgo@^1.0.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/svgo/-/svgo-1.3.2.tgz#b6dc511c063346c9e415b81e43401145b96d4167" + integrity sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw== + dependencies: + chalk "^2.4.1" + coa "^2.0.2" + css-select "^2.0.0" + css-select-base-adapter "^0.1.1" + css-tree "1.0.0-alpha.37" + csso "^4.0.2" + js-yaml "^3.13.1" + mkdirp "~0.5.1" + object.values "^1.1.0" + sax "~1.2.4" + stable "^0.1.8" + unquote "~1.1.1" + util.promisify "~1.0.0" + +symbol-observable@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" + integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ== + +table-layout@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/table-layout/-/table-layout-1.0.1.tgz#8411181ee951278ad0638aea2f779a9ce42894f9" + integrity sha512-dEquqYNJiGwY7iPfZ3wbXDI944iqanTSchrACLL2nOB+1r+h1Nzu2eH+DuPPvWvm5Ry7iAPeFlgEtP5bIp5U7Q== + dependencies: + array-back "^4.0.1" + deep-extend "~0.6.0" + typical "^5.2.0" + wordwrapjs "^4.0.0" + +table@^5.2.3: + version "5.4.6" + resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e" + integrity sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug== + dependencies: + ajv "^6.10.2" + lodash "^4.17.14" + slice-ansi "^2.1.0" + string-width "^3.0.0" + +table@^6.0.1: + version "6.0.3" + resolved "https://registry.yarnpkg.com/table/-/table-6.0.3.tgz#e5b8a834e37e27ad06de2e0fda42b55cfd8a0123" + integrity sha512-8321ZMcf1B9HvVX/btKv8mMZahCjn2aYrDlpqHaBFCfnox64edeH9kEid0vTLTRR8gWR2A20aDgeuTTea4sVtw== + dependencies: + ajv "^6.12.4" + lodash "^4.17.20" + slice-ansi "^4.0.0" + string-width "^4.2.0" + +tapable@^1.0.0, tapable@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" + integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== + +tapable@^2.0.0, tapable@^2.1.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.0.tgz#5c373d281d9c672848213d0e037d1c4165ab426b" + integrity sha512-FBk4IesMV1rBxX2tfiK8RAmogtWn53puLOQlvO8XuwlgxcYbP4mVPS9Ph4aeamSyyVjOl24aYWAuc8U5kCVwMw== + +tar@^6.0.2: + version "6.0.5" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.0.5.tgz#bde815086e10b39f1dcd298e89d596e1535e200f" + integrity sha512-0b4HOimQHj9nXNEAA7zWwMM91Zhhba3pspja6sQbgTpynOJf+bkjBnfybNYzbpLbnwXnbyB4LOREvlyXLkCHSg== + dependencies: + chownr "^2.0.0" + fs-minipass "^2.0.0" + minipass "^3.0.0" + minizlib "^2.1.1" + mkdirp "^1.0.3" + yallist "^4.0.0" + +terser-webpack-plugin@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.0.0.tgz#88f58d27d1c8244965c59540d3ccda1598fc958c" + integrity sha512-rf7l5a9xamIVX3enQeTl0MY2MNeZClo5yPX/tVPy22oY0nzu0b45h7JqyFi/bygqKWtzXMnml0u12mArhQPsBQ== + dependencies: + jest-worker "^26.5.0" + p-limit "^3.0.2" + schema-utils "^3.0.0" + serialize-javascript "^5.0.1" + source-map "^0.6.1" + terser "^5.3.5" + +terser-webpack-plugin@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.0.3.tgz#ec60542db2421f45735c719d2e17dabfbb2e3e42" + integrity sha512-zFdGk8Lh9ZJGPxxPE6jwysOlATWB8GMW8HcfGULWA/nPal+3VdATflQvSBSLQJRCmYZnfFJl6vkRTiwJGNgPiQ== + dependencies: + jest-worker "^26.6.1" + p-limit "^3.0.2" + schema-utils "^3.0.0" + serialize-javascript "^5.0.1" + source-map "^0.6.1" + terser "^5.3.8" + +terser@^4.6.3: + version "4.8.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-4.8.0.tgz#63056343d7c70bb29f3af665865a46fe03a0df17" + integrity sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw== + dependencies: + commander "^2.20.0" + source-map "~0.6.1" + source-map-support "~0.5.12" + +terser@^5.3.5: + version "5.3.7" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.3.7.tgz#798a4ae2e7ff67050c3e99fcc4e00725827d97e2" + integrity sha512-lJbKdfxWvjpV330U4PBZStCT9h3N9A4zZVA5Y4k9sCWXknrpdyxi1oMsRKLmQ/YDMDxSBKIh88v0SkdhdqX06w== + dependencies: + commander "^2.20.0" + source-map "~0.7.2" + source-map-support "~0.5.19" + +terser@^5.3.8: + version "5.5.1" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.5.1.tgz#540caa25139d6f496fdea056e414284886fb2289" + integrity sha512-6VGWZNVP2KTUcltUQJ25TtNjx/XgdDsBDKGt8nN0MpydU36LmbPPcMBd2kmtZNNGVVDLg44k7GKeHHj+4zPIBQ== + dependencies: + commander "^2.20.0" + source-map "~0.7.2" + source-map-support "~0.5.19" + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= + +thunky@^1.0.2: + version "1.1.0" + resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d" + integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA== + +timsort@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" + integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= + +tiny-invariant@^1.0.2: + version "1.1.0" + resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875" + integrity sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw== + +tiny-warning@^1.0.0, tiny-warning@^1.0.2, tiny-warning@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" + integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== + +tinycolor2@^1.4.1: + version "1.4.2" + resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.4.2.tgz#3f6a4d1071ad07676d7fa472e1fac40a719d8803" + integrity sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA== + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= + +to-object-path@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" + integrity sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68= + dependencies: + kind-of "^3.0.2" + +to-regex-range@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" + integrity sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg= + dependencies: + is-number "^3.0.0" + repeat-string "^1.6.1" + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +to-regex@^3.0.1, to-regex@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" + integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw== + dependencies: + define-property "^2.0.2" + extend-shallow "^3.0.2" + regex-not "^1.0.2" + safe-regex "^1.1.0" + +toggle-selection@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32" + integrity sha1-bkWxJj8gF/oKzH2J14sVuL932jI= + +toidentifier@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" + integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== + +trim-newlines@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.0.tgz#79726304a6a898aa8373427298d54c2ee8b1cb30" + integrity sha512-C4+gOpvmxaSMKuEf9Qc134F1ZuOHVXKRbtEflf4NTtuuJDEIJ9p5PXsalL8SkeRw+qit1Mo+yuvMPAKwWg/1hA== + +trim-trailing-lines@^1.0.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/trim-trailing-lines/-/trim-trailing-lines-1.1.3.tgz#7f0739881ff76657b7776e10874128004b625a94" + integrity sha512-4ku0mmjXifQcTVfYDfR5lpgV7zVqPg6zV9rdZmwOPqq0+Zq19xDqEgagqVbc4pOOShbncuAOIs59R3+3gcF3ZA== + +trim@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/trim/-/trim-0.0.1.tgz#5858547f6b290757ee95cccc666fb50084c460dd" + integrity sha1-WFhUf2spB1fulczMZm+1AITEYN0= + +trough@^1.0.0: + version "1.0.5" + resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.5.tgz#b8b639cefad7d0bb2abd37d433ff8293efa5f406" + integrity sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA== + +ts-loader@^8.0.6: + version "8.0.6" + resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-8.0.6.tgz#8f47d203ef8fc95826a292a09f97a02bf1f57565" + integrity sha512-c8XkRbhKxFLbiIwZR7FBGWDq0MIz/QSpx3CGpj0abJxD5YVX8oDhQkJLeGbXUPRIlaX4Ajmr77fOiFVZ3gSU7g== + dependencies: + chalk "^2.3.0" + enhanced-resolve "^4.0.0" + loader-utils "^1.0.2" + micromatch "^4.0.0" + semver "^6.0.0" + +ts-morph@^8.1.2: + version "8.1.2" + resolved "https://registry.yarnpkg.com/ts-morph/-/ts-morph-8.1.2.tgz#f0bee6be6fbd8c3174ac5d6c0423a035e36c2bbd" + integrity sha512-5w4TzmMzECrBunIku1T6/Y0Y2IJ9sHT6hAlfF59WugXnsq1dv6DSioA+s6LgUiswxjgaJgZndOKgu8BrLcYjKw== + dependencies: + "@dsherret/to-absolute-glob" "^2.0.2" + "@ts-morph/common" "~0.6.0" + code-block-writer "^10.1.0" + +ts-node@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-9.0.0.tgz#e7699d2a110cc8c0d3b831715e417688683460b3" + integrity sha512-/TqB4SnererCDR/vb4S/QvSZvzQMJN8daAslg7MeaiHvD8rDZsSfXmNeNumyZZzMned72Xoq/isQljYSt8Ynfg== + dependencies: + arg "^4.1.0" + diff "^4.0.1" + make-error "^1.1.1" + source-map-support "^0.5.17" + yn "3.1.1" + +tsconfig-paths@^3.9.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz#098547a6c4448807e8fcb8eae081064ee9a3c90b" + integrity sha512-dRcuzokWhajtZWkQsDVKbWyY+jgcLC5sqJhg2PSgf4ZkH2aHPvaOY8YWGhmjb68b5qqTfasSsDO9k7RUiEmZAw== + dependencies: + "@types/json5" "^0.0.29" + json5 "^1.0.1" + minimist "^1.2.0" + strip-bom "^3.0.0" + +tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: + version "1.14.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + +tsutils@^3.17.1: + version "3.17.1" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759" + integrity sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g== + dependencies: + tslib "^1.8.1" + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +type-fest@^0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.13.1.tgz#0172cb5bce80b0bd542ea348db50c7e21834d934" + integrity sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg== + +type-fest@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" + integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg== + +type-fest@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" + integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== + +type-is@~1.6.17, type-is@~1.6.18: + version "1.6.18" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" + integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== + dependencies: + media-typer "0.3.0" + mime-types "~2.1.24" + +typedarray-to-buffer@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" + integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== + dependencies: + is-typedarray "^1.0.0" + +typescript@^4.0.3, typescript@~4.0.2: + version "4.0.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.0.3.tgz#153bbd468ef07725c1df9c77e8b453f8d36abba5" + integrity sha512-tEu6DGxGgRJPb/mVPIZ48e69xCn2yRmCgYmDugAVwmJ6o+0u1RI18eO7E7WBTLYLaEVVOhwQmcdhQHweux/WPg== + +typical@^5.0.0, typical@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/typical/-/typical-5.2.0.tgz#4daaac4f2b5315460804f0acf6cb69c52bb93066" + integrity sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg== + +unc-path-regex@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa" + integrity sha1-5z3T17DXxe2G+6xrCufYxqadUPo= + +unherit@^1.0.4: + version "1.1.3" + resolved "https://registry.yarnpkg.com/unherit/-/unherit-1.1.3.tgz#6c9b503f2b41b262330c80e91c8614abdaa69c22" + integrity sha512-Ft16BJcnapDKp0+J/rqFC3Rrk6Y/Ng4nzsC028k2jdDII/rdZ7Wd3pPT/6+vIIxRagwRc9K0IUX0Ra4fKvw+WQ== + dependencies: + inherits "^2.0.0" + xtend "^4.0.0" + +unified@^9.0.0: + version "9.2.0" + resolved "https://registry.yarnpkg.com/unified/-/unified-9.2.0.tgz#67a62c627c40589edebbf60f53edfd4d822027f8" + integrity sha512-vx2Z0vY+a3YoTj8+pttM3tiJHCwY5UFbYdiWrwBEbHmK8pvsPj2rtAX2BFfgXen8T39CJWblWRDT4L5WGXtDdg== + dependencies: + bail "^1.0.0" + extend "^3.0.0" + is-buffer "^2.0.0" + is-plain-obj "^2.0.0" + trough "^1.0.0" + vfile "^4.0.0" + +union-value@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" + integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg== + dependencies: + arr-union "^3.1.0" + get-value "^2.0.6" + is-extendable "^0.1.1" + set-value "^2.0.1" + +uniq@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" + integrity sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8= + +uniqs@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/uniqs/-/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02" + integrity sha1-/+3ks2slKQaW5uFl1KWe25mOawI= + +unique-filename@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230" + integrity sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ== + dependencies: + unique-slug "^2.0.0" + +unique-slug@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.2.tgz#baabce91083fc64e945b0f3ad613e264f7cd4e6c" + integrity sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w== + dependencies: + imurmurhash "^0.1.4" + +unist-util-find-all-after@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/unist-util-find-all-after/-/unist-util-find-all-after-3.0.1.tgz#95cc62f48812d879b4685a0512bf1b838da50e9a" + integrity sha512-0GICgc++sRJesLwEYDjFVJPJttBpVQaTNgc6Jw0Jhzvfs+jtKePEMu+uD+PqkRUrAvGQqwhpDwLGWo1PK8PDEw== + dependencies: + unist-util-is "^4.0.0" + +unist-util-is@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-4.0.2.tgz#c7d1341188aa9ce5b3cff538958de9895f14a5de" + integrity sha512-Ofx8uf6haexJwI1gxWMGg6I/dLnF2yE+KibhD3/diOqY2TinLcqHXCV6OI5gFVn3xQqDH+u0M625pfKwIwgBKQ== + +unist-util-remove-position@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/unist-util-remove-position/-/unist-util-remove-position-2.0.1.tgz#5d19ca79fdba712301999b2b73553ca8f3b352cc" + integrity sha512-fDZsLYIe2uT+oGFnuZmy73K6ZxOPG/Qcm+w7jbEjaFcJgbQ6cqjs/eSPzXhsmGpAsWPkqZM9pYjww5QTn3LHMA== + dependencies: + unist-util-visit "^2.0.0" + +unist-util-stringify-position@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz#cce3bfa1cdf85ba7375d1d5b17bdc4cada9bd9da" + integrity sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g== + dependencies: + "@types/unist" "^2.0.2" + +unist-util-visit-parents@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-3.1.1.tgz#65a6ce698f78a6b0f56aa0e88f13801886cdaef6" + integrity sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg== + dependencies: + "@types/unist" "^2.0.0" + unist-util-is "^4.0.0" + +unist-util-visit@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-2.0.3.tgz#c3703893146df47203bb8a9795af47d7b971208c" + integrity sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q== + dependencies: + "@types/unist" "^2.0.0" + unist-util-is "^4.0.0" + unist-util-visit-parents "^3.0.0" + +universalify@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" + integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== + +universalify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-1.0.0.tgz#b61a1da173e8435b2fe3c67d29b9adf8594bd16d" + integrity sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug== + +unpipe@1.0.0, unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= + +unquote@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/unquote/-/unquote-1.1.1.tgz#8fded7324ec6e88a0ff8b905e7c098cdc086d544" + integrity sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ= + +unset-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" + integrity sha1-g3aHP30jNRef+x5vw6jtDfyKtVk= + dependencies: + has-value "^0.3.1" + isobject "^3.0.0" + +upath@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" + integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg== + +uri-js@^4.2.2: + version "4.4.0" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.0.tgz#aa714261de793e8a82347a7bcc9ce74e86f28602" + integrity sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g== + dependencies: + punycode "^2.1.0" + +urix@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" + integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= + +url-loader@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-4.1.1.tgz#28505e905cae158cf07c92ca622d7f237e70a4e2" + integrity sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA== + dependencies: + loader-utils "^2.0.0" + mime-types "^2.1.27" + schema-utils "^3.0.0" + +url-parse@^1.4.3: + version "1.4.7" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278" + integrity sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg== + dependencies: + querystringify "^2.1.1" + requires-port "^1.0.0" + +url@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" + integrity sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE= + dependencies: + punycode "1.3.2" + querystring "0.2.0" + +use@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" + integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== + +util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + +util.promisify@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.0.tgz#440f7165a459c9a16dc145eb8e72f35687097030" + integrity sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA== + dependencies: + define-properties "^1.1.2" + object.getownpropertydescriptors "^2.0.3" + +util.promisify@~1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.1.tgz#6baf7774b80eeb0f7520d8b81d07982a59abbaee" + integrity sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.2" + has-symbols "^1.0.1" + object.getownpropertydescriptors "^2.1.0" + +utila@~0.4: + version "0.4.0" + resolved "https://registry.yarnpkg.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c" + integrity sha1-ihagXURWV6Oupe7MWxKk+lN5dyw= + +utils-merge@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" + integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= + +uuid@^3.3.2, uuid@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" + integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== + +v8-compile-cache@^2.0.3, v8-compile-cache@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz#54bc3cdd43317bca91e35dcaf305b1a7237de745" + integrity sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ== + +v8-compile-cache@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.2.0.tgz#9471efa3ef9128d2f7c6a7ca39c4dd6b5055b132" + integrity sha512-gTpR5XQNKFwOd4clxfnhaqvfqMpqEwr4tOtCyz4MtYZX2JYhfr1JvBFKdS+7K/9rfpZR3VLX+YWBbKoxCgS43Q== + +validate-npm-package-license@^3.0.1: + version "3.0.4" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" + integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== + dependencies: + spdx-correct "^3.0.0" + spdx-expression-parse "^3.0.0" + +value-equal@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-1.0.1.tgz#1e0b794c734c5c0cade179c437d356d931a34d6c" + integrity sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw== + +vary@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= + +vendors@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.4.tgz#e2b800a53e7a29b93506c3cf41100d16c4c4ad8e" + integrity sha512-/juG65kTL4Cy2su4P8HjtkTxk6VmJDiOPBufWniqQ6wknac6jNiXS9vU+hO3wgusiyqWlzTbVHi0dyJqRONg3w== + +vfile-location@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-3.1.0.tgz#81cd8a04b0ac935185f4fce16f270503fc2f692f" + integrity sha512-FCZ4AN9xMcjFIG1oGmZKo61PjwJHRVA+0/tPUP2ul4uIwjGGndIxavEMRpWn5p4xwm/ZsdXp9YNygf1ZyE4x8g== + +vfile-message@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-2.0.4.tgz#5b43b88171d409eae58477d13f23dd41d52c371a" + integrity sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ== + dependencies: + "@types/unist" "^2.0.0" + unist-util-stringify-position "^2.0.0" + +vfile@^4.0.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/vfile/-/vfile-4.2.0.tgz#26c78ac92eb70816b01d4565e003b7e65a2a0e01" + integrity sha512-a/alcwCvtuc8OX92rqqo7PflxiCgXRFjdyoGVuYV+qbgCb0GgZJRvIgCD4+U/Kl1yhaRsaTwksF88xbPyGsgpw== + dependencies: + "@types/unist" "^2.0.0" + is-buffer "^2.0.0" + replace-ext "1.0.0" + unist-util-stringify-position "^2.0.0" + vfile-message "^2.0.0" + +warning@^4.0.1, warning@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3" + integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w== + dependencies: + loose-envify "^1.0.0" + +watchpack@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.0.1.tgz#2f2192c542c82a3bcde76acd3411470c120426a8" + integrity sha512-vO8AKGX22ZRo6PiOFM9dC0re8IcKh8Kd/aH2zeqUc6w4/jBGlTy2P7fTC6ekT0NjVeGjgU2dGC5rNstKkeLEQg== + dependencies: + glob-to-regexp "^0.4.1" + graceful-fs "^4.1.2" + +wbuf@^1.1.0, wbuf@^1.7.3: + version "1.7.3" + resolved "https://registry.yarnpkg.com/wbuf/-/wbuf-1.7.3.tgz#c1d8d149316d3ea852848895cb6a0bfe887b87df" + integrity sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA== + dependencies: + minimalistic-assert "^1.0.0" + +webpack-cli@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-4.2.0.tgz#10a09030ad2bd4d8b0f78322fba6ea43ec56aaaa" + integrity sha512-EIl3k88vaF4fSxWSgtAQR+VwicfLMTZ9amQtqS4o+TDPW9HGaEpbFBbAZ4A3ZOT5SOnMxNOzROsSTPiE8tBJPA== + dependencies: + "@webpack-cli/info" "^1.1.0" + "@webpack-cli/serve" "^1.1.0" + colorette "^1.2.1" + command-line-usage "^6.1.0" + commander "^6.2.0" + enquirer "^2.3.6" + execa "^4.1.0" + import-local "^3.0.2" + interpret "^2.2.0" + leven "^3.1.0" + rechoir "^0.7.0" + v8-compile-cache "^2.2.0" + webpack-merge "^4.2.2" + +webpack-dev-middleware@^3.7.2: + version "3.7.2" + resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.7.2.tgz#0019c3db716e3fa5cecbf64f2ab88a74bab331f3" + integrity sha512-1xC42LxbYoqLNAhV6YzTYacicgMZQTqRd27Sim9wn5hJrX3I5nxYy1SxSd4+gjUFsz1dQFj+yEe6zEVmSkeJjw== + dependencies: + memory-fs "^0.4.1" + mime "^2.4.4" + mkdirp "^0.5.1" + range-parser "^1.2.1" + webpack-log "^2.0.0" + +webpack-dev-server@^3.11.0: + version "3.11.0" + resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-3.11.0.tgz#8f154a3bce1bcfd1cc618ef4e703278855e7ff8c" + integrity sha512-PUxZ+oSTxogFQgkTtFndEtJIPNmml7ExwufBZ9L2/Xyyd5PnOL5UreWe5ZT7IU25DSdykL9p1MLQzmLh2ljSeg== + dependencies: + ansi-html "0.0.7" + bonjour "^3.5.0" + chokidar "^2.1.8" + compression "^1.7.4" + connect-history-api-fallback "^1.6.0" + debug "^4.1.1" + del "^4.1.1" + express "^4.17.1" + html-entities "^1.3.1" + http-proxy-middleware "0.19.1" + import-local "^2.0.0" + internal-ip "^4.3.0" + ip "^1.1.5" + is-absolute-url "^3.0.3" + killable "^1.0.1" + loglevel "^1.6.8" + opn "^5.5.0" + p-retry "^3.0.1" + portfinder "^1.0.26" + schema-utils "^1.0.0" + selfsigned "^1.10.7" + semver "^6.3.0" + serve-index "^1.9.1" + sockjs "0.3.20" + sockjs-client "1.4.0" + spdy "^4.0.2" + strip-ansi "^3.0.1" + supports-color "^6.1.0" + url "^0.11.0" + webpack-dev-middleware "^3.7.2" + webpack-log "^2.0.0" + ws "^6.2.1" + yargs "^13.3.2" + +webpack-log@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/webpack-log/-/webpack-log-2.0.0.tgz#5b7928e0637593f119d32f6227c1e0ac31e1b47f" + integrity sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg== + dependencies: + ansi-colors "^3.0.0" + uuid "^3.3.2" + +webpack-merge@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-4.2.2.tgz#a27c52ea783d1398afd2087f547d7b9d2f43634d" + integrity sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g== + dependencies: + lodash "^4.17.15" + +webpack-merge@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.2.0.tgz#31cbcc954f8f89cd4b06ca8d97a38549f7f3f0c9" + integrity sha512-QBglJBg5+lItm3/Lopv8KDDK01+hjdg2azEwi/4vKJ8ZmGPdtJsTpjtNNOW3a4WiqzXdCATtTudOZJngE7RKkA== + dependencies: + clone-deep "^4.0.1" + wildcard "^2.0.0" + +webpack-sources@^1.1.0, webpack-sources@^1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933" + integrity sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ== + dependencies: + source-list-map "^2.0.0" + source-map "~0.6.1" + +webpack-sources@^2.1.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-2.2.0.tgz#058926f39e3d443193b6c31547229806ffd02bac" + integrity sha512-bQsA24JLwcnWGArOKUxYKhX3Mz/nK1Xf6hxullKERyktjNMC4x8koOeaDNTA2fEJ09BdWLbM/iTW0ithREUP0w== + dependencies: + source-list-map "^2.0.1" + source-map "^0.6.1" + +webpack@^5.10.0: + version "5.10.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.10.0.tgz#6f77c31522a2c525152d9c344f9765d168b3df08" + integrity sha512-P0bHAXmIz0zsNcHNLqFmLY1ZtrT+jtBr7FqpuDtA2o7GiHC+zBsfhgK7SmJ1HG7BAEb3G9JoMdSVi7mEDvG3Zg== + dependencies: + "@types/eslint-scope" "^3.7.0" + "@types/estree" "^0.0.45" + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/helper-module-context" "1.9.0" + "@webassemblyjs/wasm-edit" "1.9.0" + "@webassemblyjs/wasm-parser" "1.9.0" + acorn "^8.0.4" + browserslist "^4.14.5" + chrome-trace-event "^1.0.2" + enhanced-resolve "^5.3.1" + eslint-scope "^5.1.1" + events "^3.2.0" + glob-to-regexp "^0.4.1" + graceful-fs "^4.2.4" + json-parse-better-errors "^1.0.2" + loader-runner "^4.1.0" + mime-types "^2.1.27" + neo-async "^2.6.2" + pkg-dir "^5.0.0" + schema-utils "^3.0.0" + tapable "^2.1.1" + terser-webpack-plugin "^5.0.3" + watchpack "^2.0.0" + webpack-sources "^2.1.1" + +websocket-driver@0.6.5: + version "0.6.5" + resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.6.5.tgz#5cb2556ceb85f4373c6d8238aa691c8454e13a36" + integrity sha1-XLJVbOuF9Dc8bYI4qmkchFThOjY= + dependencies: + websocket-extensions ">=0.1.1" + +websocket-driver@>=0.5.1: + version "0.7.4" + resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760" + integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg== + dependencies: + http-parser-js ">=0.5.1" + safe-buffer ">=5.1.0" + websocket-extensions ">=0.1.1" + +websocket-extensions@>=0.1.1: + version "0.1.4" + resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" + integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== + +which-module@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" + integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= + +which-pm-runs@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/which-pm-runs/-/which-pm-runs-1.0.0.tgz#670b3afbc552e0b55df6b7780ca74615f23ad1cb" + integrity sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs= + +which@^1.2.9, which@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +wildcard@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.0.tgz#a77d20e5200c6faaac979e4b3aadc7b3dd7f8fec" + integrity sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw== + +word-wrap@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" + integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + +wordwrapjs@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/wordwrapjs/-/wordwrapjs-4.0.0.tgz#9aa9394155993476e831ba8e59fb5795ebde6800" + integrity sha512-Svqw723a3R34KvsMgpjFBYCgNOSdcW3mQFK4wIfhGQhtaFVOJmdYoXgi63ne3dTlWgatVcUc7t4HtQ/+bUVIzQ== + dependencies: + reduce-flatten "^2.0.0" + typical "^5.0.0" + +wrap-ansi@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" + integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q== + dependencies: + ansi-styles "^3.2.0" + string-width "^3.0.0" + strip-ansi "^5.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +write-file-atomic@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" + integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== + dependencies: + imurmurhash "^0.1.4" + is-typedarray "^1.0.0" + signal-exit "^3.0.2" + typedarray-to-buffer "^3.1.5" + +write@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3" + integrity sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig== + dependencies: + mkdirp "^0.5.1" + +ws@^6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb" + integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA== + dependencies: + async-limiter "~1.0.0" + +xtend@^4.0.0, xtend@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== + +y18n@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.1.tgz#8db2b83c31c5d75099bb890b23f3094891e247d4" + integrity sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +yaml@^1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.0.tgz#3b593add944876077d4d683fee01081bd9fff31e" + integrity sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg== + +yargs-parser@^13.1.2: + version "13.1.2" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38" + integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg== + dependencies: + camelcase "^5.0.0" + decamelize "^1.2.0" + +yargs-parser@^18.1.3: + version "18.1.3" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" + integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== + dependencies: + camelcase "^5.0.0" + decamelize "^1.2.0" + +yargs@^13.3.2: + version "13.3.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd" + integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw== + dependencies: + cliui "^5.0.0" + find-up "^3.0.0" + get-caller-file "^2.0.1" + require-directory "^2.1.1" + require-main-filename "^2.0.0" + set-blocking "^2.0.0" + string-width "^3.0.0" + which-module "^2.0.0" + y18n "^4.0.0" + yargs-parser "^13.1.2" + +yn@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== diff --git a/internal/home/config.go b/internal/home/config.go index ed81c56e..92b2326c 100644 --- a/internal/home/config.go +++ b/internal/home/config.go @@ -39,13 +39,14 @@ type configuration struct { // It's reset after config is parsed fileData []byte - BindHost string `yaml:"bind_host"` // BindHost is the IP address of the HTTP server to bind to - BindPort int `yaml:"bind_port"` // BindPort is the port the HTTP server - Users []User `yaml:"users"` // Users that can access HTTP server - ProxyURL string `yaml:"http_proxy"` // Proxy address for our HTTP client - Language string `yaml:"language"` // two-letter ISO 639-1 language code - RlimitNoFile uint `yaml:"rlimit_nofile"` // Maximum number of opened fd's per process (0: default) - DebugPProf bool `yaml:"debug_pprof"` // Enable pprof HTTP server on port 6060 + BindHost string `yaml:"bind_host"` // BindHost is the IP address of the HTTP server to bind to + BindPort int `yaml:"bind_port"` // BindPort is the port the HTTP server + BetaBindPort int `yaml:"beta_bind_port"` // BetaBindPort is the port for new client + Users []User `yaml:"users"` // Users that can access HTTP server + ProxyURL string `yaml:"http_proxy"` // Proxy address for our HTTP client + Language string `yaml:"language"` // two-letter ISO 639-1 language code + RlimitNoFile uint `yaml:"rlimit_nofile"` // Maximum number of opened fd's per process (0: default) + DebugPProf bool `yaml:"debug_pprof"` // Enable pprof HTTP server on port 6060 // TTL for a web session (in hours) // An active session is automatically refreshed once a day. @@ -117,8 +118,9 @@ type tlsConfigSettings struct { // initialize to default values, will be changed later when reading config or parsing command line var config = configuration{ - BindPort: 3000, - BindHost: "0.0.0.0", + BindPort: 3000, + BetaBindPort: 0, + BindHost: "0.0.0.0", DNS: dnsConfig{ BindHost: "0.0.0.0", Port: 53, @@ -174,6 +176,10 @@ func initConfig() { config.DHCP.Conf4.LeaseDuration = 86400 config.DHCP.Conf4.ICMPTimeout = 1000 config.DHCP.Conf6.LeaseDuration = 86400 + + if updateChannel == "none" || updateChannel == "edge" { + config.BetaBindPort = 3001 + } } // getConfigFilename returns path to the current config file diff --git a/internal/home/control.go b/internal/home/control.go index 616557a8..0bdd8aab 100644 --- a/internal/home/control.go +++ b/internal/home/control.go @@ -219,7 +219,7 @@ func postInstall(handler func(http.ResponseWriter, *http.Request)) func(http.Res // construct new URL to redirect to newURL := url.URL{ Scheme: "https", - Host: net.JoinHostPort(host, strconv.Itoa(Context.web.portHTTPS)), + Host: net.JoinHostPort(host, strconv.Itoa(Context.web.conf.PortHTTPS)), Path: r.URL.Path, RawQuery: r.URL.RawQuery, } diff --git a/internal/home/controlfiltering.go b/internal/home/controlfiltering.go index 65e023b2..53ca0445 100644 --- a/internal/home/controlfiltering.go +++ b/internal/home/controlfiltering.go @@ -369,7 +369,7 @@ type checkHostResp struct { // for FilteredBlockedService: SvcName string `json:"service_name"` - // for Rewritten: + // for Rewrite: CanonName string `json:"cname"` // CNAME value IPList []net.IP `json:"ip_addrs"` // list of IP addresses } diff --git a/internal/home/controlinstall.go b/internal/home/controlinstall.go index ed3cdd38..3920af3c 100644 --- a/internal/home/controlinstall.go +++ b/internal/home/controlinstall.go @@ -12,6 +12,7 @@ import ( "path/filepath" "runtime" "strconv" + "strings" "github.com/AdguardTeam/AdGuardHome/internal/util" @@ -105,7 +106,7 @@ func (web *Web) handleInstallCheckConfig(w http.ResponseWriter, r *http.Request) return } - if reqData.Web.Port != 0 && reqData.Web.Port != config.BindPort { + if reqData.Web.Port != 0 && reqData.Web.Port != config.BindPort && reqData.Web.Port != config.BetaBindPort { err = util.CheckPortAvailable(reqData.Web.IP, reqData.Web.Port) if err != nil { respData.Web.Status = fmt.Sprintf("%v", err) @@ -276,6 +277,7 @@ type applyConfigReq struct { func copyInstallSettings(dst, src *configuration) { dst.BindHost = src.BindHost dst.BindPort = src.BindPort + dst.BetaBindPort = src.BetaBindPort dst.DNS.BindHost = src.DNS.BindHost dst.DNS.Port = src.DNS.Port } @@ -308,6 +310,7 @@ func (web *Web) handleInstallConfigure(w http.ResponseWriter, r *http.Request) { net.JoinHostPort(newSettings.Web.IP, strconv.Itoa(newSettings.Web.Port)), err) return } + } err = util.CheckPacketPortAvailable(newSettings.DNS.IP, newSettings.DNS.Port) @@ -366,7 +369,10 @@ func (web *Web) handleInstallConfigure(w http.ResponseWriter, r *http.Request) { // until all requests are finished, and _we_ are inside a request right now, so it will block indefinitely if restartHTTP { go func() { - _ = Context.web.httpServer.Shutdown(context.TODO()) + _ = web.httpServer.Shutdown(context.TODO()) + }() + go func() { + _ = web.httpServerBeta.Shutdown(context.TODO()) }() } } @@ -376,3 +382,196 @@ func (web *Web) registerInstallHandlers() { Context.mux.HandleFunc("/control/install/check_config", preInstall(ensurePOST(web.handleInstallCheckConfig))) Context.mux.HandleFunc("/control/install/configure", preInstall(ensurePOST(web.handleInstallConfigure))) } + +// checkConfigReqEntBeta is a struct representing new client's config check +// request entry. It supports multiple IP values unlike the checkConfigReqEnt. +// +// TODO(e.burkov): this should removed with the API v1 when the appropriate +// functionality will appear in default checkConfigReqEnt. +type checkConfigReqEntBeta struct { + Port int `json:"port"` + IP []string `json:"ip"` + Autofix bool `json:"autofix"` +} + +// checkConfigReqBeta is a struct representing new client's config check request +// body. It uses checkConfigReqEntBeta instead of checkConfigReqEnt. +// +// TODO(e.burkov): this should removed with the API v1 when the appropriate +// functionality will appear in default checkConfigReq. +type checkConfigReqBeta struct { + Web checkConfigReqEntBeta `json:"web"` + DNS checkConfigReqEntBeta `json:"dns"` + SetStaticIP bool `json:"set_static_ip"` +} + +// handleInstallCheckConfigBeta is a substitution of /install/check_config +// handler for new client. +// +// TODO(e.burkov): this should removed with the API v1 when the appropriate +// functionality will appear in default handleInstallCheckConfig. +func (web *Web) handleInstallCheckConfigBeta(w http.ResponseWriter, r *http.Request) { + reqData := checkConfigReqBeta{} + err := json.NewDecoder(r.Body).Decode(&reqData) + if err != nil { + httpError(w, http.StatusBadRequest, "Failed to parse 'check_config' JSON data: %s", err) + return + } + + if len(reqData.DNS.IP) == 0 || len(reqData.Web.IP) == 0 { + httpError(w, http.StatusBadRequest, http.StatusText(http.StatusBadRequest)) + return + } + + nonBetaReqData := checkConfigReq{ + Web: checkConfigReqEnt{ + Port: reqData.Web.Port, + IP: reqData.Web.IP[0], + Autofix: reqData.Web.Autofix, + }, + DNS: checkConfigReqEnt{ + Port: reqData.DNS.Port, + IP: reqData.DNS.IP[0], + Autofix: reqData.DNS.Autofix, + }, + SetStaticIP: reqData.SetStaticIP, + } + + nonBetaReqBody := &strings.Builder{} + + err = json.NewEncoder(nonBetaReqBody).Encode(nonBetaReqData) + if err != nil { + httpError(w, http.StatusBadRequest, "Failed to encode 'check_config' JSON data: %s", err) + return + } + body := nonBetaReqBody.String() + r.Body = ioutil.NopCloser(strings.NewReader(body)) + r.ContentLength = int64(len(body)) + + web.handleInstallCheckConfig(w, r) +} + +// applyConfigReqEntBeta is a struct representing new client's config setting +// request entry. It supports multiple IP values unlike the applyConfigReqEnt. +// +// TODO(e.burkov): this should removed with the API v1 when the appropriate +// functionality will appear in default applyConfigReqEnt. +type applyConfigReqEntBeta struct { + IP []string `json:"ip"` + Port int `json:"port"` +} + +// applyConfigReqBeta is a struct representing new client's config setting +// request body. It uses applyConfigReqEntBeta instead of applyConfigReqEnt. +// +// TODO(e.burkov): this should removed with the API v1 when the appropriate +// functionality will appear in default applyConfigReq. +type applyConfigReqBeta struct { + Web applyConfigReqEntBeta `json:"web"` + DNS applyConfigReqEntBeta `json:"dns"` + Username string `json:"username"` + Password string `json:"password"` +} + +// handleInstallConfigureBeta is a substitution of /install/configure handler +// for new client. +// +// TODO(e.burkov): this should removed with the API v1 when the appropriate +// functionality will appear in default handleInstallConfigure. +func (web *Web) handleInstallConfigureBeta(w http.ResponseWriter, r *http.Request) { + reqData := applyConfigReqBeta{} + err := json.NewDecoder(r.Body).Decode(&reqData) + if err != nil { + httpError(w, http.StatusBadRequest, "Failed to parse 'check_config' JSON data: %s", err) + return + } + + if len(reqData.DNS.IP) == 0 || len(reqData.Web.IP) == 0 { + httpError(w, http.StatusBadRequest, http.StatusText(http.StatusBadRequest)) + return + } + + nonBetaReqData := applyConfigReq{ + Web: applyConfigReqEnt{ + IP: reqData.Web.IP[0], + Port: reqData.Web.Port, + }, + DNS: applyConfigReqEnt{ + IP: reqData.DNS.IP[0], + Port: reqData.DNS.Port, + }, + Username: reqData.Username, + Password: reqData.Password, + } + + nonBetaReqBody := &strings.Builder{} + + err = json.NewEncoder(nonBetaReqBody).Encode(nonBetaReqData) + if err != nil { + httpError(w, http.StatusBadRequest, "Failed to encode 'check_config' JSON data: %s", err) + return + } + body := nonBetaReqBody.String() + r.Body = ioutil.NopCloser(strings.NewReader(body)) + r.ContentLength = int64(len(body)) + + web.handleInstallConfigure(w, r) +} + +// firstRunDataBeta is a struct representing new client's getting addresses +// request body. It uses array of structs instead of map. +// +// TODO(e.burkov): this should removed with the API v1 when the appropriate +// functionality will appear in default firstRunData. +type firstRunDataBeta struct { + WebPort int `json:"web_port"` + DNSPort int `json:"dns_port"` + Interfaces []netInterfaceJSON `json:"interfaces"` +} + +// handleInstallConfigureBeta is a substitution of /install/get_addresses +// handler for new client. +// +// TODO(e.burkov): this should removed with the API v1 when the appropriate +// functionality will appear in default handleInstallGetAddresses. +func (web *Web) handleInstallGetAddressesBeta(w http.ResponseWriter, r *http.Request) { + data := firstRunDataBeta{} + data.WebPort = 80 + data.DNSPort = 53 + + ifaces, err := util.GetValidNetInterfacesForWeb() + if err != nil { + httpError(w, http.StatusInternalServerError, "Couldn't get interfaces: %s", err) + return + } + + data.Interfaces = make([]netInterfaceJSON, 0, len(ifaces)) + for _, iface := range ifaces { + ifaceJSON := netInterfaceJSON{ + Name: iface.Name, + MTU: iface.MTU, + HardwareAddr: iface.HardwareAddr, + Addresses: iface.Addresses, + Flags: iface.Flags, + } + data.Interfaces = append(data.Interfaces, ifaceJSON) + } + + w.Header().Set("Content-Type", "application/json") + err = json.NewEncoder(w).Encode(data) + if err != nil { + httpError(w, http.StatusInternalServerError, "Unable to marshal default addresses to json: %s", err) + return + } +} + +// registerBetaInstallHandlers registers the install handlers for new client +// with the structures it supports. +// +// TODO(e.burkov): this should removed with the API v1 when the appropriate +// functionality will appear in default handlers. +func (web *Web) registerBetaInstallHandlers() { + Context.mux.HandleFunc("/control/install/get_addresses_beta", preInstall(ensureGET(web.handleInstallGetAddressesBeta))) + Context.mux.HandleFunc("/control/install/check_config_beta", preInstall(ensurePOST(web.handleInstallCheckConfigBeta))) + Context.mux.HandleFunc("/control/install/configure_beta", preInstall(ensurePOST(web.handleInstallConfigureBeta))) +} diff --git a/internal/home/home.go b/internal/home/home.go index 58c99ff6..aeb875a4 100644 --- a/internal/home/home.go +++ b/internal/home/home.go @@ -316,9 +316,10 @@ func run(args options) { } webConf := webConfig{ - firstRun: Context.firstRun, - BindHost: config.BindHost, - BindPort: config.BindPort, + firstRun: Context.firstRun, + BindHost: config.BindHost, + BindPort: config.BindPort, + BetaBindPort: config.BetaBindPort, ReadTimeout: ReadTimeout, ReadHeaderTimeout: ReadHeaderTimeout, @@ -592,8 +593,6 @@ func loadOptions() options { // prints IP addresses which user can use to open the admin interface // proto is either "http" or "https" func printHTTPAddresses(proto string) { - var address string - tlsConf := tlsConfigSettings{} if Context.tls != nil { Context.tls.WriteDiskConfig(&tlsConf) @@ -615,20 +614,26 @@ func printHTTPAddresses(proto string) { ifaces, err := util.GetValidNetInterfacesForWeb() if err != nil { // That's weird, but we'll ignore it - address = net.JoinHostPort(config.BindHost, port) - log.Printf("Go to %s://%s", proto, address) + log.Printf("Go to %s://%s", proto, net.JoinHostPort(config.BindHost, port)) + if config.BetaBindPort != 0 { + log.Printf("Go to %s://%s (BETA)", proto, net.JoinHostPort(config.BindHost, strconv.Itoa(config.BetaBindPort))) + } return } for _, iface := range ifaces { for _, addr := range iface.Addresses { - address = net.JoinHostPort(addr, strconv.Itoa(config.BindPort)) - log.Printf("Go to %s://%s", proto, address) + log.Printf("Go to %s://%s", proto, net.JoinHostPort(addr, strconv.Itoa(config.BindPort))) + if config.BetaBindPort != 0 { + log.Printf("Go to %s://%s (BETA)", proto, net.JoinHostPort(addr, strconv.Itoa(config.BetaBindPort))) + } } } } else { - address = net.JoinHostPort(config.BindHost, port) - log.Printf("Go to %s://%s", proto, address) + log.Printf("Go to %s://%s", proto, net.JoinHostPort(config.BindHost, port)) + if config.BetaBindPort != 0 { + log.Printf("Go to %s://%s (BETA)", proto, net.JoinHostPort(config.BindHost, strconv.Itoa(config.BetaBindPort))) + } } } diff --git a/internal/home/middlewares.go b/internal/home/middlewares.go index a5758985..d530ace8 100644 --- a/internal/home/middlewares.go +++ b/internal/home/middlewares.go @@ -40,3 +40,18 @@ func limitRequestBody(h http.Handler) (limited http.Handler) { h.ServeHTTP(w, r) }) } + +// wrapIndexBeta returns handler that deals with new client. +func (web *Web) wrapIndexBeta(http.Handler) (wrapped http.Handler) { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + h, pattern := Context.mux.Handler(r) + switch pattern { + case "/": + web.handlerBeta.ServeHTTP(w, r) + case "/install.html": + web.installerBeta.ServeHTTP(w, r) + default: + h.ServeHTTP(w, r) + } + }) +} diff --git a/internal/home/web.go b/internal/home/web.go index 97f06979..83fe9db4 100644 --- a/internal/home/web.go +++ b/internal/home/web.go @@ -30,10 +30,11 @@ const ( ) type webConfig struct { - firstRun bool - BindHost string - BindPort int - PortHTTPS int + firstRun bool + BindHost string + BindPort int + BetaBindPort int + PortHTTPS int // ReadTimeout is an option to pass to http.Server for setting an // appropriate field. @@ -62,9 +63,16 @@ type HTTPSServer struct { type Web struct { conf *webConfig forceHTTPS bool - portHTTPS int httpServer *http.Server // HTTP module httpsServer HTTPSServer // HTTPS module + + // handlerBeta is the handler for new client. + handlerBeta http.Handler + // installerBeta is the pre-install handler for new client. + installerBeta http.Handler + + // httpServerBeta is a server for new client. + httpServerBeta *http.Server } // CreateWeb - create module @@ -76,15 +84,20 @@ func CreateWeb(conf *webConfig) *Web { // Initialize and run the admin Web interface box := packr.NewBox("../../build/static") + boxBeta := packr.NewBox("../../build2/static") // if not configured, redirect / to /install.html, otherwise redirect /install.html to / - Context.mux.Handle("/", postInstallHandler(optionalAuthHandler(gziphandler.GzipHandler(http.FileServer(box))))) + Context.mux.Handle("/", withMiddlewares(http.FileServer(box), gziphandler.GzipHandler, optionalAuthHandler, postInstallHandler)) + w.handlerBeta = withMiddlewares(http.FileServer(boxBeta), gziphandler.GzipHandler, optionalAuthHandler, postInstallHandler) // add handlers for /install paths, we only need them when we're not configured yet if conf.firstRun { log.Info("This is the first launch of AdGuard Home, redirecting everything to /install.html ") Context.mux.Handle("/install.html", preInstallHandler(http.FileServer(box))) + w.installerBeta = preInstallHandler(http.FileServer(boxBeta)) w.registerInstallHandlers() + // This must be removed in API v1. + w.registerBetaInstallHandlers() } else { registerControlHandlers() } @@ -114,7 +127,6 @@ func (web *Web) TLSConfigChanged(tlsConf tlsConfigSettings) { log.Debug("Web: applying new TLS configuration") web.conf.PortHTTPS = tlsConf.PortHTTPS web.forceHTTPS = (tlsConf.ForceHTTPS && tlsConf.Enabled && tlsConf.PortHTTPS != 0) - web.portHTTPS = tlsConf.PortHTTPS enabled := tlsConf.Enabled && tlsConf.PortHTTPS != 0 && @@ -147,19 +159,36 @@ func (web *Web) Start() { // this loop is used as an ability to change listening host and/or port for !web.httpsServer.shutdown { printHTTPAddresses("http") + errs := make(chan error, 2) // we need to have new instance, because after Shutdown() the Server is not usable - address := net.JoinHostPort(web.conf.BindHost, strconv.Itoa(web.conf.BindPort)) web.httpServer = &http.Server{ ErrorLog: log.StdLog("web: http", log.DEBUG), - Addr: address, + Addr: net.JoinHostPort(web.conf.BindHost, strconv.Itoa(web.conf.BindPort)), Handler: withMiddlewares(Context.mux, limitRequestBody), ReadTimeout: web.conf.ReadTimeout, ReadHeaderTimeout: web.conf.ReadHeaderTimeout, WriteTimeout: web.conf.WriteTimeout, } + go func() { + errs <- web.httpServer.ListenAndServe() + }() - err := web.httpServer.ListenAndServe() + if web.conf.BetaBindPort != 0 { + web.httpServerBeta = &http.Server{ + ErrorLog: log.StdLog("web: http", log.DEBUG), + Addr: net.JoinHostPort(web.conf.BindHost, strconv.Itoa(web.conf.BetaBindPort)), + Handler: withMiddlewares(Context.mux, limitRequestBody, web.wrapIndexBeta), + ReadTimeout: web.conf.ReadTimeout, + ReadHeaderTimeout: web.conf.ReadHeaderTimeout, + WriteTimeout: web.conf.WriteTimeout, + } + go func() { + errs <- web.httpServerBeta.ListenAndServe() + }() + } + + err := <-errs if err != http.ErrServerClosed { cleanupAlways() log.Fatal(err) @@ -180,6 +209,9 @@ func (web *Web) Close() { if web.httpServer != nil { _ = web.httpServer.Shutdown(context.TODO()) } + if web.httpServerBeta != nil { + _ = web.httpServerBeta.Shutdown(context.TODO()) + } log.Info("Stopped HTTP server") } diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index 8b3ef126..ab1eac69 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -103,6 +103,8 @@ server works, any other text means an error. 'content': 'application/json': + 'schema': + '$ref': '#/components/schemas/UpstreamsConfigResponse' 'examples': 'response': 'value': @@ -604,6 +606,11 @@ 'description': 'OK.' 'content': 'application/json': + 'schema': + 'type': 'object' + 'parameters': + 'enabled': + 'type': 'boolean' 'examples': 'response': 'value': @@ -655,6 +662,13 @@ 'description': 'OK.' 'content': 'application/json': + 'schema': + 'type': 'object' + 'parameters': + 'enable': + 'type': 'boolean' + 'sensitivity': + 'type': 'integer' 'examples': 'response': 'value': @@ -689,6 +703,11 @@ 'description': 'OK.' 'content': 'application/json': + 'schema': + 'type': 'object' + 'parameters': + 'enabled': + 'type': 'boolean' 'examples': 'response': 'value': @@ -867,6 +886,20 @@ 'examples': 'response': 'value': 'en' + '/install/get_addresses_beta': + 'get': + 'tags': + - 'install' + 'operationId': 'installGetAddressesBeta' + 'summary': > + 'UNSTABLE!: Gets the network interfaces information.' + 'responses': + '200': + 'description': 'OK.' + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/AddressesInfoBeta' '/install/get_addresses': 'get': 'tags': @@ -880,6 +913,30 @@ 'application/json': 'schema': '$ref': '#/components/schemas/AddressesInfo' + '/install/check_config_beta': + 'post': + 'tags': + - 'install' + 'operationId': 'installCheckConfigBeta' + 'summary': > + 'UNSTABLE!: Checks configuration' + 'requestBody': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/CheckConfigRequestBeta' + 'description': 'Configuration to be checked' + 'required': true + 'responses': + '200': + 'description': 'OK.' + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/CheckConfigResponse' + '400': + 'description': > + Failed to parse JSON or cannot listen on the specified address. '/install/check_config': 'post': 'tags': @@ -903,6 +960,29 @@ '400': 'description': > Failed to parse JSON or cannot listen on the specified address. + '/install/configure_beta': + 'post': + 'tags': + - 'install' + 'operationId': 'installConfigureBeta' + 'summary': > + 'UNSTABLE!: Applies the initial configuration.' + 'requestBody': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/InitialConfigurationBeta' + 'description': 'Initial configuration JSON' + 'required': true + 'responses': + '200': + 'description': 'OK.' + '400': + 'description': > + Failed to parse initial configuration or cannot listen to the + specified addresses. + '500': + 'description': 'Cannot start the DNS server' '/install/configure': 'post': 'tags': @@ -1059,7 +1139,7 @@ 'example': '127.0.0.1' 'dns_port': 'type': 'integer' - 'format': 'int32' + 'format': 'uint16' 'example': 53 'minimum': 1 'maximum': 65535 @@ -1163,6 +1243,11 @@ 'example': - 'tls://1.1.1.1' - 'tls://1.0.0.1' + 'UpstreamsConfigResponse': + 'type': 'object' + 'description': 'Upstreams configuration response' + 'additionalProperties': + 'type': 'string' 'Filter': 'type': 'object' 'description': 'Filter subscription info' @@ -1733,17 +1818,17 @@ 'description': 'if true, forces HTTP->HTTPS redirect' 'port_https': 'type': 'integer' - 'format': 'int32' + 'format': 'uint16' 'example': 443 'description': 'HTTPS port. If 0, HTTPS will be disabled.' 'port_dns_over_tls': 'type': 'integer' - 'format': 'int32' + 'format': 'uint16' 'example': 853 'description': 'DNS-over-TLS port. If 0, DOT will be disabled.' 'port_dns_over_quic': 'type': 'integer' - 'format': 'int32' + 'format': 'uint16' 'example': 784 'description': 'DNS-over-QUIC port. If 0, DOQ will be disabled.' 'certificate_chain': @@ -1821,6 +1906,11 @@ 'NetInterface': 'type': 'object' 'description': 'Network interface info' + 'required': + - 'flags' + - 'hardware_address' + - 'name' + - 'mtu' 'properties': 'flags': 'type': 'string' @@ -1831,38 +1921,59 @@ 'name': 'type': 'string' 'example': 'eth0' - 'ipv4_addresses': + 'ip_addresses': 'type': 'array' 'items': 'type': 'string' - 'ipv6_addresses': + 'mtu': + 'type': 'integer' + 'AddressInfoBeta': + 'type': 'object' + 'description': 'Port information' + 'required': + - 'ip' + - 'port' + 'properties': + 'ip': 'type': 'array' 'items': 'type': 'string' - 'gateway_ip': - 'type': 'string' + 'minItems': 1 + 'example': + - '127.0.0.1' + 'port': + 'type': 'integer' + 'format': 'uint16' + 'example': 53 'AddressInfo': 'type': 'object' 'description': 'Port information' + 'required': + - 'ip' + - 'port' 'properties': 'ip': 'type': 'string' 'example': '127.0.0.1' 'port': 'type': 'integer' - 'format': 'int32' + 'format': 'uint16' 'example': 53 'AddressesInfo': 'type': 'object' 'description': 'AdGuard Home addresses configuration' + 'required': + - 'dns_port' + - 'web_port' + - 'interfaces' 'properties': 'dns_port': 'type': 'integer' - 'format': 'int32' + 'format': 'uint16' 'example': 53 'web_port': 'type': 'integer' - 'format': 'int32' + 'format': 'uint16' 'example': 80 'interfaces': 'type': 'object' @@ -1870,6 +1981,28 @@ Network interfaces dictionary, keys are interface names. 'additionalProperties': '$ref': '#/components/schemas/NetInterface' + 'AddressesInfoBeta': + 'type': 'object' + 'description': 'AdGuard Home addresses configuration' + 'required': + - 'dns_port' + - 'web_port' + - 'interfaces' + 'properties': + 'dns_port': + 'type': 'integer' + 'format': 'uint16' + 'example': 53 + 'web_port': + 'type': 'integer' + 'format': 'uint16' + 'example': 80 + 'interfaces': + 'type': 'array' + 'description': > + Network interfaces dictionary, keys are interface names. + 'items': + '$ref': '#/components/schemas/NetInterface' 'ProfileInfo': 'type': 'object' 'description': 'Information about the current user' @@ -1946,11 +2079,10 @@ '$ref': '#/components/schemas/ClientsFindEntry' 'ClientsFindEntry': 'type': 'object' - 'properties': - '1.2.3.4': - 'items': - '$ref': '#/components/schemas/ClientFindSubEntry' - + 'additionalProperties': + '$ref': '#/components/schemas/ClientFindSubEntry' + 'example': + '1.2.3.4': 'test' 'ClientFindSubEntry': 'type': 'object' 'properties': @@ -2043,6 +2175,17 @@ 'type': 'array' 'items': 'type': 'string' + 'CheckConfigRequestBeta': + 'type': 'object' + 'description': 'Configuration to be checked' + 'properties': + 'dns': + '$ref': '#/components/schemas/CheckConfigRequestInfoBeta' + 'web': + '$ref': '#/components/schemas/CheckConfigRequestInfoBeta' + 'set_static_ip': + 'type': 'boolean' + 'example': false 'CheckConfigRequest': 'type': 'object' 'description': 'Configuration to be checked' @@ -2054,6 +2197,23 @@ 'set_static_ip': 'type': 'boolean' 'example': false + 'CheckConfigRequestInfoBeta': + 'type': 'object' + 'properties': + 'ip': + 'type': 'array' + 'items': + 'type': 'string' + 'minItems': 1 + 'example': + - '127.0.0.1' + 'port': + 'type': 'integer' + 'format': 'uint16' + 'example': 53 + 'autofix': + 'type': 'boolean' + 'example': false 'CheckConfigRequestInfo': 'type': 'object' 'properties': @@ -2062,13 +2222,17 @@ 'example': '127.0.0.1' 'port': 'type': 'integer' - 'format': 'int32' + 'format': 'uint16' 'example': 53 'autofix': 'type': 'boolean' 'example': false 'CheckConfigResponse': 'type': 'object' + 'required': + - 'dns' + - 'web' + - 'static_ip' 'properties': 'dns': '$ref': '#/components/schemas/CheckConfigResponseInfo' @@ -2078,32 +2242,71 @@ '$ref': '#/components/schemas/CheckConfigStaticIpInfo' 'CheckConfigResponseInfo': 'type': 'object' + 'required': + - 'status' + - 'can_autofix' 'properties': 'status': 'type': 'string' + 'default': '' 'example': '' 'can_autofix': 'type': 'boolean' 'example': false + 'CheckConfigStaticIpInfoStatic': + 'type': 'string' + 'example': 'no' + 'enum': + - 'yes' + - 'no' + - 'error' + 'description': 'Can be: yes, no, error' 'CheckConfigStaticIpInfo': 'type': 'object' 'properties': 'static': - 'type': 'string' - 'example': 'no' - 'description': 'Can be: yes, no, error' + '$ref': '#/components/schemas/CheckConfigStaticIpInfoStatic' 'ip': 'type': 'string' + 'default': '' 'example': '192.168.1.1' 'description': 'Current dynamic IP address. Set if static=no' 'error': 'type': 'string' + 'default': '' 'example': '' 'description': 'Error text. Set if static=error' + 'InitialConfigurationBeta': + 'type': 'object' + 'description': > + AdGuard Home initial configuration for the first-install wizard. + 'required': + - 'dns' + - 'web' + - 'username' + - 'password' + 'properties': + 'dns': + '$ref': '#/components/schemas/AddressInfoBeta' + 'web': + '$ref': '#/components/schemas/AddressInfoBeta' + 'username': + 'type': 'string' + 'description': 'Basic auth username' + 'example': 'admin' + 'password': + 'type': 'string' + 'description': 'Basic auth password' + 'example': 'password' 'InitialConfiguration': 'type': 'object' 'description': > AdGuard Home initial configuration for the first-install wizard. + 'required': + - 'dns' + - 'web' + - 'username' + - 'password' 'properties': 'dns': '$ref': '#/components/schemas/AddressInfo' From 0e84962fde7bbb430d4c23e1d7582bfcf173dcb5 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Wed, 30 Dec 2020 18:26:25 +0300 Subject: [PATCH 031/141] Pull request: all: add a new Makefile and scripts, remove goreleaaser Merge in DNS/adguard-home from 2276-releases to master Updates #2276. Squashed commit of the following: commit 84961947c51477aae53606ec6e2e0cce0bdfc139 Author: Ainar Garipov Date: Wed Dec 30 14:36:13 2020 +0300 all: fix github build commit 54af2adbf2f433e80393fb142e66ba6b3a78b13e Author: Ainar Garipov Date: Wed Dec 30 14:34:02 2020 +0300 all: remove old Dockerfile, improve build scripts commit 99bb2f2ba1458d32074ac0911b5c02ce6669e43e Merge: 2292b677a 5e20ac7ed Author: Ainar Garipov Date: Wed Dec 30 13:47:19 2020 +0300 Merge branch 'master' into WIP-2276-releases commit 2292b677a20ce8e93d9e6e2bb042cd468606fec3 Author: Ainar Garipov Date: Wed Dec 30 13:30:10 2020 +0300 all: improve docker build commit 0bcc97c41f105ee4a4363f20fa4775c7643bf0cc Merge: c7d3f12ef aef4659e9 Author: Ainar Garipov Date: Tue Dec 29 17:47:45 2020 +0300 Merge branch 'master' into WIP-2276-releases commit c7d3f12ef2b63ddfa2acf46e3129fcbc56fb0a90 Author: Ainar Garipov Date: Tue Dec 29 16:28:25 2020 +0300 all: improve build scripts commit 55de1e5d7ef0fbdbd1a76cfb71362d16ca0a1966 Author: Ainar Garipov Date: Tue Dec 29 15:36:47 2020 +0300 all: fix Makefile commit d11b1fe28d0fde1efeaf6160a614951b19d0ef94 Author: Ainar Garipov Date: Tue Dec 29 14:16:19 2020 +0300 scripts: fix build-release commit ecc0577e2451afa86c37da7283a63a9d26fb37ba Merge: dde64ed8e 483f02c92 Author: Ainar Garipov Date: Tue Dec 29 13:59:32 2020 +0300 Merge branch 'master' into WIP-2276-releases commit dde64ed8e456f73559f21c2ca549dc3b46724add Author: Ainar Garipov Date: Fri Dec 25 18:04:46 2020 +0300 all: imp docs, other improvements commit be8574408db79901bb15c1d31916db3ca352a35f Author: Ainar Garipov Date: Fri Dec 25 14:48:30 2020 +0300 all: imp docker build commit fc1876f34b93d667bf166226f4bc666d394f10c7 Merge: fa5a304c8 955b735c8 Author: Ainar Garipov Date: Fri Dec 25 13:54:29 2020 +0300 Merge branch 'master' into WIP-2276-releases commit fa5a304c83d86145796a2de4141de6d18f7c56bf Author: Ainar Garipov Date: Thu Dec 24 19:10:51 2020 +0300 all: improve scripts commit 3f32e3fd5e658d058d5c5172519384efc6cfef83 Author: Ainar Garipov Date: Thu Dec 24 18:50:01 2020 +0300 all: improve scripts commit 2d38b81421acab4b90a7a19da7598c75063e8e93 Author: Ainar Garipov Date: Thu Dec 24 18:25:21 2020 +0300 all: fix shell for windows, improve go-lint.sh commit d695285cd6dc476c0d972cfe0c49bbeea5f5a049 Merge: 313b020e9 9fb6bf82c Author: Ainar Garipov Date: Thu Dec 24 18:14:38 2020 +0300 Merge branch 'master' into WIP-2276-releases commit 313b020e9dfcdab736670cee72b2171eac8c32b7 Author: Ainar Garipov Date: Thu Dec 24 18:13:31 2020 +0300 Makefile: use npm ci again commit 5acee9d6a6c8cd2a7dd04b173a73929650882bad Author: Ainar Garipov Date: Thu Dec 24 17:57:54 2020 +0300 all: try fixing windows build commit c63a2a54641ac8cd032a3306bb35e49b9ae74728 Author: Ainar Garipov Date: Thu Dec 24 17:39:30 2020 +0300 all: imp scripts, try another goproxy and direct commit 423229e8b63ee73caeee8e84c23f67d145aff9df Author: Ainar Garipov Date: Thu Dec 24 17:25:29 2020 +0300 all: imp HACKING.md, try a new proxy ... and 1 more commit --- .dockerignore | 45 +- .github/workflows/build.yml | 26 +- .github/workflows/lint.yml | 8 +- .goreleaser.yml | 115 ----- CHANGELOG.md | 9 +- Dockerfile | 79 ---- HACKING.md | 2 +- Makefile | 417 ++++-------------- README.md | 20 +- main.go | 3 - scripts/README.md | 159 +++++++ {.githooks => scripts/hooks}/pre-commit | 0 scripts/install.sh | 4 +- scripts/make/Dockerfile | 49 ++ scripts/make/build-docker.sh | 101 +++++ scripts/make/build-release.sh | 325 ++++++++++++++ scripts/make/clean.sh | 33 ++ scripts/make/go-build.sh | 98 ++++ scripts/make/go-deps.sh | 31 ++ scripts/{ => make}/go-lint.sh | 21 +- scripts/make/go-test.sh | 41 ++ .../{go-install-tools.sh => make/go-tools.sh} | 8 +- scripts/make/version.sh | 57 +++ scripts/querylog/README.md | 10 - scripts/snap/README.md | 6 - scripts/snap/snap.tmpl.yaml | 37 ++ scripts/translations/README.md | 11 - scripts/whotracksme/README.md | 13 - 28 files changed, 1065 insertions(+), 663 deletions(-) delete mode 100644 .goreleaser.yml delete mode 100644 Dockerfile create mode 100644 scripts/README.md rename {.githooks => scripts/hooks}/pre-commit (100%) create mode 100644 scripts/make/Dockerfile create mode 100644 scripts/make/build-docker.sh create mode 100644 scripts/make/build-release.sh create mode 100644 scripts/make/clean.sh create mode 100644 scripts/make/go-build.sh create mode 100644 scripts/make/go-deps.sh rename scripts/{ => make}/go-lint.sh (88%) create mode 100644 scripts/make/go-test.sh rename scripts/{go-install-tools.sh => make/go-tools.sh} (89%) create mode 100644 scripts/make/version.sh delete mode 100644 scripts/querylog/README.md delete mode 100644 scripts/snap/README.md create mode 100644 scripts/snap/snap.tmpl.yaml delete mode 100644 scripts/translations/README.md delete mode 100644 scripts/whotracksme/README.md diff --git a/.dockerignore b/.dockerignore index de2a4c61..bdd388f3 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,42 +1,3 @@ -.DS_Store -/.git -/.github -/.vscode -.idea -/AdGuardHome -/AdGuardHome.exe -/AdGuardHome.yaml -/AdGuardHome.log -/data -/build -/build2 -/dist -/client/node_modules -/client2/node_modules -/.gitattributes -/.gitignore -/.goreleaser.yml -/changelog.config.js -/coverage.txt -/Dockerfile -/LICENSE.txt -/Makefile -/querylog.json -/querylog.json.1 -/*.md - -# Test output -dnsfilter/tests/top-1m.csv -dnsfilter/tests/dnsfilter.TestLotsOfRules*.pprof - -# Snapcraft build temporary files -*.snap -launchpad_credentials -snapcraft_login -snapcraft.yaml.bak - -# IntelliJ IDEA project files -*.iml - -# Packr -*-packr.go +# Ignore everything except for explicitly allowed stuff. +* +!dist/docker diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bf4e0683..0bf8807b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -55,14 +55,14 @@ 'restore-keys': '${{ runner.os }}-node-' - 'name': 'Run make ci' 'shell': 'bash' - 'run': 'make ci' + 'run': 'make VERBOSE=1 ci' - 'name': 'Upload coverage' 'uses': 'codecov/codecov-action@v1' 'if': "success() && matrix.os == 'ubuntu-latest'" 'with': 'token': '${{ secrets.CODECOV_TOKEN }}' 'file': './coverage.txt' - 'app': + 'build-release': 'runs-on': 'ubuntu-latest' 'needs': 'test' 'steps': @@ -95,30 +95,16 @@ 'restore-keys': '${{ runner.os }}-node-' - 'name': 'Set up Snapcraft' 'run': 'sudo apt-get -yq --no-install-suggests --no-install-recommends install snapcraft' - - 'name': 'Set up GoReleaser' - 'run': 'curl -sfL https://install.goreleaser.com/github.com/goreleaser/goreleaser.sh | BINDIR="$(go env GOPATH)/bin" sh' - - 'name': 'Run snapshot build' - 'run': 'make release' - - 'docker': - 'runs-on': 'ubuntu-latest' - 'needs': 'test' - 'steps': - - 'name': 'Checkout' - 'uses': 'actions/checkout@v2' - 'with': - 'fetch-depth': 0 - 'name': 'Set up QEMU' 'uses': 'docker/setup-qemu-action@v1' - 'name': 'Set up Docker Buildx' 'uses': 'docker/setup-buildx-action@v1' - - 'name': 'Docker Buildx (build)' - 'run': 'make docker-multi-arch' + - 'name': 'Run snapshot build' + 'run': 'make SIGN=0 VERBOSE=1 js-deps js-build build-release build-docker' 'notify': 'needs': - - 'app' - - 'docker' + - 'build-release' # Secrets are not passed to workflows that are triggered by a pull request # from a fork. # @@ -139,7 +125,7 @@ 'uses': '8398a7/action-slack@v3' 'with': 'status': '${{ env.WORKFLOW_CONCLUSION }}' - 'fields': 'repo, message, commit, author, job' + 'fields': 'repo, message, commit, author, workflow' 'env': 'GITHUB_TOKEN': '${{ secrets.GITHUB_TOKEN }}' 'SLACK_WEBHOOK_URL': '${{ secrets.SLACK_WEBHOOK_URL }}' diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index d003dee9..0ed7c02c 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -13,15 +13,15 @@ - 'uses': 'actions/checkout@v2' - 'name': 'run-lint' 'run': > - make go-install-tools go-lint + make go-deps go-tools go-lint 'eslint': 'runs-on': 'ubuntu-latest' 'steps': - 'uses': 'actions/checkout@v2' - 'name': 'Install modules' - 'run': 'npm --prefix client ci' + 'run': 'npm --prefix="./client" ci' - 'name': 'Run ESLint' - 'run': 'npm --prefix client run lint' + 'run': 'npm --prefix="./client" run lint' 'notify': 'needs': - 'go-lint' @@ -46,7 +46,7 @@ 'uses': '8398a7/action-slack@v3' 'with': 'status': '${{ env.WORKFLOW_CONCLUSION }}' - 'fields': 'repo, message, commit, author, job' + 'fields': 'repo, message, commit, author, workflow' 'env': 'GITHUB_TOKEN': '${{ secrets.GITHUB_TOKEN }}' 'SLACK_WEBHOOK_URL': '${{ secrets.SLACK_WEBHOOK_URL }}' diff --git a/.goreleaser.yml b/.goreleaser.yml deleted file mode 100644 index c533712b..00000000 --- a/.goreleaser.yml +++ /dev/null @@ -1,115 +0,0 @@ -'project_name': 'AdGuardHome' - -'env': -- 'GO111MODULE=on' -- 'GOPROXY=https://goproxy.io' - -'before': - 'hooks': - - 'go mod download' - - 'go generate ./...' - -'builds': -- 'main': './main.go' - 'ldflags': - - '-s -w -X main.version={{.Version}} -X main.channel={{.Env.CHANNEL}} -X main.goarm={{.Env.GOARM}}' - 'env': - - 'CGO_ENABLED=0' - 'goos': - - 'darwin' - - 'linux' - - 'freebsd' - - 'windows' - 'goarch': - - '386' - - 'amd64' - - 'arm' - - 'arm64' - - 'mips' - - 'mipsle' - - 'mips64' - - 'mips64le' - 'goarm': - - '5' - - '6' - - '7' - 'gomips': - - 'softfloat' - 'ignore': - - 'goos': 'freebsd' - 'goarch': 'mips' - - 'goos': 'freebsd' - 'goarch': 'mipsle' - -'archives': -- # Archive name template. - # Defaults: - # - if format is `tar.gz`, `tar.xz`, `gz` or `zip`: - # - `{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}{{ if .Mips }}_{{ .Mips }}{{ end }}` - # - if format is `binary`: - # - `{{ .Binary }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}{{ if .Mips }}_{{ .Mips }}{{ end }}` - 'name_template': '{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}{{ if .Mips }}_{{ .Mips }}{{ end }}' - 'wrap_in_directory': 'AdGuardHome' - 'format_overrides': - - 'goos': 'windows' - 'format': 'zip' - - 'goos': 'darwin' - 'format': 'zip' - 'files': - - 'LICENSE.txt' - - 'README.md' - -'snapcrafts': -- 'name': 'adguard-home' - 'base': 'core20' - 'name_template': '{{ .ProjectName }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}' - 'summary': 'Network-wide ads & trackers blocking DNS server' - 'description': | - AdGuard Home is a network-wide software for blocking ads & tracking. After - you set it up, it'll cover ALL your home devices, and you don't need any - client-side software for that. - - It operates as a DNS server that re-routes tracking domains to a "black hole," - thus preventing your devices from connecting to those servers. It's based - on software we use for our public AdGuard DNS servers -- both share a lot - of common code. - 'grade': 'stable' - 'confinement': 'strict' - 'publish': false - 'license': 'GPL-3.0' - 'extra_files': - - 'source': 'scripts/snap/local/adguard-home-web.sh' - 'destination': 'adguard-home-web.sh' - 'mode': 0755 - - 'source': 'scripts/snap/gui/adguard-home-web.desktop' - 'destination': 'meta/gui/adguard-home-web.desktop' - 'mode': 0644 - - 'source': 'scripts/snap/gui/adguard-home-web.png' - 'destination': 'meta/gui/adguard-home-web.png' - 'mode': 0644 - 'apps': - 'adguard-home': - 'command': 'AdGuardHome -w $SNAP_DATA --no-check-update' - 'plugs': - # Add the "netrwork-bind" plug to bind to interfaces. - - 'network-bind' - # Add the "netrwork-observe" plug to be able to bind to ports below 1024 - # (cap_net_bind_service) and also to bind to a particular interface using - # SO_BINDTODEVICE (cap_net_raw). - - 'network-observe' - 'daemon': 'simple' - 'adguard-home-web': - 'command': 'adguard-home-web.sh' - 'plugs': - - 'desktop' - -'checksum': - 'name_template': 'checksums.txt' - -'snapshot': - # TODO(a.garipov): A temporary solution to trim the prerelease versions. - # A real solution would consist of making a better versioning scheme that also - # doesn't break SemVer or Snapcraft. - # - # See https://github.com/AdguardTeam/AdGuardHome/issues/2412. - 'name_template': '{{ slice .Tag 0 8 }}-SNAPSHOT-{{ .ShortCommit }}' diff --git a/CHANGELOG.md b/CHANGELOG.md index 46c421ba..e2fa248b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ and this project adheres to ## [Unreleased] ### Added @@ -44,10 +44,11 @@ and this project adheres to improve error response ([#2358]). - Improved HTTP requests handling and timeouts ([#2343]). - Our snap package now uses the `core20` image as its base ([#2306]). -- Various internal improvements ([#2267], [#2271], [#2297]). +- New build system and various internal improvements ([#2276], [#2271], + [#2297]). [#2231]: https://github.com/AdguardTeam/AdGuardHome/issues/2231 -[#2267]: https://github.com/AdguardTeam/AdGuardHome/issues/2267 +[#2276]: https://github.com/AdguardTeam/AdGuardHome/issues/2276 [#2271]: https://github.com/AdguardTeam/AdGuardHome/issues/2271 [#2297]: https://github.com/AdguardTeam/AdGuardHome/issues/2297 [#2306]: https://github.com/AdguardTeam/AdGuardHome/issues/2306 @@ -81,6 +82,8 @@ and this project adheres to ### Removed +- `Dockerfile` ([#2276]). Replaced with the script + `scripts/make/build-docker.sh` which uses `scripts/make/Dockerfile`. - Support for pre-v0.99.3 format of query logs ([#2102]). ## [v0.104.3] - 2020-11-19 diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index a218f2d4..00000000 --- a/Dockerfile +++ /dev/null @@ -1,79 +0,0 @@ -FROM --platform=${BUILDPLATFORM:-linux/amd64} tonistiigi/xx:golang AS xgo -FROM --platform=${BUILDPLATFORM:-linux/amd64} golang:1.14-alpine as builder - -ARG BUILD_DATE -ARG VCS_REF -ARG VERSION=dev -ARG CHANNEL=release - -ENV CGO_ENABLED 0 -ENV GO111MODULE on -ENV GOPROXY https://goproxy.io - -COPY --from=xgo / / -RUN go env - -RUN apk --update --no-cache add \ - build-base \ - gcc \ - git \ - npm \ - yarn \ - && rm -rf /tmp/* /var/cache/apk/* - -WORKDIR /app - -COPY . ./ - -# Prepare the client code -RUN npm --prefix client ci && npm --prefix client run build-prod -RUN yarn --cwd client2 build - -# Download go dependencies -RUN go mod download -RUN go generate ./... - -# It's important to place TARGET* arguments here to avoid running npm and go mod download for every platform -ARG TARGETPLATFORM -ARG TARGETOS -ARG TARGETARCH -RUN go build -ldflags="-s -w -X main.version=${VERSION} -X main.channel=${CHANNEL} -X main.goarm=${GOARM}" - -FROM --platform=${TARGETPLATFORM:-linux/amd64} alpine:latest - -ARG BUILD_DATE -ARG VCS_REF -ARG VERSION -ARG CHANNEL - -LABEL maintainer="AdGuard Team " \ - org.opencontainers.image.created=$BUILD_DATE \ - org.opencontainers.image.url="https://adguard.com/adguard-home.html" \ - org.opencontainers.image.source="https://github.com/AdguardTeam/AdGuardHome" \ - org.opencontainers.image.version=$VERSION \ - org.opencontainers.image.revision=$VCS_REF \ - org.opencontainers.image.vendor="AdGuard" \ - org.opencontainers.image.title="AdGuard Home" \ - org.opencontainers.image.description="Network-wide ads & trackers blocking DNS server" \ - org.opencontainers.image.licenses="GPL-3.0" - -RUN apk --update --no-cache add \ - ca-certificates \ - libcap \ - libressl \ - && rm -rf /tmp/* /var/cache/apk/* - -COPY --from=builder --chown=nobody:nogroup /app/AdGuardHome /opt/adguardhome/AdGuardHome -COPY --from=builder --chown=nobody:nogroup /usr/local/go/lib/time/zoneinfo.zip /usr/local/go/lib/time/zoneinfo.zip - -RUN /opt/adguardhome/AdGuardHome --version \ - && mkdir -p /opt/adguardhome/conf /opt/adguardhome/work \ - && chown -R nobody: /opt/adguardhome \ - && setcap 'cap_net_bind_service=+eip' /opt/adguardhome/AdGuardHome - -EXPOSE 53/tcp 53/udp 67/udp 68/udp 80/tcp 443/tcp 853/tcp 3000/tcp -WORKDIR /opt/adguardhome/work -VOLUME ["/opt/adguardhome/conf", "/opt/adguardhome/work"] - -ENTRYPOINT ["/opt/adguardhome/AdGuardHome"] -CMD ["-h", "0.0.0.0", "-c", "/opt/adguardhome/conf/AdGuardHome.yaml", "-w", "/opt/adguardhome/work", "--no-check-update"] diff --git a/HACKING.md b/HACKING.md index 237e554f..750e5f47 100644 --- a/HACKING.md +++ b/HACKING.md @@ -1,4 +1,4 @@ - # *AdGuardHome* Developer Guidelines + # AdGuard Home Developer Guidelines As of **December 2020**, this document is partially a work-in-progress, but should still be followed. Some of the rules aren't enforced as thoroughly or diff --git a/Makefile b/Makefile index 68c7f9dc..d9ce026d 100644 --- a/Makefile +++ b/Makefile @@ -1,355 +1,94 @@ +# Keep the Makefile POSIX-compliant. We currently allow hyphens in +# target names, but that may change in the future. # -# Available targets +# See https://pubs.opengroup.org/onlinepubs/9699919799/utilities/make.html. +.POSIX: + +CHANNEL = development +CLIENT_BETA_DIR = client2 +CLIENT_DIR = client +COMMIT = $$(git rev-parse --short HEAD) +DIST_DIR = dist +GO = go +# TODO(a.garipov): Add more default proxies using pipes after update to +# Go 1.15. # -# * build -- builds AdGuardHome for the current platform -# * client -- builds client-side code of AdGuard Home -# * client-watch -- builds client-side code of AdGuard Home and watches for changes there -# * docker -- builds a docker image for the current platform -# * clean -- clean everything created by previous builds -# * lint -- run all linters -# * test -- run all unit-tests -# * dependencies -- installs dependencies (go and npm modules) -# * ci -- installs dependencies, runs linters and tests, intended to be used by CI/CD -# -# Building releases: -# -# * release -- builds AdGuard Home distros. CHANNEL must be specified (edge, release or beta). -# * release_and_sign -- builds AdGuard Home distros and signs the binary files. -# CHANNEL must be specified (edge, release or beta). -# * sign -- Repacks all release archive files and signs the binary files inside them. -# For signing to work, the public+private key pair for $(GPG_KEY) must be imported: -# gpg --import public.txt -# gpg --import private.txt -# GPG_KEY_PASSPHRASE must contain the GPG key passphrase -# * docker-multi-arch -- builds a multi-arch image. If you want it to be pushed to docker hub, -# you must specify: -# * DOCKER_IMAGE_NAME - adguard/adguard-home -# * DOCKER_OUTPUT - type=image,name=adguard/adguard-home,push=true +# GOPROXY = https://goproxy.io|https://goproxy.cn|direct +GOPROXY = https://goproxy.cn,https://goproxy.io,direct +GPG_KEY_PASSPHRASE = not-a-real-password +NPM = npm +NPM_FLAGS = --prefix $(CLIENT_DIR) +SIGN = 1 +VERBOSE = 0 +VERSION = v0.0.0 +YARN = yarn +YARN_FLAGS = --cwd $(CLIENT_BETA_DIR) -GO := go -GOPATH := $(shell $(GO) env GOPATH) -PWD := $(shell pwd) -TARGET=AdGuardHome -BASE_URL="https://static.adguard.com/adguardhome/$(CHANNEL)" -GPG_KEY := devteam@adguard.com -GPG_KEY_PASSPHRASE := -GPG_CMD := gpg --detach-sig --default-key $(GPG_KEY) --pinentry-mode loopback --passphrase $(GPG_KEY_PASSPHRASE) -VERBOSE := -v -REBUILD_CLIENT = 1 +ENV = env\ + COMMIT='$(COMMIT)'\ + CHANNEL='$(CHANNEL)'\ + GPG_KEY_PASSPHRASE='$(GPG_KEY_PASSPHRASE)'\ + DIST_DIR='$(DIST_DIR)'\ + GO='$(GO)'\ + GOPROXY='$(GOPROXY)'\ + PATH="$${PWD}/bin:$$($(GO) env GOPATH)/bin:$${PATH}"\ + SIGN='$(SIGN)'\ + VERBOSE='$(VERBOSE)'\ + VERSION='$(VERSION)'\ -# See release target -DIST_DIR=dist +# Keep the line above blank. -# Update channel. Can be release, beta or edge. Uses edge by default. -CHANNEL ?= edge +# Keep this target first, so that a naked make invocation triggers +# a full build. +build: deps quick-build -# Validate channel -ifneq ($(CHANNEL),release) -ifneq ($(CHANNEL),beta) -ifneq ($(CHANNEL),edge) -$(error CHANNEL value is not valid. Valid values are release,beta or edge) -endif -endif -endif +quick-build: js-build go-build -# Version history URL (see -VERSION_HISTORY_URL="https://github.com/AdguardTeam/AdGuardHome/releases" -ifeq ($(CHANNEL),edge) - VERSION_HISTORY_URL="https://github.com/AdguardTeam/AdGuardHome/commits/master" -endif - -# goreleaser command depends on the $CHANNEL -GORELEASER_COMMAND=goreleaser release --rm-dist --skip-publish --snapshot --parallelism 1 -ifneq ($(CHANNEL),edge) - # If this is not an "edge" build, use normal release command - GORELEASER_COMMAND=goreleaser release --rm-dist --skip-publish --parallelism 1 -endif - -# Version properties -COMMIT=$(shell git rev-parse --short HEAD) -TAG_NAME=$(shell git describe --abbrev=0) -PRERELEASE_VERSION=$(shell git describe --abbrev=0) -# TODO(a.garipov): The cut call is a temporary solution to trim -# prerelease versions. See the comment in .goreleaser.yml. -RELEASE_VERSION=$(shell git describe --abbrev=0 | cut -c 1-8) -SNAPSHOT_VERSION=$(RELEASE_VERSION)-SNAPSHOT-$(COMMIT) - -# Set proper version -VERSION= -ifeq ($(TAG_NAME),$(shell git describe --abbrev=4)) - ifeq ($(CHANNEL),edge) - VERSION=$(SNAPSHOT_VERSION) - else ifeq ($(CHANNEL),beta) - VERSION=$(PRERELEASE_VERSION) - else - VERSION=$(RELEASE_VERSION) - endif -else - VERSION=$(SNAPSHOT_VERSION) -endif - -# Docker target parameters -DOCKER_IMAGE_NAME ?= adguardhome-dev -DOCKER_IMAGE_FULL_NAME = $(DOCKER_IMAGE_NAME):$(VERSION) -DOCKER_PLATFORMS=linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64,linux/386,linux/ppc64le -DOCKER_OUTPUT ?= type=image,name=$(DOCKER_IMAGE_NAME),push=false -BUILD_DATE=$(shell date -u +'%Y-%m-%dT%H:%M:%SZ') - -# Docker tags (can be redefined) -DOCKER_TAGS ?= -ifndef DOCKER_TAGS - ifeq ($(CHANNEL),release) - DOCKER_TAGS := --tag $(DOCKER_IMAGE_NAME):latest - endif - ifeq ($(CHANNEL),beta) - DOCKER_TAGS := --tag $(DOCKER_IMAGE_NAME):beta - endif - ifeq ($(CHANNEL),edge) - # Don't set the version tag when pushing to "edge" - DOCKER_IMAGE_FULL_NAME := $(DOCKER_IMAGE_NAME):edge - # DOCKER_TAGS := --tag $(DOCKER_IMAGE_NAME):edge - endif -endif - -# Validate docker build arguments -ifndef DOCKER_IMAGE_NAME -$(error DOCKER_IMAGE_NAME value is not set) -endif - -# OS-specific flags -TEST_FLAGS := --race $(VERBOSE) -ifeq ($(OS),Windows_NT) - TEST_FLAGS := -endif - -.PHONY: all build client client-watch docker lint lint-js lint-go test dependencies clean release docker-multi-arch -all: build - -init: - git config core.hooksPath .githooks - -build: - test '$(REBUILD_CLIENT)' = '1' && $(MAKE) client_with_deps || exit 0 - $(GO) mod download - PATH=$(GOPATH)/bin:$(PATH) $(GO) generate ./... - CGO_ENABLED=0 $(GO) build -ldflags="-s -w -X main.version=$(VERSION) -X main.channel=$(CHANNEL) -X main.goarm=$(GOARM)" - PATH=$(GOPATH)/bin:$(PATH) packr clean - -client: - npm --prefix client run build-prod - yarn --cwd client2 build - -client_with_deps: - npm --prefix client ci - npm --prefix client run build-prod - yarn --cwd client2 build - -client-watch: - npm --prefix client run watch - yarn --cwd client2 start - -docker: - DOCKER_CLI_EXPERIMENTAL=enabled \ - docker buildx build \ - --build-arg VERSION=$(VERSION) \ - --build-arg CHANNEL=$(CHANNEL) \ - --build-arg VCS_REF=$(COMMIT) \ - --build-arg BUILD_DATE=$(BUILD_DATE) \ - $(DOCKER_TAGS) \ - --load \ - -t "$(DOCKER_IMAGE_NAME)" -f ./Dockerfile . - - @echo Now you can run the docker image: - @echo docker run --name "adguard-home" -p 53:53/tcp -p 53:53/udp -p 80:80/tcp -p 443:443/tcp -p 853:853/tcp -p 3000:3000/tcp $(DOCKER_IMAGE_NAME) +ci: deps test +deps: js-deps go-deps lint: js-lint go-lint - -js-lint: dependencies - npm --prefix client run lint - yarn --cwd client2 lint - -go-install-tools: - env GO=$(GO) sh ./scripts/go-install-tools.sh - -go-lint: - env GO=$(GO) PATH="$$PWD/bin:$$PATH" sh ./scripts/go-lint.sh - test: js-test go-test +# Here and below, keep $(SHELL) in quotes, because on Windows this will +# expand to something like "C:/Program Files/Git/usr/bin/sh.exe". +build-docker: ; $(ENV) "$(SHELL)" ./scripts/make/build-docker.sh + +build-release: deps js-build + $(ENV) "$(SHELL)" ./scripts/make/build-release.sh + +clean: ; $(ENV) "$(SHELL)" ./scripts/make/clean.sh +init: ; git config core.hooksPath ./scripts/hooks + +js-build: + $(NPM) $(NPM_FLAGS) run build-prod + $(YARN) $(YARN_FLAGS) build +js-deps: + $(NPM) $(NPM_FLAGS) ci + $(YARN) $(YARN_FLAGS) install +js-lint: + $(NPM) $(NPM_FLAGS) run lint + $(YARN) $(YARN_FLAGS) lint js-test: - npm run test --prefix client + $(NPM) $(NPM_FLAGS) run test -go-test: - $(GO) test $(TEST_FLAGS) --coverprofile coverage.txt ./... - -ci: client_with_deps - $(GO) mod download - $(MAKE) test +go-build: ; $(ENV) "$(SHELL)" ./scripts/make/go-build.sh +go-deps: ; $(ENV) "$(SHELL)" ./scripts/make/go-deps.sh +go-lint: ; $(ENV) "$(SHELL)" ./scripts/make/go-lint.sh +go-test: ; $(ENV) "$(SHELL)" ./scripts/make/go-test.sh +go-tools: ; $(ENV) "$(SHELL)" ./scripts/make/go-tools.sh +# TODO(a.garipov): Remove the legacy targets once the build +# infrastructure stops using them. dependencies: - npm --prefix client ci - yarn --cwd client2 install - $(GO) mod download - -clean: - rm -f ./AdGuardHome ./AdGuardHome.exe ./coverage.txt - rm -f -r ./build/ ./client/node_modules/ ./data/ ./$(DIST_DIR)/ -# Set the GOPATH explicitly in case make clean is called from under sudo -# after a Docker build. - env PATH="$(GOPATH)/bin:$$PATH" packr clean - rm -f -r ./bin/ - + @ echo "use make deps instead" + @ $(MAKE) deps docker-multi-arch: - DOCKER_CLI_EXPERIMENTAL=enabled \ - docker buildx build \ - --platform $(DOCKER_PLATFORMS) \ - --build-arg VERSION=$(VERSION) \ - --build-arg CHANNEL=$(CHANNEL) \ - --build-arg VCS_REF=$(COMMIT) \ - --build-arg BUILD_DATE=$(BUILD_DATE) \ - $(DOCKER_TAGS) \ - --output "$(DOCKER_OUTPUT)" \ - -t "$(DOCKER_IMAGE_FULL_NAME)" -f ./Dockerfile . - - @echo If the image was pushed to the registry, you can now run it: - @echo docker run --name "adguard-home" -p 53:53/tcp -p 53:53/udp -p 80:80/tcp -p 443:443/tcp -p 853:853/tcp -p 3000:3000/tcp $(DOCKER_IMAGE_NAME) - -release: client_with_deps - $(GO) mod download - @echo Starting release build: version $(VERSION), channel $(CHANNEL) - CHANNEL=$(CHANNEL) $(GORELEASER_COMMAND) - $(call write_version_file,$(VERSION)) - PATH=$(GOPATH)/bin:$(PATH) packr clean - -release_and_sign: client_with_deps - $(MAKE) release - $(call repack_dist) - -sign: - $(call repack_dist) - -define write_version_file - $(eval version := $(1)) - - @echo Writing version file: $(version) - - # Variables for CI - rm -f $(DIST_DIR)/version.txt - echo "version=$(version)" > $(DIST_DIR)/version.txt - - # Prepare the version.json file - rm -f $(DIST_DIR)/version.json - echo "{" >> $(DIST_DIR)/version.json - echo " \"version\": \"$(version)\"," >> $(DIST_DIR)/version.json - echo " \"announcement\": \"AdGuard Home $(version) is now available!\"," >> $(DIST_DIR)/version.json - echo " \"announcement_url\": \"$(VERSION_HISTORY_URL)\"," >> $(DIST_DIR)/version.json - echo " \"selfupdate_min_version\": \"0.0\"," >> $(DIST_DIR)/version.json - - # Windows builds - echo " \"download_windows_amd64\": \"$(BASE_URL)/AdGuardHome_windows_amd64.zip\"," >> $(DIST_DIR)/version.json - echo " \"download_windows_386\": \"$(BASE_URL)/AdGuardHome_windows_386.zip\"," >> $(DIST_DIR)/version.json - - # MacOS builds - echo " \"download_darwin_amd64\": \"$(BASE_URL)/AdGuardHome_darwin_amd64.zip\"," >> $(DIST_DIR)/version.json - echo " \"download_darwin_386\": \"$(BASE_URL)/AdGuardHome_darwin_386.zip\"," >> $(DIST_DIR)/version.json - - # Linux - echo " \"download_linux_amd64\": \"$(BASE_URL)/AdGuardHome_linux_amd64.tar.gz\"," >> $(DIST_DIR)/version.json - echo " \"download_linux_386\": \"$(BASE_URL)/AdGuardHome_linux_386.tar.gz\"," >> $(DIST_DIR)/version.json - - # Linux, all kinds of ARM - echo " \"download_linux_arm\": \"$(BASE_URL)/AdGuardHome_linux_armv6.tar.gz\"," >> $(DIST_DIR)/version.json - echo " \"download_linux_armv5\": \"$(BASE_URL)/AdGuardHome_linux_armv5.tar.gz\"," >> $(DIST_DIR)/version.json - echo " \"download_linux_armv6\": \"$(BASE_URL)/AdGuardHome_linux_armv6.tar.gz\"," >> $(DIST_DIR)/version.json - echo " \"download_linux_armv7\": \"$(BASE_URL)/AdGuardHome_linux_armv7.tar.gz\"," >> $(DIST_DIR)/version.json - echo " \"download_linux_arm64\": \"$(BASE_URL)/AdGuardHome_linux_arm64.tar.gz\"," >> $(DIST_DIR)/version.json - - # Linux, MIPS - echo " \"download_linux_mips\": \"$(BASE_URL)/AdGuardHome_linux_mips_softfloat.tar.gz\"," >> $(DIST_DIR)/version.json - echo " \"download_linux_mipsle\": \"$(BASE_URL)/AdGuardHome_linux_mipsle_softfloat.tar.gz\"," >> $(DIST_DIR)/version.json - echo " \"download_linux_mips64\": \"$(BASE_URL)/AdGuardHome_linux_mips64_softfloat.tar.gz\"," >> $(DIST_DIR)/version.json - echo " \"download_linux_mips64le\": \"$(BASE_URL)/AdGuardHome_linux_mips64le_softfloat.tar.gz\"," >> $(DIST_DIR)/version.json - - # FreeBSD - echo " \"download_freebsd_386\": \"$(BASE_URL)/AdGuardHome_freebsd_386.tar.gz\"," >> $(DIST_DIR)/version.json - echo " \"download_freebsd_amd64\": \"$(BASE_URL)/AdGuardHome_freebsd_amd64.tar.gz\"," >> $(DIST_DIR)/version.json - - # FreeBSD, all kinds of ARM - echo " \"download_freebsd_arm\": \"$(BASE_URL)/AdGuardHome_freebsd_armv6.tar.gz\"," >> $(DIST_DIR)/version.json - echo " \"download_freebsd_armv5\": \"$(BASE_URL)/AdGuardHome_freebsd_armv5.tar.gz\"," >> $(DIST_DIR)/version.json - echo " \"download_freebsd_armv6\": \"$(BASE_URL)/AdGuardHome_freebsd_armv6.tar.gz\"," >> $(DIST_DIR)/version.json - echo " \"download_freebsd_armv7\": \"$(BASE_URL)/AdGuardHome_freebsd_armv7.tar.gz\"," >> $(DIST_DIR)/version.json - echo " \"download_freebsd_arm64\": \"$(BASE_URL)/AdGuardHome_freebsd_arm64.tar.gz\"" >> $(DIST_DIR)/version.json - - # Finish - echo "}" >> $(DIST_DIR)/version.json -endef - -define repack_dist - # Repack archive files - # A temporary solution for our auto-update code to be able to unpack these archive files - # The problem is that goreleaser doesn't add directory AdGuardHome/ to the archive file - # and we can't create it - rm -rf $(DIST_DIR)/AdGuardHome - - # Windows builds - $(call zip_repack_windows,AdGuardHome_windows_amd64.zip) - $(call zip_repack_windows,AdGuardHome_windows_386.zip) - - # MacOS builds - $(call zip_repack,AdGuardHome_darwin_amd64.zip) - $(call zip_repack,AdGuardHome_darwin_386.zip) - - # Linux - $(call tar_repack,AdGuardHome_linux_amd64.tar.gz) - $(call tar_repack,AdGuardHome_linux_386.tar.gz) - - # Linux, all kinds of ARM - $(call tar_repack,AdGuardHome_linux_armv5.tar.gz) - $(call tar_repack,AdGuardHome_linux_armv6.tar.gz) - $(call tar_repack,AdGuardHome_linux_armv7.tar.gz) - $(call tar_repack,AdGuardHome_linux_arm64.tar.gz) - - # Linux, MIPS - $(call tar_repack,AdGuardHome_linux_mips_softfloat.tar.gz) - $(call tar_repack,AdGuardHome_linux_mipsle_softfloat.tar.gz) - $(call tar_repack,AdGuardHome_linux_mips64_softfloat.tar.gz) - $(call tar_repack,AdGuardHome_linux_mips64le_softfloat.tar.gz) - - # FreeBSD - $(call tar_repack,AdGuardHome_freebsd_386.tar.gz) - $(call tar_repack,AdGuardHome_freebsd_amd64.tar.gz) - - # FreeBSD, all kinds of ARM - $(call tar_repack,AdGuardHome_freebsd_armv5.tar.gz) - $(call tar_repack,AdGuardHome_freebsd_armv6.tar.gz) - $(call tar_repack,AdGuardHome_freebsd_armv7.tar.gz) - $(call tar_repack,AdGuardHome_freebsd_arm64.tar.gz) -endef - -define zip_repack_windows - $(eval ARC := $(1)) - cd $(DIST_DIR) && \ - unzip $(ARC) && \ - $(GPG_CMD) AdGuardHome/AdGuardHome.exe && \ - zip -r $(ARC) AdGuardHome/ && \ - rm -rf AdGuardHome -endef - -define zip_repack - $(eval ARC := $(1)) - cd $(DIST_DIR) && \ - unzip $(ARC) && \ - $(GPG_CMD) AdGuardHome/AdGuardHome && \ - zip -r $(ARC) AdGuardHome/ && \ - rm -rf AdGuardHome -endef - -define tar_repack - $(eval ARC := $(1)) - cd $(DIST_DIR) && \ - tar xzf $(ARC) && \ - $(GPG_CMD) AdGuardHome/AdGuardHome && \ - tar czf $(ARC) AdGuardHome/ && \ - rm -rf AdGuardHome -endef + @ echo "use make build-docker instead" + @ $(MAKE) build-docker +go-install-tools: + @ echo "use make go-tools instead" + @ $(MAKE) go-tools +release: + @ echo "use make build-release instead" + @ $(MAKE) build-release diff --git a/README.md b/README.md index 69ba6c20..f09da329 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ It operates as a DNS server that re-routes tracking domains to a "black hole," t * [Reporting issues](#reporting-issues) * [Help with translations](#translate) * [Other](#help-other) -* [Projects that use AdGuardHome](#uses) +* [Projects that use AdGuard Home](#uses) * [Acknowledgments](#acknowledgments) * [Privacy](#privacy) @@ -189,26 +189,28 @@ In order to do this, specify `GOOS` and `GOARCH` env variables before running ma For example: ``` -GOOS=linux GOARCH=arm64 make +env GOOS='linux' GOARCH='arm64' make +``` +Or: +``` +make GOOS='linux' GOARCH='arm64' ``` #### Preparing release You'll need this to prepare a release build: -* [goreleaser](https://goreleaser.com/) * [snapcraft](https://snapcraft.io/) Commands: -* `make release` - builds a snapshot build (CHANNEL=edge) -* `CHANNEL=beta make release` - builds beta version, tag is mandatory. -* `CHANNEL=release make release` - builds release version, tag is mandatory. +``` +make build-release CHANNEL='...' VERSION='...' +``` #### Docker image -* Run `make docker` to build the Docker image locally. -* Run `make docker-multi-arch` to build the multi-arch Docker image (the one that we publish to Docker Hub). +* Run `make build-docker` to build the Docker image locally (the one that we publish to DockerHub). Please note, that we're using [Docker Buildx](https://docs.docker.com/buildx/working-with-buildx/) to build our official image. @@ -322,7 +324,7 @@ This software wouldn't have been possible without: * And many more node.js packages. * [whotracks.me data](https://github.com/cliqz-oss/whotracks.me) -You might have seen that [CoreDNS](https://coredns.io) was mentioned here before — we've stopped using it in AdGuardHome. While we still use it on our servers for [AdGuard DNS](https://adguard.com/adguard-dns/overview.html) service, it seemed like an overkill for Home as it impeded with Home features that we plan to implement. +You might have seen that [CoreDNS](https://coredns.io) was mentioned here before — we've stopped using it in AdGuard Home. While we still use it on our servers for [AdGuard DNS](https://adguard.com/adguard-dns/overview.html) service, it seemed like an overkill for Home as it impeded with Home features that we plan to implement. For a full list of all node.js packages in use, please take a look at [client/package.json](https://github.com/AdguardTeam/AdGuardHome/blob/master/client/package.json) file. diff --git a/main.go b/main.go index ecbb3d3b..be08b192 100644 --- a/main.go +++ b/main.go @@ -1,4 +1,3 @@ -//go:generate go install -v github.com/gobuffalo/packr/packr //go:generate packr clean //go:generate packr -z package main @@ -17,8 +16,6 @@ var channel = "release" var goarm = "" // gomips is the GOMIPS value. It is set by the linker. -// -// TODO(a.garipov): Implement. var gomips = "" func main() { diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 00000000..1971f5cd --- /dev/null +++ b/scripts/README.md @@ -0,0 +1,159 @@ + # AdGuard Home Scripts + +## `hooks/`: Git Hooks + + ### Usage + +Run `make init` from the project root. + +## `querylog/`: Query Log Helpers + + ### Usage + + * `npm install`: install dependencies. Run this first. + * `npm run anonymize `: read the query log from the `` + and write anonymized version to ``. + +## `make/`: Makefile Scripts + +The release channels are: `development` (the default), `edge`, `beta`, and +`release`. If verbosity levels aren't documented here, there are only two: `0`, +don't print anything, and `1`, be verbose. + + ### `build-docker.sh`: Build A Multi-Architecture Docker Image + +Required environment: + + * `CHANNEL`: release channel, see above. + * `COMMIT`: current Git revision. + * `DIST_DIR`: the directory where a release has previously been built. + * `VERSION`: release version. + +Optional environment: + + * `DOCKER_IMAGE_NAME`: the name of the resulting Docker container. By default + it's `adguardhome-dev`. + * `DOCKER_OUTPUT`: the `--output` parameters. By default they are + `type=image,name=${DOCKER_IMAGE_NAME},push=false`. + * `SUDO`: allow users to use `sudo` or `doas` with `docker`. By default none + is used. + + ### `build-release.sh`: Build A Release For All Platforms + +Required environment: + * `CHANNEL`: release channel, see above. + * `GPG_KEY` and `GPG_KEY_PASSPHRASE`: data for `gpg`. Only required if `SIGN` + is `1`. + +Optional environment: + * `DIST_DIR`: the directory to build a release into. The default value is + `dist`. + * `GO`: set an alternarive name for the Go compiler. + * `SIGN`: `0` to not sign the resulting packages, `1` to sign. The default + value is `1`. + * `VERBOSE`: `1` to be verbose, `2` to also print environment. This script + calls `go-build.sh` with the verbosity level one level lower, so to get + verbosity level `2` in `go-build.sh`, set this to `3` when calling + `build-release.sh`. + * `VERSION`: release version. Will be set by `version.sh` if it is unset or + it has the default `Makefile` value of `v0.0.0`. + + ### `clean.sh`: Cleanup + +Optional environment: + * `GO`: set an alternarive name for the Go compiler. + +Required environment: + * `DIST_DIR`: the directory where a release has previously been built. + + ### `go-build.sh`: Build The Backend + +Optional environment: + * `GOARM`: ARM processor options for the Go compiler. + * `GOMIPS`: ARM processor options for the Go compiler. + * `GO`: set an alternarive name for the Go compiler. + * `OUT`: output binary name. + * `PARALLELISM`: set the maximum number of concurrently run build commands + (that is, compiler, linker, etc.). + * `VERBOSE`: verbosity level. `1` shows every command that is run and every + Go package that is processed. `2` also shows subcommands and environment. + The default value is `0`, don't be verbose. + +Required environment: + * `CHANNEL`: release channel, see above. + * `VERSION`: release version. + + ### `go-deps.sh`: Install Backend Dependencies + +Optional environment: + * `GO`: set an alternarive name for the Go compiler. + * `VERBOSE`: verbosity level. `1` shows every command that is run and every + Go package that is processed. `2` also shows subcommands and environment. + The default value is `0`, don't be verbose. + + ### `go-lint.sh`: Run Backend Static Analyzers + +Don't forget to run `make go-tools` once first! + +Optional environment: + * `EXIT_ON_ERROR`: if set to `0`, don't exit the script after the first + encountered error. The default value is `1`. + * `GO`: set an alternarive name for the Go compiler. + * `VERBOSE`: verbosity level. `1` shows every command that is run. `2` also + shows subcommands. The default value is `0`, don't be verbose. + + ### `go-test.sh`: Run Backend Tests + +Optional environment: + * `GO`: set an alternarive name for the Go compiler. + * `RACE`: set to `0` to not use the Go race detector. The default value is + `1`, use the race detector. + * `VERBOSE`: verbosity level. `1` shows every command that is run and every + Go package that is processed. `2` also shows subcommands. The default + value is `0`, don't be verbose. + + ### `go-tools.sh`: Install Backend Tooling + +Installs the Go static analysis and other tools into `${PWD}/bin`. Either add +`${PWD}/bin` to your `$PATH` before all other entries, or use the commands +directly, or use the commands through `make` (for example, `make go-lint`). + +Optional environment: + * `GO`: set an alternarive name for the Go compiler. + + ### `version.sh`: Print The Current Version + +Required environment: + * `CHANNEL`: release channel, see above. + +## `snap/`: Snap GUI Files + +App icons (see https://github.com/AdguardTeam/AdGuardHome/pull/1836), Snap +manifest file templates, and helper scripts. + +## `translations/`: Twosky Integration Script + + ### Usage + + * `npm install`: install dependencies. Run this first. + * `npm run locales:download`: download and save all translations. + * `npm run locales:upload`: upload the base `en` locale. + * `npm run locales:summary`: show the current locales summary. + * `npm run locales:unused`: show the list of unused strings. + +After the download you'll find the output locales in the `client/src/__locales/` +directory. + +## `whotracksme/`: Whotracks.me Database Converter + +A simple script that converts the Ghostery/Cliqz trackers database to a json format. + + ### Usage + +```sh +yarn install +node index.js +``` + +You'll find the output in the `whotracksmedb.json` file. Then, move it to +`client/src/helpers/trackers`. diff --git a/.githooks/pre-commit b/scripts/hooks/pre-commit similarity index 100% rename from .githooks/pre-commit rename to scripts/hooks/pre-commit diff --git a/scripts/install.sh b/scripts/install.sh index 6f1fa85f..1114c641 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -1,6 +1,6 @@ #!/bin/sh -# AdGuardHome installation script +# AdGuard Home Installation Script # # 1. Download the package # 2. Unpack it @@ -227,4 +227,4 @@ main() { log_info " sudo ${AGH_DIR}/AdGuardHome -s start|stop|restart|status|install|uninstall" } -main "$@" \ No newline at end of file +main "$@" diff --git a/scripts/make/Dockerfile b/scripts/make/Dockerfile new file mode 100644 index 00000000..8494e7a5 --- /dev/null +++ b/scripts/make/Dockerfile @@ -0,0 +1,49 @@ +# A docker file for scripts/make/build-docker.sh. + +FROM alpine:3.12 + +ARG BUILD_DATE +ARG VERSION +ARG VCS_REF +LABEL maintainer="AdGuard Team " \ + org.opencontainers.image.created=$BUILD_DATE \ + org.opencontainers.image.url="https://adguard.com/adguard-home.html" \ + org.opencontainers.image.source="https://github.com/AdguardTeam/AdGuardHome" \ + org.opencontainers.image.version=$VERSION \ + org.opencontainers.image.revision=$VCS_REF \ + org.opencontainers.image.vendor="AdGuard" \ + org.opencontainers.image.title="AdGuard Home" \ + org.opencontainers.image.description="Network-wide ads & trackers blocking DNS server" \ + org.opencontainers.image.licenses="GPL-3.0" + +# Update certificates. +RUN apk --no-cache --update add ca-certificates libcap && \ + rm -rf /var/cache/apk/* && \ + mkdir -p /opt/adguardhome/conf /opt/adguardhome/work && \ + chown -R nobody: /opt/adguardhome + +ARG DIST_DIR +ARG TARGETARCH +ARG TARGETOS +ARG TARGETVARIANT + +COPY --chown=nobody:nogroup\ + ./${DIST_DIR}/docker/AdGuardHome_${TARGETOS}_${TARGETARCH}_${TARGETVARIANT}\ + /opt/adguardhome/AdGuardHome + +RUN setcap 'cap_net_bind_service=+eip' /opt/adguardhome/AdGuardHome + +EXPOSE 53/tcp 53/udp 67/udp 68/udp 80/tcp 443/tcp 853/tcp 3000/tcp + +VOLUME ["/opt/adguardhome/conf", "/opt/adguardhome/work"] + +WORKDIR /opt/adguardhome/work + +ENTRYPOINT ["/opt/adguardhome/AdGuardHome"] + +CMD [ \ + "--no-check-update", \ + "-c", "/opt/adguardhome/conf/AdGuardHome.yaml", \ + "-h", "0.0.0.0", \ + "-w", "/opt/adguardhome/work", \ +] diff --git a/scripts/make/build-docker.sh b/scripts/make/build-docker.sh new file mode 100644 index 00000000..d8eb2737 --- /dev/null +++ b/scripts/make/build-docker.sh @@ -0,0 +1,101 @@ +#!/bin/sh + +verbose="${VERBOSE:-0}" + +if [ "$verbose" -gt '0' ] +then + set -x + debug_flags='-D' +else + set +x + debug_flags='' +fi + +set -e -f -u + +# Require these to be set. The channel value is validated later. +readonly channel="$CHANNEL" +readonly commit="$COMMIT" +readonly dist_dir="$DIST_DIR" +readonly version="$VERSION" + +# Allow users to use sudo. +readonly sudo_cmd="${SUDO:-}" + +readonly docker_platforms="\ +linux/386,\ +linux/amd64,\ +linux/arm/v6,\ +linux/arm/v7,\ +linux/arm64,\ +linux/ppc64le" + +readonly build_date="$(date -u +'%Y-%m-%dT%H:%M:%SZ')" + +# Set DOCKER_IMAGE_NAME to 'adguard/adguard-home' if you want (and are +# allowed) to push to DockerHub. +readonly docker_image_name="${DOCKER_IMAGE_NAME:-adguardhome-dev}" + +# Set DOCKER_OUTPUT to 'type=image,name=adguard/adguard-home,push=true' +# if you want (and are allowed) to push to DockerHub. +readonly docker_output="${DOCKER_OUTPUT:-type=image,name=${docker_image_name},push=false}" + +case "$channel" +in +('release') + readonly docker_image_full_name="${docker_image_name}:${version}" + readonly docker_tags="--tag ${docker_image_name}:latest" + ;; +('beta') + readonly docker_image_full_name="${docker_image_name}:${version}" + readonly docker_tags="--tag ${docker_image_name}:beta" + ;; +('edge') + # Don't set the version tag when pushing to the edge channel. + readonly docker_image_full_name="${docker_image_name}:edge" + readonly docker_tags='' + ;; +('development') + readonly docker_image_full_name="${docker_image_name}" + readonly docker_tags='' + ;; +(*) + echo "invalid channel '$channel', supported values are\ + 'development', 'edge', 'beta', and 'release'" 1>&2 + exit 1 + ;; +esac + +# Copy the binaries into a new directory under new names, so that it's +# eaiser to COPY them later. DO NOT remove the trailing underscores. +# See scripts/make/Dockerfile. +readonly dist_docker="${dist_dir}/docker" +mkdir -p "$dist_docker" +cp "${dist_dir}/AdGuardHome_linux_386/AdGuardHome/AdGuardHome"\ + "${dist_docker}/AdGuardHome_linux_386_" +cp "${dist_dir}/AdGuardHome_linux_amd64/AdGuardHome/AdGuardHome"\ + "${dist_docker}/AdGuardHome_linux_amd64_" +cp "${dist_dir}/AdGuardHome_linux_arm64/AdGuardHome/AdGuardHome"\ + "${dist_docker}/AdGuardHome_linux_arm64_" +cp "${dist_dir}/AdGuardHome_linux_arm_6/AdGuardHome/AdGuardHome"\ + "${dist_docker}/AdGuardHome_linux_arm_v6" +cp "${dist_dir}/AdGuardHome_linux_arm_7/AdGuardHome/AdGuardHome"\ + "${dist_docker}/AdGuardHome_linux_arm_v7" +cp "${dist_dir}/AdGuardHome_linux_ppc64le/AdGuardHome/AdGuardHome"\ + "${dist_docker}/AdGuardHome_linux_ppc64le_" + +# Don't use quotes with $docker_tags and $debug_flags because we want +# word splitting and or an empty space if tags are empty. +$sudo_cmd docker\ + $debug_flags\ + buildx build\ + --build-arg BUILD_DATE="$build_date"\ + --build-arg DIST_DIR="$dist_dir"\ + --build-arg VCS_REF="$commit"\ + --build-arg VERSION="$version"\ + --output "$docker_output"\ + --platform "$docker_platforms"\ + $docker_tags\ + -t "$docker_image_full_name"\ + -f ./scripts/make/Dockerfile\ + . diff --git a/scripts/make/build-release.sh b/scripts/make/build-release.sh new file mode 100644 index 00000000..e031d049 --- /dev/null +++ b/scripts/make/build-release.sh @@ -0,0 +1,325 @@ +#!/bin/sh + +# AdGuard Home Release Script +# +# The commentary in this file is written with the assumption that the +# reader only has superficial knowledge of the POSIX shell language and +# alike. Experienced readers may find it overly verbose. + +# The default verbosity level is 0. Show every command that is run if +# the caller requested verbosity level greater than 0. Show the +# environment if the callre requested verbosity level greater than 1. +# Otherwise, print nothing. +# +# The level of verbosity for the build script is the same minus one +# level. See below in build(). +readonly verbose="${VERBOSE:-0}" +if [ "$verbose" -gt '1' ] +then + env + set -x +elif [ "$verbose" -gt '0' ] +then + set -x +fi + +# By default, sign the packages, but allow users to skip that step. +readonly sign="${SIGN:-1}" + +# Exit the script if a pipeline fails (-e), prevent accidental filename +# expansion (-f), and consider undefined variables as errors (-u). +set -e -f -u + +# Function log is an echo wrapper that writes to stderr if the caller +# requested verbosity level greater than 0. Otherwise, it does nothing. +log() { + if [ "$verbose" -gt '0' ] + then + # Don't use quotes to get word splitting. + echo $@ 1>&2 + fi +} + +log 'starting to build AdGuard Home release' + +# Require the channel to be set. Additional validation is performed +# later by go-build.sh. +readonly channel="$CHANNEL" + +# Check VERSION against the default value from the Makefile. If it is +# that, use the version calculation script. +if [ "${VERSION:-}" = 'v0.0.0' -o "${VERSION:-}" = '' ] +then + readonly version="$(sh ./scripts/make/version.sh)" +else + readonly version="$VERSION" +fi + +log "channel '$channel'" +log "version '$version'" + +# Require the gpg key and passphrase to be set if the signing is +# required. +if [ "$sign" = '1' ] +then + readonly gpg_key_passphrase="$GPG_KEY_PASSPHRASE" + readonly gpg_key="$GPG_KEY" +fi + +# The default distribution files directory is dist. +readonly dist="${DIST_DIR:-dist}" + +# Give users the ability to override the go command from environment. +# For example, to build two releases with two different Go versions and +# test the difference. +readonly go="${GO:-go}" + +log "checking tools" + +# Make sure we fail gracefully if one of the tools we need is missing. +for tool in gpg gzip sed sha256sum snapcraft tar zip +do + which "$tool" >/dev/null ||\ + { log "pieces don't fit, '$tool' not found"; exit 1; } +done + +# Data section. Arrange data into space-separated tables for read -r to +# read. Use 0 for missing values. + +readonly arms='5 +6 +7' + +readonly mipses='softfloat' + +# os arch arm mips snap +readonly platforms="\ +darwin 386 0 0 0 +darwin amd64 0 0 0 +freebsd 386 0 0 0 +freebsd amd64 0 0 0 +freebsd arm 5 0 0 +freebsd arm 6 0 0 +freebsd arm 7 0 0 +linux 386 0 0 i386 +linux amd64 0 0 amd64 +linux arm 5 0 0 +linux arm 6 0 0 +linux arm 7 0 armhf +linux arm64 0 0 arm64 +linux mips 0 softfloat 0 +linux mips64 0 softfloat 0 +linux mips64le 0 softfloat 0 +linux mipsle 0 softfloat 0 +linux ppc64le 0 0 0 +windows 386 0 0 0 +windows amd64 0 0 0" + +# Function build builds the release for one platform. It builds +# a binary, an archive and, if needed, a snap package. +build() { + # Get the arguments. Here and below, use the "build_" prefix + # for all variables local to function build. + build_dir="${dist}/${1}/AdGuardHome"\ + build_ar="$2"\ + build_os="$3"\ + build_arch="$4"\ + build_arm="$5"\ + build_mips="$6"\ + build_snap="$7"\ + ; + + # Use the ".exe" filename extension if we build a Windows + # release. + if [ "$build_os" = 'windows' ] + then + build_output="./${build_dir}/AdGuardHome.exe" + else + build_output="./${build_dir}/AdGuardHome" + fi + + mkdir -p "./${build_dir}" + + # Build the binary. + # + # Set GOARM and GOMIPS to an empty string if $build_arm and + # $build_mips are zero by removing the zero as if it's a prefix. + # + # Don't use quotes with $build_par because we want an empty + # space if parallelism wasn't set. + env\ + GOARCH="$build_arch"\ + GOARM="${build_arm#0}"\ + GOMIPS="${build_mips#0}"\ + GOOS="$os"\ + VERBOSE="$(( verbose - 1 ))"\ + OUT="$build_output"\ + sh ./scripts/make/go-build.sh\ + ; + + log "$build_output" + + if [ "$sign" = '1' ] + then + gpg\ + --default-key "$gpg_key"\ + --detach-sig\ + --passphrase "$gpg_key_passphrase"\ + --pinentry-mode loopback\ + -q\ + "$build_output"\ + ; + fi + + # Prepare the build directory for archiving. + cp ./CHANGELOG.md ./LICENSE.txt ./README.md "$build_dir" + + # Make archives. Windows and macOS prefer ZIP archives; the + # rest, gzipped tarballs. + case "$build_os" + in + ('darwin'|'windows') + build_archive="./${dist}/${build_ar}.zip" + zip -9 -q "$build_archive" "$build_dir" + ;; + (*) + build_archive="./${dist}/${build_ar}.tar.gz" + tar -C "./${dist}/${1}" -c -f - "./AdGuardHome"\ + | gzip -9 - >"$build_archive" + ;; + esac + + log "$build_archive" + + if [ "$build_snap" = '0' ] + then + return + fi + + # Prepare snap build. + build_snap_output="./${dist}/AdGuardHome_${build_snap}.snap" + build_snap_dir="${build_snap_output}.dir" + + # Create the meta subdirectory and copy files there. + mkdir -p "${build_snap_dir}/meta" + cp "$build_output"\ + './scripts/snap/local/adguard-home-web.sh'\ + "$build_snap_dir" + cp -r './scripts/snap/gui'\ + "${build_snap_dir}/meta/" + + # Create a snap.yaml file, setting the values. + sed -e 's/%VERSION%/'"$version"'/'\ + -e 's/%ARCH%/'"$build_snap"'/'\ + ./scripts/snap/snap.tmpl.yaml\ + >"${build_snap_dir}/meta/snap.yaml" + + # TODO(a.garipov): The snapcraft tool will *always* write + # everything, including errors, to stdout. And there doesn't + # seem to be a way to change that. So, save the combined + # output, but only show it when snapcraft actually fails. + set +e + build_snapcraft_output="$( + snapcraft pack "$build_snap_dir"\ + --output "$build_snap_output" 2>&1 + )" + build_snapcraft_exit_code="$?" + set -e + if [ "$build_snapcraft_exit_code" != '0' ] + then + log "$build_snapcraft_output" + exit "$build_snapcraft_exit_code" + fi + + log "$build_snap_output" +} + +log "starting builds" + +# Go over all platforms defined in the space-separated table above, +# tweak the values where necessary, and feed to build. +echo "$platforms" | while read -r os arch arm mips snap +do + case "$arch" + in + (arm) + dir="AdGuardHome_${os}_${arch}_${arm}" + ar="AdGuardHome_${os}_${arch}v${arm}" + ;; + (mips*) + dir="AdGuardHome_${os}_${arch}_${mips}" + ar="$dir" + ;; + (*) + dir="AdGuardHome_${os}_${arch}" + ar="$dir" + ;; + esac + + build "$dir" "$ar" "$os" "$arch" "$arm" "$mips" "$snap" +done + +log "calculating checksums" + +# Calculate the checksums of the files in a subshell with file expansion +# enabled (+f) so that we don't need to use find or basename. +( + set +f + + cd "./${dist}" + + # Don't use quotes to get word splitting. + sha256sum $(ls *.tar.gz *.zip) > ./checksums.txt +) + +log "writing versions" + +echo "$version" > "./${dist}/version.txt" + +# Create the verison.json file. + +readonly version_download_url="https://static.adguard.com/adguardhome/${channel}" +readonly version_json="./${dist}/version.json" + +# Point users to the master branch if the channel is edge. +if [ "$channel" = 'edge' ] +then + readonly version_history_url='https://github.com/AdguardTeam/AdGuardHome/commits/master' +else + readonly version_history_url='https://github.com/AdguardTeam/AdGuardHome/releases' +fi + +rm -f "$version_json" +echo "{ + \"version\": \"${version}\", + \"announcement\": \"AdGuard Home ${version} is now available!\", + \"announcement_url\": \"${version_history_url}\", + \"selfupdate_min_version\": \"0.0\", +" >> "$version_json" + +( + # Use +f here so that ls works and we don't need to use find. + set +f + + # Don't use quotes to get word splitting. + for f in $(ls "./${dist}/"*.tar.gz "./${dist}/"*.zip) + do + platform="$f" + + # Remove the prefix. + platform="${platform#./${dist}/AdGuardHome_}" + + # Remove the filename extensions. + platform="${platform%.zip}" + platform="${platform%.tar.gz}" + + # Use the filename's base path. + filename="${f#./${dist}/}" + + echo " \"download_${platform}\": \"${version_download_url}/${filename}\"," >> "$version_json" + done +) + +echo '}' >> "$version_json" + +log "finished" diff --git a/scripts/make/clean.sh b/scripts/make/clean.sh new file mode 100644 index 00000000..a366e79c --- /dev/null +++ b/scripts/make/clean.sh @@ -0,0 +1,33 @@ +#!/bin/sh + +verbose="${VERBOSE:-0}" + +if [ "$verbose" -gt '0' ] +then + set -x +fi + +set -e -f -u + +dist_dir="$DIST_DIR" +go="${GO:-go}" + +# Set the GOPATH explicitly in case make clean is called from under sudo +# after a Docker build. +env PATH="$("$go" env GOPATH)/bin":"$PATH" packr clean + +rm -f\ + ./AdGuardHome\ + ./AdGuardHome.exe\ + ./coverage.txt\ + ; + +rm -f -r\ + ./bin/\ + ./build/\ + ./build2/\ + ./client/node_modules/\ + ./client2/node_modules/\ + ./data/\ + "./${dist_dir}/"\ + ; diff --git a/scripts/make/go-build.sh b/scripts/make/go-build.sh new file mode 100644 index 00000000..a2a4a93a --- /dev/null +++ b/scripts/make/go-build.sh @@ -0,0 +1,98 @@ +#!/bin/sh + +# AdGuard Home Build Script +# +# The commentary in this file is written with the assumption that the +# reader only has superficial knowledge of the POSIX shell language and +# alike. Experienced readers may find it overly verbose. + +# The default verbosity level is 0. Show every command that is run and +# every package that is processed if the caller requested verbosity +# level greater than 0. Also show subcommands if the requested +# verbosity level is greater than 1. Otherwise, do nothing. +verbose="${VERBOSE:-0}" +if [ "$verbose" -gt '1' ] +then + env + set -x + readonly v_flags='-v' + readonly x_flags='-x' +elif [ "$verbose" -gt '0' ] +then + set -x + readonly v_flags='-v' + readonly x_flags='' +else + set +x + readonly v_flags='' + readonly x_flags='' +fi + +# Exit the script if a pipeline fails (-e), prevent accidental filename +# expansion (-f), and consider undefined variables as errors (-u). +set -e -f -u + +# Allow users to set the Go version. +go="${GO:-go}" + +# Require the channel to be set and validate the value. +channel="$CHANNEL" +case "$channel" +in +('development'|'edge'|'beta'|'release') + # All is well, go on. + ;; +(*) + echo "invalid channel '$channel', supported values are\ + 'development', 'edge', 'beta', and 'release'" 1>&2 + exit 1 + ;; +esac + +# Require the version to be set. +# +# TODO(a.garipov): Additional validation? +version="$VERSION" + +# Set the linker flags accordingly: set the channel and the versio as +# well as goarm and gomips variable values, if the variables are set and +# are not empty. +ldflags="-s -w -X main.version=${version}" +ldflags="${ldflags} -X main.channel=${channel}" +if [ "${GOARM:-}" != '' ] +then + ldflags="${ldflags} -X main.goarm=${GOARM}" +elif [ "${GOMIPS:-}" != '' ] +then + ldflags="${ldflags} -X main.gomips=${GOMIPS}" +fi + +# Allow users to limit the build's parallelism. +readonly parallelism="${PARALLELISM:-}" +if [ "$parallelism" != '' ] +then + readonly par_flags="-p ${parallelism}" +else + readonly par_flags='' +fi + +# Allow users to specify a different output name. +readonly out="${OUT:-}" +if [ "$out" != '' ] +then + readonly out_flags="-o ${out}" +else + readonly out_flags='' +fi + +# Don't use cgo. Use modules. +export CGO_ENABLED='0' GO111MODULE='on' + +readonly build_flags="${BUILD_FLAGS:-$out_flags $par_flags\ + $v_flags $x_flags}" + +# Don't use quotes with flag variables to get word splitting. +"$go" generate $v_flags $x_flags ./... + +# Don't use quotes with flag variables to get word splitting. +"$go" build --ldflags "$ldflags" $build_flags diff --git a/scripts/make/go-deps.sh b/scripts/make/go-deps.sh new file mode 100644 index 00000000..f9bfb365 --- /dev/null +++ b/scripts/make/go-deps.sh @@ -0,0 +1,31 @@ +#!/bin/sh + +verbose="${VERBOSE:-0}" + +if [ "$verbose" -gt '1' ] +then + env + set -x + readonly v_flags='-v' + readonly x_flags='-x' +elif [ "$verbose" -gt '0' ] +then + set -x + readonly v_flags='-v' + readonly x_flags='' +else + set +x + readonly v_flags='' + readonly x_flags='' +fi + +set -e -f -u + +go="${GO:-go}" + +# Don't use quotes with flag variables because we want an empty space if +# those aren't set. +"$go" mod download $x_flags + +env GOBIN="${PWD}/bin" "$go" install $v_flags $x_flags\ + github.com/gobuffalo/packr/packr diff --git a/scripts/go-lint.sh b/scripts/make/go-lint.sh similarity index 88% rename from scripts/go-lint.sh rename to scripts/make/go-lint.sh index ca30e1e7..22a57616 100644 --- a/scripts/go-lint.sh +++ b/scripts/make/go-lint.sh @@ -1,13 +1,23 @@ #!/bin/sh +verbose="${VERBOSE:-0}" + # Verbosity levels: # 0 = Don't print anything except for errors. # 1 = Print commands, but not nested commands. # 2 = Print everything. -test "${VERBOSE:=0}" -gt '0' && set -x +if [ "$verbose" -gt '0' ] +then + set -x +fi -# Set $EXITONERROR to zero to see all errors. -test "${EXITONERROR:=1}" = '0' && set +e || set -e +# Set $EXIT_ON_ERROR to zero to see all errors. +if [ "${EXIT_ON_ERROR:-1}" = '0' ] +then + set +e +else + set -e +fi # We don't need glob expansions and we want to see errors about unset # variables. @@ -17,7 +27,7 @@ not_found_msg=' looks like a binary not found error. make sure you have installed the linter binaries using: - $ make go-install-tools + $ make go-tools ' not_found() { @@ -95,7 +105,8 @@ ineffassign . unparam ./... -git ls-files -- '*.go' '*.md' '*.yaml' '*.yml' | xargs misspell --error +git ls-files -- '*.go' '*.md' '*.yaml' '*.yml' 'Makefile'\ + | xargs misspell --error looppointer ./... diff --git a/scripts/make/go-test.sh b/scripts/make/go-test.sh new file mode 100644 index 00000000..098b56d8 --- /dev/null +++ b/scripts/make/go-test.sh @@ -0,0 +1,41 @@ +#!/bin/sh + +verbose="${VERBOSE:-0}" + +# Verbosity levels: +# 0 = Don't print anything except for errors. +# 1 = Print commands, but not nested commands. +# 2 = Print everything. +if [ "$verbose" -gt '1' ] +then + set -x + v_flags='-v' + x_flags='-x' +elif [ "$verbose" -gt '0' ] +then + set -x + v_flags='-v' + x_flags='' +else + set +x + v_flags='' + x_flags='' +fi + +set -e -f -u + +race="${RACE:-1}" +if [ "$race" = '0' ] +then + race_flags='' +else + race_flags='--race' +fi + +go="${GO:-go}" +cover_flags='--coverprofile ./coverage.txt' +count_flags='--count 1' + +# Don't use quotes with flag variables because we want an empty space if +# those aren't set. +"$go" test $race_flags $count_flags $cover_flags $x_flags $v_flags ./... diff --git a/scripts/go-install-tools.sh b/scripts/make/go-tools.sh similarity index 89% rename from scripts/go-install-tools.sh rename to scripts/make/go-tools.sh index e75a54e4..6c96e1cb 100644 --- a/scripts/go-install-tools.sh +++ b/scripts/make/go-tools.sh @@ -1,6 +1,12 @@ #!/bin/sh -test "$VERBOSE" = '1' && set -x +verbose="${VERBOSE:-0}" + +if [ "$verbose" -gt '0' ] +then + set -x +fi + set -e -f -u # TODO(a.garipov): Add goconst? diff --git a/scripts/make/version.sh b/scripts/make/version.sh new file mode 100644 index 00000000..08f9b482 --- /dev/null +++ b/scripts/make/version.sh @@ -0,0 +1,57 @@ +#!/bin/sh + +readonly verbose="${VERBOSE:-0}" +if [ "$verbose" -gt '1' ] +then + set -x +fi + +set -e -f -u + +readonly awk_program='/^v[0-9]+\.[0-9]+\.[0-9]+.*$/ { + if (!$4) { + # The last tag is a full release version, so bump the + # minor one to get the next one. + $2++; + } + + print($1 "." $2 "." $3); + + next; +} + +{ + printf("invalid version: \"%s\"\n", $0); + + exit 1; +}' + +readonly last_tag="$(git describe --abbrev=0)" +readonly current_desc="$(git describe)" + +readonly channel="$CHANNEL" +case "$channel" +in +('development') + echo 'v0.0.0' + ;; +('edge') + next=$(echo $last_tag | awk -F '[.+-]' "$awk_program") + echo "${next}-SNAPSHOT-$(git rev-parse --short HEAD)" + ;; +('beta'|'release') + if [ "$current_desc" != "$last_tag" ] + then + echo 'need a tag' 1>&2 + + exit 1 + fi + + echo "$last_tag" + ;; +(*) + echo "invalid channel '$channel', supported values are\ + 'development', 'edge', 'beta', and 'release'" 1>&2 + exit 1 + ;; +esac diff --git a/scripts/querylog/README.md b/scripts/querylog/README.md deleted file mode 100644 index 219d9581..00000000 --- a/scripts/querylog/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# Helper tools to work with the Query log - -### Usage - -- `npm install` - Install the dependencies -- `npm run anonymize ` - Reads querylog from the `` and writes anonymized version to `` - -### Examples - -- `npm run anonymize test/querylog.json test/anonquerylog.json` - anonymizes the `test/querylog.json`. \ No newline at end of file diff --git a/scripts/snap/README.md b/scripts/snap/README.md deleted file mode 100644 index 6997dfee..00000000 --- a/scripts/snap/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# Snap GUI - -These files are added to the AdGuard Home snap in order to add an app icon: -https://github.com/AdguardTeam/AdGuardHome/pull/1836 - -See .goreleaser.yml: snapcrafts.extra_files \ No newline at end of file diff --git a/scripts/snap/snap.tmpl.yaml b/scripts/snap/snap.tmpl.yaml new file mode 100644 index 00000000..1dff31f6 --- /dev/null +++ b/scripts/snap/snap.tmpl.yaml @@ -0,0 +1,37 @@ +# The %VARIABLES% are be replaced by actual values by the build script. + +'name': 'adguard-home' +'base': 'core20' +'version': '%VERSION%' +'summary': Network-wide ads & trackers blocking DNS server +'description': | + AdGuard Home is a network-wide software for blocking ads & tracking. After + you set it up, it'll cover ALL your home devices, and you don't need any + client-side software for that. + + It operates as a DNS server that re-routes tracking domains to a "black hole," + thus preventing your devices from connecting to those servers. It's based + on software we use for our public AdGuard DNS servers -- both share a lot + of common code. +'grade': 'stable' +'confinement': 'strict' + +'architectures': +- '%ARCH%' + +'apps': + 'adguard-home': + 'command': 'AdGuardHome --no-check-update -w $SNAP_DATA' + 'plugs': + # Add the "netrwork-bind" plug to bind to interfaces. + - 'network-bind' + # Add the "netrwork-observe" plug to be able to bind to ports below 1024 + # (cap_net_bind_service) and also to bind to a particular interface using + # SO_BINDTODEVICE (cap_net_raw). + - 'network-observe' + 'daemon': 'simple' + 'restart-condition': 'always' + 'adguard-home-web': + 'command': 'adguard-home-web.sh' + 'plugs': + - 'desktop' diff --git a/scripts/translations/README.md b/scripts/translations/README.md deleted file mode 100644 index 3a5e336c..00000000 --- a/scripts/translations/README.md +++ /dev/null @@ -1,11 +0,0 @@ -## Twosky integration script - -### Usage - -- `npm install` Install the dependencies in the local node_modules folder -- `npm run locales:download` - Download and save all translations -- `npm run locales:upload` - Upload base `en` locale -- `npm run locales:summary` - Shows the current locales summary -- `npm run locales:unused` - Shows a list of unused strings - -After download you'll find the output locales in the `client/src/__locales/` folder. diff --git a/scripts/whotracksme/README.md b/scripts/whotracksme/README.md deleted file mode 100644 index cd5618ad..00000000 --- a/scripts/whotracksme/README.md +++ /dev/null @@ -1,13 +0,0 @@ -## Whotracks.me database converter - -A simple script that converts the Ghostery/Cliqz trackers database to a json format. - -### Usage - -``` -yarn install -node index.js -``` - -You'll find the output in the `whotracksmedb.json` file. -Move it to `client/src/helpers/trackers`. \ No newline at end of file From e20e94ddd47645e9aec30d623e963eb55c82a57c Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Wed, 30 Dec 2020 20:36:57 +0300 Subject: [PATCH 032/141] Pull request: all: fix gpg_key, release Merge in DNS/adguard-home from 2276-fix-release to master Updates #2276. Squashed commit of the following: commit d7b4af6aa867159c5ced8b421a20606f5ee11fd6 Author: Ainar Garipov Date: Wed Dec 30 20:26:33 2020 +0300 all: remove comment commit cd779699159c4efee4b64184b331add94a7ba57e Author: Ainar Garipov Date: Wed Dec 30 20:25:24 2020 +0300 all: fix gpg_key, release --- Makefile | 2 ++ scripts/make/build-release.sh | 1 + 2 files changed, 3 insertions(+) diff --git a/Makefile b/Makefile index d9ce026d..246a7c55 100644 --- a/Makefile +++ b/Makefile @@ -15,6 +15,7 @@ GO = go # # GOPROXY = https://goproxy.io|https://goproxy.cn|direct GOPROXY = https://goproxy.cn,https://goproxy.io,direct +GPG_KEY = devteam@adguard.com GPG_KEY_PASSPHRASE = not-a-real-password NPM = npm NPM_FLAGS = --prefix $(CLIENT_DIR) @@ -27,6 +28,7 @@ YARN_FLAGS = --cwd $(CLIENT_BETA_DIR) ENV = env\ COMMIT='$(COMMIT)'\ CHANNEL='$(CHANNEL)'\ + GPG_KEY='$(GPG_KEY)'\ GPG_KEY_PASSPHRASE='$(GPG_KEY_PASSPHRASE)'\ DIST_DIR='$(DIST_DIR)'\ GO='$(GO)'\ diff --git a/scripts/make/build-release.sh b/scripts/make/build-release.sh index e031d049..848da099 100644 --- a/scripts/make/build-release.sh +++ b/scripts/make/build-release.sh @@ -101,6 +101,7 @@ freebsd amd64 0 0 0 freebsd arm 5 0 0 freebsd arm 6 0 0 freebsd arm 7 0 0 +freebsd arm64 0 0 0 linux 386 0 0 i386 linux amd64 0 0 amd64 linux arm 5 0 0 From 3706f559c19523c35dfd55ebd5a0832b5dcf03cf Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Wed, 30 Dec 2020 23:18:15 +0300 Subject: [PATCH 033/141] Pull request: scripts: fix Dockerfile, build-release.sh Merge in DNS/adguard-home from 2276-fix-release-2 to master Updates #2276. Squashed commit of the following: commit cc7edffca6862579a745600b4008428d4c9a73d9 Author: Ainar Garipov Date: Wed Dec 30 22:56:01 2020 +0300 scripts: fix Dockerfile, build-release.sh --- scripts/make/Dockerfile | 2 +- scripts/make/build-release.sh | 25 +++++++++++++++++++++---- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/scripts/make/Dockerfile b/scripts/make/Dockerfile index 8494e7a5..7e9bec86 100644 --- a/scripts/make/Dockerfile +++ b/scripts/make/Dockerfile @@ -45,5 +45,5 @@ CMD [ \ "--no-check-update", \ "-c", "/opt/adguardhome/conf/AdGuardHome.yaml", \ "-h", "0.0.0.0", \ - "-w", "/opt/adguardhome/work", \ + "-w", "/opt/adguardhome/work" \ ] diff --git a/scripts/make/build-release.sh b/scripts/make/build-release.sh index 848da099..bad82e89 100644 --- a/scripts/make/build-release.sh +++ b/scripts/make/build-release.sh @@ -92,6 +92,9 @@ readonly arms='5 readonly mipses='softfloat' +# TODO(a.garipov): Remove armv6, because it was always overwritten by +# armv7. Rename armv7 to armhf. Rename the 386 snap to i386. + # os arch arm mips snap readonly platforms="\ darwin 386 0 0 0 @@ -102,11 +105,11 @@ freebsd arm 5 0 0 freebsd arm 6 0 0 freebsd arm 7 0 0 freebsd arm64 0 0 0 -linux 386 0 0 i386 +linux 386 0 0 386 linux amd64 0 0 amd64 linux arm 5 0 0 -linux arm 6 0 0 -linux arm 7 0 armhf +linux arm 6 0 armv6 +linux arm 7 0 armv7 linux arm64 0 0 arm64 linux mips 0 softfloat 0 linux mips64 0 softfloat 0 @@ -209,9 +212,23 @@ build() { cp -r './scripts/snap/gui'\ "${build_snap_dir}/meta/" + # TODO(a.garipov): Remove this crutch later. + case "$build_snap" + in + ('386') + build_snap_arch="i386" + ;; + ('armv6'|'armv7') + build_snap_arch="armhf" + ;; + (*) + build_snap_arch="$build_snap" + ;; + esac + # Create a snap.yaml file, setting the values. sed -e 's/%VERSION%/'"$version"'/'\ - -e 's/%ARCH%/'"$build_snap"'/'\ + -e 's/%ARCH%/'"$build_snap_arch"'/'\ ./scripts/snap/snap.tmpl.yaml\ >"${build_snap_dir}/meta/snap.yaml" From cfd492cf7cf089a540073d60d025ed1d3c84ecf9 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Thu, 31 Dec 2020 13:55:39 +0300 Subject: [PATCH 034/141] Pull request: scripts: fix build-release Merge in DNS/adguard-home from 2276-fix-release-3 to master Updates #2276. Updates #2507. Squashed commit of the following: commit 1f756975922b403e06dca3e9d44d9ed2edccb97c Author: Ainar Garipov Date: Thu Dec 31 13:45:25 2020 +0300 scripts: fix build-release --- scripts/make/build-release.sh | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/scripts/make/build-release.sh b/scripts/make/build-release.sh index bad82e89..36dae35b 100644 --- a/scripts/make/build-release.sh +++ b/scripts/make/build-release.sh @@ -157,6 +157,7 @@ build() { GOMIPS="${build_mips#0}"\ GOOS="$os"\ VERBOSE="$(( verbose - 1 ))"\ + VERSION="$version"\ OUT="$build_output"\ sh ./scripts/make/go-build.sh\ ; @@ -287,7 +288,7 @@ log "calculating checksums" cd "./${dist}" # Don't use quotes to get word splitting. - sha256sum $(ls *.tar.gz *.zip) > ./checksums.txt + sha256sum $(ls -1 -A -q *.tar.gz *.zip) > ./checksums.txt ) log "writing versions" @@ -295,6 +296,9 @@ log "writing versions" echo "$version" > "./${dist}/version.txt" # Create the verison.json file. +# +# TODO(a.garipov): Perhaps rewrite this as a go run program. Dealing +# with structured documents is really not a Shell's job. readonly version_download_url="https://static.adguard.com/adguardhome/${channel}" readonly version_json="./${dist}/version.json" @@ -319,8 +323,12 @@ echo "{ # Use +f here so that ls works and we don't need to use find. set +f + readonly ar_files="$(ls -1 -A -q "./${dist}/"*.tar.gz "./${dist}/"*.zip)" + readonly ar_files_len="$(echo "$ar_files" | wc -l)" + + i='1' # Don't use quotes to get word splitting. - for f in $(ls "./${dist}/"*.tar.gz "./${dist}/"*.zip) + for f in $ar_files do platform="$f" @@ -334,7 +342,14 @@ echo "{ # Use the filename's base path. filename="${f#./${dist}/}" - echo " \"download_${platform}\": \"${version_download_url}/${filename}\"," >> "$version_json" + if [ "$i" = "$ar_files_len" ] + then + echo " \"download_${platform}\": \"${version_download_url}/${filename}\"" >> "$version_json" + else + echo " \"download_${platform}\": \"${version_download_url}/${filename}\"," >> "$version_json" + fi + + : "$(( i++ ))" done ) From 933ca2af2a088cec49d8b23d257c9de55d19da77 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Thu, 31 Dec 2020 15:12:46 +0300 Subject: [PATCH 035/141] Pull request: scripts: don't use features not guaranteed by posix Merge in DNS/adguard-home from 2276-fix-portability to master Updates #2276. Squashed commit of the following: commit 3019099f2498923495575873daf372598aa72478 Author: Ainar Garipov Date: Thu Dec 31 14:31:02 2020 +0300 scripts: don't use features not guaranteed by posix --- scripts/make/build-release.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/make/build-release.sh b/scripts/make/build-release.sh index 36dae35b..2bc1a04e 100644 --- a/scripts/make/build-release.sh +++ b/scripts/make/build-release.sh @@ -349,7 +349,7 @@ echo "{ echo " \"download_${platform}\": \"${version_download_url}/${filename}\"," >> "$version_json" fi - : "$(( i++ ))" + i="$(( i + 1 ))" done ) From 2298a9ed09eff7fef18152d8bf4ff92e178f7da7 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Thu, 31 Dec 2020 17:37:19 +0300 Subject: [PATCH 036/141] Pull request: scripts: zip output dir recursively Merge in DNS/adguard-home from 2276-fix-zip to master Updates #2276. Updates #2507. Squashed commit of the following: commit 9bd2c53d0aea98fd735655315c33336546f86ccb Author: Ainar Garipov Date: Thu Dec 31 17:12:53 2020 +0300 scripts: zip output dir recursively --- scripts/make/build-release.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/make/build-release.sh b/scripts/make/build-release.sh index 2bc1a04e..4be45616 100644 --- a/scripts/make/build-release.sh +++ b/scripts/make/build-release.sh @@ -184,8 +184,8 @@ build() { case "$build_os" in ('darwin'|'windows') - build_archive="./${dist}/${build_ar}.zip" - zip -9 -q "$build_archive" "$build_dir" + build_archive="${PWD}/${dist}/${build_ar}.zip" + ( cd "${dist}/${1}" && zip -9 -q -r "$build_archive" "./AdGuardHome" ) ;; (*) build_archive="./${dist}/${build_ar}.tar.gz" From 76d95602928201151ee19d56699110cee4cc8c97 Mon Sep 17 00:00:00 2001 From: Stefan Kuhn Date: Tue, 5 Jan 2021 18:49:28 +0100 Subject: [PATCH 037/141] Fix typo in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f09da329..725d56db 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ AdGuard Home is a network-wide software for blocking ads & tracking. After you set it up, it'll cover ALL your home devices, and you don't need any client-side software for that. -It operates as a DNS server that re-routes tracking domains to a "black hole," thus preventing your devices from connecting to those servers. It's based on software we use for our public [AdGuard DNS](https://adguard.com/en/adguard-dns/overview.html) servers -- both share a lot of common code. +It operates as a DNS server that re-routes tracking domains to a "black hole", thus preventing your devices from connecting to those servers. It's based on software we use for our public [AdGuard DNS](https://adguard.com/en/adguard-dns/overview.html) servers -- both share a lot of common code. * [Getting Started](#getting-started) * [Comparing AdGuard Home to other solutions](#comparison) From ce55625d708cc34f9f3cf8e400c8ebb47048f766 Mon Sep 17 00:00:00 2001 From: Andrey Meshkov Date: Wed, 6 Jan 2021 12:41:53 +0300 Subject: [PATCH 038/141] Fix #2519 --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 725d56db..e694452f 100644 --- a/README.md +++ b/README.md @@ -170,7 +170,8 @@ You will need this to build AdGuard Home: * [go](https://golang.org/dl/) v1.14 or later. * [node.js](https://nodejs.org/en/download/) v10.16.2 or later. - * [npm](https://www.npmjs.com/) v6.14 or later. + * [npm](https://www.npmjs.com/) v6.14 or later (temporary requirement, TODO: remove when redesign is finished). + * [yarn](https://yarnpkg.com/) v1.22.5 or later. ### Building From 89915e35bdaf698e195fe82d79021c50bc8ecd92 Mon Sep 17 00:00:00 2001 From: Eugene Burkov Date: Mon, 11 Jan 2021 13:17:22 +0300 Subject: [PATCH 039/141] Pull request: fix beta client bug Merge in DNS/adguard-home from beta-client-bug to master Squashed commit of the following: commit 641f8b5eb3827e1fd9b15e60773e365206dc1f49 Author: Eugene Burkov Date: Mon Jan 11 11:54:17 2021 +0300 home: fix beta client bug --- internal/home/controlinstall.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/internal/home/controlinstall.go b/internal/home/controlinstall.go index 3920af3c..7d67d140 100644 --- a/internal/home/controlinstall.go +++ b/internal/home/controlinstall.go @@ -371,9 +371,11 @@ func (web *Web) handleInstallConfigure(w http.ResponseWriter, r *http.Request) { go func() { _ = web.httpServer.Shutdown(context.TODO()) }() - go func() { - _ = web.httpServerBeta.Shutdown(context.TODO()) - }() + if web.httpServerBeta != nil { + go func() { + _ = web.httpServerBeta.Shutdown(context.TODO()) + }() + } } } From 18be0ad80baa7e729322695a29c0b15b10e12887 Mon Sep 17 00:00:00 2001 From: Eugene Burkov Date: Mon, 11 Jan 2021 15:06:54 +0300 Subject: [PATCH 040/141] Pull request: beta client on development channel Merge in DNS/adguard-home from beta-client-channels to master Squashed commit of the following: commit c65611c1743aedd14779c95ed587330aff611b1d Author: Eugene Burkov Date: Mon Jan 11 14:09:21 2021 +0300 home: activate beta client on development channel by default --- internal/home/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/home/config.go b/internal/home/config.go index 92b2326c..840678d4 100644 --- a/internal/home/config.go +++ b/internal/home/config.go @@ -177,7 +177,7 @@ func initConfig() { config.DHCP.Conf4.ICMPTimeout = 1000 config.DHCP.Conf6.LeaseDuration = 86400 - if updateChannel == "none" || updateChannel == "edge" { + if updateChannel == "none" || updateChannel == "edge" || updateChannel == "development" { config.BetaBindPort = 3001 } } From 18097edee1aa02382484119dcc6c0fac6be9282e Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Mon, 11 Jan 2021 15:59:42 +0300 Subject: [PATCH 041/141] Pull request: scripts: ignore unchecked errors in generated code Merge in DNS/adguard-home from fix-errcheck to master Squashed commit of the following: commit 1cc4e12c1d49612046ce6f0a1b56920ffcad2526 Author: Ainar Garipov Date: Mon Jan 11 15:28:42 2021 +0300 scripts: ignore unchecked errors in generated code --- HACKING.md | 5 +++-- scripts/make/go-lint.sh | 6 ++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/HACKING.md b/HACKING.md index 750e5f47..abfa86e8 100644 --- a/HACKING.md +++ b/HACKING.md @@ -19,8 +19,9 @@ The rules are mostly sorted in the alphabetical order. pkg: fix the network error logging issue ``` - Where `pkg` is the package where most changes took place. If there are - several such packages, or the change is top-level only, write `all`. + Where `pkg` is the directory or Go package (without the `internal/` part) + where most changes took place. If there are several such packages, or the + change is top-level only, write `all`. * Keep your commit messages, including headers, to eighty (**80**) columns. diff --git a/scripts/make/go-lint.sh b/scripts/make/go-lint.sh index 22a57616..417b6b57 100644 --- a/scripts/make/go-lint.sh +++ b/scripts/make/go-lint.sh @@ -116,10 +116,12 @@ nilness ./... # shadow --strict ./... # TODO(a.garipov): Enable errcheck fully after handling all errors, -# including the deferred ones, properly. Also, perhaps, enable --blank. +# including the deferred and generated ones, properly. Also, perhaps, +# enable --blank. +# # errcheck ./... exit_on_output sh -c ' - errcheck --asserts ./... |\ + errcheck --asserts --ignoregenerated ./... |\ { grep -e "defer" -e "_test\.go:" -v || exit 0; } ' From bba74859e2b58a82b0984e3459e1cd65bfa81e4b Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Tue, 12 Jan 2021 17:45:19 +0300 Subject: [PATCH 042/141] Pull request: openapi: fix incorrect parameter name Merge in DNS/adguard-home from fix-openapi to master Squashed commit of the following: commit a448b80a57851d44634c00f0fd986f58b35dae80 Author: Ainar Garipov Date: Tue Jan 12 17:27:32 2021 +0300 openapi: fix incorrect parameter name --- openapi/openapi.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index ab1eac69..751eb073 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -2324,7 +2324,7 @@ 'type': 'object' 'description': 'Login request data' 'properties': - 'username': + 'name': 'type': 'string' 'description': 'User name' 'password': From 0d67aa251d18f8ab47cfd90072e9d0387dff4224 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Wed, 13 Jan 2021 16:18:51 +0300 Subject: [PATCH 043/141] Pull request: 2546 updater fix Merge in DNS/adguard-home from 2546-updater-fix to master Closes #2546. Squashed commit of the following: commit af243c9fad710efe099506fda281e628c3e5ec30 Author: Ainar Garipov Date: Wed Jan 13 14:33:37 2021 +0300 updater: fix go 1.14 compat commit 742fba24b300ce51c04acb586996c3c75e56ea20 Author: Ainar Garipov Date: Wed Jan 13 13:58:27 2021 +0300 util: imp error check commit c2bdbce8af657a7f4b7e05c018cfacba86e06753 Author: Ainar Garipov Date: Mon Jan 11 18:51:26 2021 +0300 all: fix and refactor update checking --- HACKING.md | 8 + internal/home/config.go | 3 +- internal/home/control.go | 5 +- internal/home/controlupdate.go | 16 +- internal/home/home.go | 63 +--- internal/home/options.go | 4 +- internal/update/check.go | 114 ------- internal/update/update_test.go | 215 ------------ internal/updater/check.go | 117 +++++++ .../testdata}/AdGuardHome.tar.gz | Bin .../test => updater/testdata}/AdGuardHome.zip | Bin internal/{update => updater}/updater.go | 178 ++++++---- internal/updater/updater_test.go | 316 ++++++++++++++++++ internal/util/helpers.go | 2 +- internal/version/version.go | 53 +++ main.go | 14 +- scripts/make/build-release.sh | 11 + scripts/make/go-build.sh | 15 +- 18 files changed, 669 insertions(+), 465 deletions(-) delete mode 100644 internal/update/check.go delete mode 100644 internal/update/update_test.go create mode 100644 internal/updater/check.go rename internal/{update/test => updater/testdata}/AdGuardHome.tar.gz (100%) rename internal/{update/test => updater/testdata}/AdGuardHome.zip (100%) rename internal/{update => updater}/updater.go (70%) create mode 100644 internal/updater/updater_test.go create mode 100644 internal/version/version.go diff --git a/HACKING.md b/HACKING.md index abfa86e8..dc291bb7 100644 --- a/HACKING.md +++ b/HACKING.md @@ -46,6 +46,14 @@ The rules are mostly sorted in the alphabetical order. * Avoid `new`, especially with structs. + * Check against empty strings like this: + + ```go + if s == "" { + // … + } + ``` + * Constructors should validate their arguments and return meaningful errors. As a corollary, avoid lazy initialization. diff --git a/internal/home/config.go b/internal/home/config.go index 840678d4..f7b799dc 100644 --- a/internal/home/config.go +++ b/internal/home/config.go @@ -11,6 +11,7 @@ import ( "github.com/AdguardTeam/AdGuardHome/internal/dnsforward" "github.com/AdguardTeam/AdGuardHome/internal/querylog" "github.com/AdguardTeam/AdGuardHome/internal/stats" + "github.com/AdguardTeam/AdGuardHome/internal/version" "github.com/AdguardTeam/golibs/file" "github.com/AdguardTeam/golibs/log" yaml "gopkg.in/yaml.v2" @@ -177,7 +178,7 @@ func initConfig() { config.DHCP.Conf4.ICMPTimeout = 1000 config.DHCP.Conf6.LeaseDuration = 86400 - if updateChannel == "none" || updateChannel == "edge" || updateChannel == "development" { + if ch := version.Channel(); ch == "edge" || ch == "development" { config.BetaBindPort = 3001 } } diff --git a/internal/home/control.go b/internal/home/control.go index 0bdd8aab..01bf00ce 100644 --- a/internal/home/control.go +++ b/internal/home/control.go @@ -11,6 +11,7 @@ import ( "strings" "github.com/AdguardTeam/AdGuardHome/internal/dnsforward" + "github.com/AdguardTeam/AdGuardHome/internal/version" "github.com/AdguardTeam/golibs/log" "github.com/NYTimes/gziphandler" ) @@ -53,7 +54,7 @@ func handleStatus(w http.ResponseWriter, r *http.Request) { "http_port": config.BindPort, "dns_port": config.DNS.Port, "running": isRunning(), - "version": versionString, + "version": version.Version(), "language": config.Language, "protection_enabled": c.ProtectionEnabled, @@ -118,7 +119,7 @@ func registerControlHandlers() { } func httpRegister(method, url string, handler func(http.ResponseWriter, *http.Request)) { - if len(method) == 0 { + if method == "" { // "/dns-query" handler doesn't need auth, gzip and isn't restricted by 1 HTTP method Context.mux.HandleFunc(url, postInstall(handler)) return diff --git a/internal/home/controlupdate.go b/internal/home/controlupdate.go index ff9dc4ab..a502b902 100644 --- a/internal/home/controlupdate.go +++ b/internal/home/controlupdate.go @@ -12,7 +12,7 @@ import ( "time" "github.com/AdguardTeam/AdGuardHome/internal/sysutil" - "github.com/AdguardTeam/AdGuardHome/internal/update" + "github.com/AdguardTeam/AdGuardHome/internal/updater" "github.com/AdguardTeam/golibs/log" ) @@ -47,13 +47,13 @@ func handleGetVersionJSON(w http.ResponseWriter, r *http.Request) { } } - var info update.VersionInfo + var info updater.VersionInfo for i := 0; i != 3; i++ { func() { Context.controlLock.Lock() defer Context.controlLock.Unlock() - info, err = Context.updater.GetVersionResponse(req.RecheckNow) + info, err = Context.updater.VersionInfo(req.RecheckNow) }() if err != nil { @@ -75,7 +75,9 @@ func handleGetVersionJSON(w http.ResponseWriter, r *http.Request) { break } if err != nil { - httpError(w, http.StatusBadGateway, "Couldn't get version check json from %s: %T %s\n", versionCheckURL, err, err) + vcu := Context.updater.VersionCheckURL() + httpError(w, http.StatusBadGateway, "Couldn't get version check json from %s: %T %s\n", vcu, err, err) + return } @@ -88,12 +90,12 @@ func handleGetVersionJSON(w http.ResponseWriter, r *http.Request) { // Perform an update procedure to the latest available version func handleUpdate(w http.ResponseWriter, _ *http.Request) { - if len(Context.updater.NewVersion) == 0 { + if Context.updater.NewVersion() == "" { httpError(w, http.StatusBadRequest, "/update request isn't allowed now") return } - err := Context.updater.DoUpdate() + err := Context.updater.Update() if err != nil { httpError(w, http.StatusInternalServerError, "%s", err) return @@ -108,7 +110,7 @@ func handleUpdate(w http.ResponseWriter, _ *http.Request) { } // Convert version.json data to our JSON response -func getVersionResp(info update.VersionInfo) []byte { +func getVersionResp(info updater.VersionInfo) []byte { ret := make(map[string]interface{}) ret["can_autoupdate"] = false ret["new_version"] = info.NewVersion diff --git a/internal/home/home.go b/internal/home/home.go index aeb875a4..fbead57a 100644 --- a/internal/home/home.go +++ b/internal/home/home.go @@ -27,8 +27,9 @@ import ( "github.com/AdguardTeam/AdGuardHome/internal/querylog" "github.com/AdguardTeam/AdGuardHome/internal/stats" "github.com/AdguardTeam/AdGuardHome/internal/sysutil" - "github.com/AdguardTeam/AdGuardHome/internal/update" + "github.com/AdguardTeam/AdGuardHome/internal/updater" "github.com/AdguardTeam/AdGuardHome/internal/util" + "github.com/AdguardTeam/AdGuardHome/internal/version" "github.com/AdguardTeam/golibs/log" "gopkg.in/natefinch/lumberjack.v2" ) @@ -38,15 +39,6 @@ const ( configSyslog = "syslog" ) -// Update-related variables -var ( - versionString = "dev" - updateChannel = "none" - versionCheckURL = "" - ARMVersion = "" - MIPSVersion = "" -) - // Global context type homeContext struct { // Modules @@ -65,7 +57,7 @@ type homeContext struct { web *Web // Web (HTTP, HTTPS) module tls *TLSMod // TLS module autoHosts util.AutoHosts // IP-hostname pairs taken from system configuration (e.g. /etc/hosts) files - updater *update.Updater + updater *updater.Updater ipDetector *ipDetector @@ -99,14 +91,7 @@ func (c *homeContext) getDataDir() string { var Context homeContext // Main is the entry point -func Main(version, channel, armVer, mipsVer string) { - // Init update-related global variables - versionString = version - updateChannel = channel - ARMVersion = armVer - MIPSVersion = mipsVer - versionCheckURL = "https://static.adguard.com/adguardhome/" + updateChannel + "/version.json" - +func Main() { // config can be specified, which reads options from there, but other command line flags have to override config values // therefore, we must do it manually instead of using a lib args := loadOptions() @@ -139,20 +124,6 @@ func Main(version, channel, armVer, mipsVer string) { run(args) } -// version - returns the current version string -func version() string { - // TODO(a.garipov): I'm pretty sure we can extract some of this stuff - // from the build info. - msg := "AdGuard Home, version %s, channel %s, arch %s %s" - if ARMVersion != "" { - msg = msg + " v" + ARMVersion - } else if MIPSVersion != "" { - msg = msg + " " + MIPSVersion - } - - return fmt.Sprintf(msg, versionString, updateChannel, runtime.GOOS, runtime.GOARCH) -} - func setupContext(args options) { Context.runningAsService = args.runningAsService Context.disableUpdate = args.disableUpdate @@ -214,15 +185,16 @@ func setupConfig(args options) { Context.autoHosts.Init("") - Context.updater = update.NewUpdater(update.Config{ - Client: Context.client, - WorkDir: Context.workDir, - VersionURL: versionCheckURL, - VersionString: versionString, - OS: runtime.GOOS, - Arch: runtime.GOARCH, - ARMVersion: ARMVersion, - ConfigName: config.getConfigFilename(), + Context.updater = updater.NewUpdater(&updater.Config{ + Client: Context.client, + Version: version.Version(), + Channel: version.Channel(), + GOARCH: runtime.GOARCH, + GOOS: runtime.GOOS, + GOARM: version.GOARM(), + GOMIPS: version.GOMIPS(), + WorkDir: Context.workDir, + ConfName: config.getConfigFilename(), }) Context.clients.Init(config.Clients, Context.dhcpServer, &Context.autoHosts) @@ -260,7 +232,7 @@ func run(args options) { memoryUsage(args) // print the first message after logger is configured - log.Println(version()) + log.Println(version.Full()) log.Debug("Current working directory is %s", Context.workDir) if args.runningAsService { log.Info("AdGuard Home is running as a service") @@ -690,10 +662,11 @@ func customDialContext(ctx context.Context, network, addr string) (net.Conn, err return nil, agherr.Many(fmt.Sprintf("couldn't dial to %s", addr), dialErrs...) } -func getHTTPProxy(req *http.Request) (*url.URL, error) { - if len(config.ProxyURL) == 0 { +func getHTTPProxy(_ *http.Request) (*url.URL, error) { + if config.ProxyURL == "" { return nil, nil } + return url.Parse(config.ProxyURL) } diff --git a/internal/home/options.go b/internal/home/options.go index 514ed3a1..0493e856 100644 --- a/internal/home/options.go +++ b/internal/home/options.go @@ -4,6 +4,8 @@ import ( "fmt" "os" "strconv" + + "github.com/AdguardTeam/AdGuardHome/internal/version" ) // options passed from command-line arguments @@ -180,7 +182,7 @@ var versionArg = arg{ "Show the version and exit", "version", "", nil, nil, func(o options, exec string) (effect, error) { - return func() error { fmt.Println(version()); os.Exit(0); return nil }, nil + return func() error { fmt.Println(version.Full()); os.Exit(0); return nil }, nil }, func(o options) []string { return nil }, } diff --git a/internal/update/check.go b/internal/update/check.go deleted file mode 100644 index e83ab5c2..00000000 --- a/internal/update/check.go +++ /dev/null @@ -1,114 +0,0 @@ -package update - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "strings" - "time" - - "github.com/AdguardTeam/AdGuardHome/internal/aghio" -) - -const versionCheckPeriod = 8 * 60 * 60 - -// VersionInfo - VersionInfo -type VersionInfo struct { - NewVersion string // New version string - Announcement string // Announcement text - AnnouncementURL string // Announcement URL - SelfUpdateMinVersion string // Min version starting with which we can auto-update - CanAutoUpdate bool // If true - we can auto-update -} - -// MaxResponseSize is responses on server's requests maximum length in bytes. -const MaxResponseSize = 64 * 1024 - -// GetVersionResponse - downloads version.json (if needed) and deserializes it -func (u *Updater) GetVersionResponse(forceRecheck bool) (VersionInfo, error) { - if !forceRecheck && - u.versionCheckLastTime.Unix()+versionCheckPeriod > time.Now().Unix() { - return u.parseVersionResponse(u.versionJSON) - } - - resp, err := u.Client.Get(u.VersionURL) - if err != nil { - return VersionInfo{}, fmt.Errorf("updater: HTTP GET %s: %w", u.VersionURL, err) - } - defer resp.Body.Close() - - resp.Body, err = aghio.LimitReadCloser(resp.Body, MaxResponseSize) - if err != nil { - return VersionInfo{}, fmt.Errorf("updater: LimitReadCloser: %w", err) - } - defer resp.Body.Close() - - // This use of ReadAll is safe, because we just limited the appropriate - // ReadCloser. - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return VersionInfo{}, fmt.Errorf("updater: HTTP GET %s: %w", u.VersionURL, err) - } - - u.versionJSON = body - u.versionCheckLastTime = time.Now() - - return u.parseVersionResponse(body) -} - -func (u *Updater) parseVersionResponse(data []byte) (VersionInfo, error) { - info := VersionInfo{} - versionJSON := make(map[string]interface{}) - err := json.Unmarshal(data, &versionJSON) - if err != nil { - return info, fmt.Errorf("version.json: %w", err) - } - - var ok1, ok2, ok3, ok4 bool - info.NewVersion, ok1 = versionJSON["version"].(string) - info.Announcement, ok2 = versionJSON["announcement"].(string) - info.AnnouncementURL, ok3 = versionJSON["announcement_url"].(string) - info.SelfUpdateMinVersion, ok4 = versionJSON["selfupdate_min_version"].(string) - if !ok1 || !ok2 || !ok3 || !ok4 { - return info, fmt.Errorf("version.json: invalid data") - } - - packageURL, ok := u.getDownloadURL(versionJSON) - - if ok && - info.NewVersion != u.VersionString && - strings.TrimPrefix(u.VersionString, "v") >= strings.TrimPrefix(info.SelfUpdateMinVersion, "v") { - info.CanAutoUpdate = true - } - - u.NewVersion = info.NewVersion - u.PackageURL = packageURL - - return info, nil -} - -// Get download URL for the current GOOS/GOARCH/ARMVersion -func (u *Updater) getDownloadURL(json map[string]interface{}) (string, bool) { - var key string - - if u.Arch == "arm" && u.ARMVersion != "" { - // the key is: - // download_linux_armv5 for ARMv5 - // download_linux_armv6 for ARMv6 - // download_linux_armv7 for ARMv7 - key = fmt.Sprintf("download_%s_%sv%s", u.OS, u.Arch, u.ARMVersion) - } - - val, ok := json[key] - if !ok { - // the key is download_linux_arm or download_linux_arm64 for regular ARM versions - key = fmt.Sprintf("download_%s_%s", u.OS, u.Arch) - val, ok = json[key] - } - - if !ok { - return "", false - } - - return val.(string), true -} diff --git a/internal/update/update_test.go b/internal/update/update_test.go deleted file mode 100644 index ceca2b9d..00000000 --- a/internal/update/update_test.go +++ /dev/null @@ -1,215 +0,0 @@ -package update - -import ( - "fmt" - "io/ioutil" - "net" - "net/http" - "os" - "testing" - - "github.com/AdguardTeam/AdGuardHome/internal/testutil" - "github.com/stretchr/testify/assert" -) - -func TestMain(m *testing.M) { - testutil.DiscardLogOutput(m) -} - -func startHTTPServer(data string) (net.Listener, uint16) { - mux := http.NewServeMux() - mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - _, _ = w.Write([]byte(data)) - }) - - listener, err := net.Listen("tcp", ":0") - if err != nil { - panic(err) - } - - go func() { _ = http.Serve(listener, mux) }() - return listener, uint16(listener.Addr().(*net.TCPAddr).Port) -} - -func TestUpdateGetVersion(t *testing.T) { - const jsonData = `{ - "version": "v0.103.0-beta2", - "announcement": "AdGuard Home v0.103.0-beta2 is now available!", - "announcement_url": "https://github.com/AdguardTeam/AdGuardHome/internal/releases", - "selfupdate_min_version": "v0.0", - "download_windows_amd64": "https://static.adguard.com/adguardhome/beta/AdGuardHome_windows_amd64.zip", - "download_windows_386": "https://static.adguard.com/adguardhome/beta/AdGuardHome_windows_386.zip", - "download_darwin_amd64": "https://static.adguard.com/adguardhome/beta/AdGuardHome_darwin_amd64.zip", - "download_darwin_386": "https://static.adguard.com/adguardhome/beta/AdGuardHome_darwin_386.zip", - "download_linux_amd64": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_amd64.tar.gz", - "download_linux_386": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_386.tar.gz", - "download_linux_arm": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv6.tar.gz", - "download_linux_armv5": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv5.tar.gz", - "download_linux_armv6": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv6.tar.gz", - "download_linux_armv7": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv7.tar.gz", - "download_linux_arm64": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_arm64.tar.gz", - "download_linux_mips": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mips_softfloat.tar.gz", - "download_linux_mipsle": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mipsle_softfloat.tar.gz", - "download_linux_mips64": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mips64_softfloat.tar.gz", - "download_linux_mips64le": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mips64le_softfloat.tar.gz", - "download_freebsd_386": "https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_386.tar.gz", - "download_freebsd_amd64": "https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_amd64.tar.gz", - "download_freebsd_arm": "https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_armv6.tar.gz", - "download_freebsd_armv5": "https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_armv5.tar.gz", - "download_freebsd_armv6": "https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_armv6.tar.gz", - "download_freebsd_armv7": "https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_armv7.tar.gz", - "download_freebsd_arm64": "https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_arm64.tar.gz" -}` - - l, lport := startHTTPServer(jsonData) - defer func() { _ = l.Close() }() - - u := NewUpdater(Config{ - Client: &http.Client{}, - VersionURL: fmt.Sprintf("http://127.0.0.1:%d/", lport), - OS: "linux", - Arch: "arm", - VersionString: "v0.103.0-beta1", - }) - - info, err := u.GetVersionResponse(false) - assert.Nil(t, err) - assert.Equal(t, "v0.103.0-beta2", info.NewVersion) - assert.Equal(t, "AdGuard Home v0.103.0-beta2 is now available!", info.Announcement) - assert.Equal(t, "https://github.com/AdguardTeam/AdGuardHome/internal/releases", info.AnnouncementURL) - assert.Equal(t, "v0.0", info.SelfUpdateMinVersion) - assert.True(t, info.CanAutoUpdate) - - _ = l.Close() - - // check cached - _, err = u.GetVersionResponse(false) - assert.Nil(t, err) -} - -func TestUpdate(t *testing.T) { - _ = os.Mkdir("aghtest", 0o755) - defer func() { - _ = os.RemoveAll("aghtest") - }() - - // create "current" files - assert.Nil(t, ioutil.WriteFile("aghtest/AdGuardHome", []byte("AdGuardHome"), 0o755)) - assert.Nil(t, ioutil.WriteFile("aghtest/README.md", []byte("README.md"), 0o644)) - assert.Nil(t, ioutil.WriteFile("aghtest/LICENSE.txt", []byte("LICENSE.txt"), 0o644)) - assert.Nil(t, ioutil.WriteFile("aghtest/AdGuardHome.yaml", []byte("AdGuardHome.yaml"), 0o644)) - - // start server for returning package file - pkgData, err := ioutil.ReadFile("test/AdGuardHome.tar.gz") - assert.Nil(t, err) - l, lport := startHTTPServer(string(pkgData)) - defer func() { _ = l.Close() }() - - u := NewUpdater(Config{ - Client: &http.Client{}, - PackageURL: fmt.Sprintf("http://127.0.0.1:%d/AdGuardHome.tar.gz", lport), - VersionString: "v0.103.0", - NewVersion: "v0.103.1", - ConfigName: "aghtest/AdGuardHome.yaml", - WorkDir: "aghtest", - }) - - assert.Nil(t, u.prepare()) - u.currentExeName = "aghtest/AdGuardHome" - assert.Nil(t, u.downloadPackageFile(u.PackageURL, u.packageName)) - assert.Nil(t, u.unpack()) - // assert.Nil(t, u.check()) - assert.Nil(t, u.backup()) - assert.Nil(t, u.replace()) - u.clean() - - // check backup files - d, err := ioutil.ReadFile("aghtest/agh-backup/AdGuardHome.yaml") - assert.Nil(t, err) - assert.Equal(t, "AdGuardHome.yaml", string(d)) - - d, err = ioutil.ReadFile("aghtest/agh-backup/AdGuardHome") - assert.Nil(t, err) - assert.Equal(t, "AdGuardHome", string(d)) - - // check updated files - d, err = ioutil.ReadFile("aghtest/AdGuardHome") - assert.Nil(t, err) - assert.Equal(t, "1", string(d)) - - d, err = ioutil.ReadFile("aghtest/README.md") - assert.Nil(t, err) - assert.Equal(t, "2", string(d)) - - d, err = ioutil.ReadFile("aghtest/LICENSE.txt") - assert.Nil(t, err) - assert.Equal(t, "3", string(d)) - - d, err = ioutil.ReadFile("aghtest/AdGuardHome.yaml") - assert.Nil(t, err) - assert.Equal(t, "AdGuardHome.yaml", string(d)) -} - -func TestUpdateWindows(t *testing.T) { - _ = os.Mkdir("aghtest", 0o755) - defer func() { - _ = os.RemoveAll("aghtest") - }() - - // create "current" files - assert.Nil(t, ioutil.WriteFile("aghtest/AdGuardHome.exe", []byte("AdGuardHome.exe"), 0o755)) - assert.Nil(t, ioutil.WriteFile("aghtest/README.md", []byte("README.md"), 0o644)) - assert.Nil(t, ioutil.WriteFile("aghtest/LICENSE.txt", []byte("LICENSE.txt"), 0o644)) - assert.Nil(t, ioutil.WriteFile("aghtest/AdGuardHome.yaml", []byte("AdGuardHome.yaml"), 0o644)) - - // start server for returning package file - pkgData, err := ioutil.ReadFile("test/AdGuardHome.zip") - assert.Nil(t, err) - l, lport := startHTTPServer(string(pkgData)) - defer func() { _ = l.Close() }() - - u := NewUpdater(Config{ - WorkDir: "aghtest", - Client: &http.Client{}, - PackageURL: fmt.Sprintf("http://127.0.0.1:%d/AdGuardHome.zip", lport), - OS: "windows", - VersionString: "v0.103.0", - NewVersion: "v0.103.1", - ConfigName: "aghtest/AdGuardHome.yaml", - }) - - assert.Nil(t, u.prepare()) - u.currentExeName = "aghtest/AdGuardHome.exe" - assert.Nil(t, u.downloadPackageFile(u.PackageURL, u.packageName)) - assert.Nil(t, u.unpack()) - // assert.Nil(t, u.check()) - assert.Nil(t, u.backup()) - assert.Nil(t, u.replace()) - u.clean() - - // check backup files - d, err := ioutil.ReadFile("aghtest/agh-backup/AdGuardHome.yaml") - assert.Nil(t, err) - assert.Equal(t, "AdGuardHome.yaml", string(d)) - - d, err = ioutil.ReadFile("aghtest/agh-backup/AdGuardHome.exe") - assert.Nil(t, err) - assert.Equal(t, "AdGuardHome.exe", string(d)) - - // check updated files - d, err = ioutil.ReadFile("aghtest/AdGuardHome.exe") - assert.Nil(t, err) - assert.Equal(t, "1", string(d)) - - d, err = ioutil.ReadFile("aghtest/README.md") - assert.Nil(t, err) - assert.Equal(t, "2", string(d)) - - d, err = ioutil.ReadFile("aghtest/LICENSE.txt") - assert.Nil(t, err) - assert.Equal(t, "3", string(d)) - - d, err = ioutil.ReadFile("aghtest/AdGuardHome.yaml") - assert.Nil(t, err) - assert.Equal(t, "AdGuardHome.yaml", string(d)) -} diff --git a/internal/updater/check.go b/internal/updater/check.go new file mode 100644 index 00000000..6418ffb6 --- /dev/null +++ b/internal/updater/check.go @@ -0,0 +1,117 @@ +package updater + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "strings" + "time" + + "github.com/AdguardTeam/AdGuardHome/internal/aghio" +) + +// TODO(a.garipov): Make configurable. +const versionCheckPeriod = 8 * time.Hour + +// VersionInfo contains information about a new version. +type VersionInfo struct { + NewVersion string + Announcement string + AnnouncementURL string + SelfUpdateMinVersion string + CanAutoUpdate bool +} + +// MaxResponseSize is responses on server's requests maximum length in bytes. +const MaxResponseSize = 64 * 1024 + +// VersionInfo downloads the latest version information. If forceRecheck is +// false and there are cached results, those results are returned. +func (u *Updater) VersionInfo(forceRecheck bool) (VersionInfo, error) { + u.mu.Lock() + defer u.mu.Unlock() + + now := time.Now() + recheckTime := u.prevCheckTime.Add(versionCheckPeriod) + if !forceRecheck && now.Before(recheckTime) { + return u.prevCheckResult, u.prevCheckError + } + + vcu := u.versionCheckURL + resp, err := u.client.Get(vcu) + if err != nil { + return VersionInfo{}, fmt.Errorf("updater: HTTP GET %s: %w", vcu, err) + } + defer resp.Body.Close() + + resp.Body, err = aghio.LimitReadCloser(resp.Body, MaxResponseSize) + if err != nil { + return VersionInfo{}, fmt.Errorf("updater: LimitReadCloser: %w", err) + } + defer resp.Body.Close() + + // This use of ReadAll is safe, because we just limited the appropriate + // ReadCloser. + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return VersionInfo{}, fmt.Errorf("updater: HTTP GET %s: %w", vcu, err) + } + + u.prevCheckTime = time.Now() + u.prevCheckResult, u.prevCheckError = u.parseVersionResponse(body) + + return u.prevCheckResult, u.prevCheckError +} + +func (u *Updater) parseVersionResponse(data []byte) (VersionInfo, error) { + info := VersionInfo{} + versionJSON := make(map[string]interface{}) + err := json.Unmarshal(data, &versionJSON) + if err != nil { + return info, fmt.Errorf("version.json: %w", err) + } + + var ok1, ok2, ok3, ok4 bool + info.NewVersion, ok1 = versionJSON["version"].(string) + info.Announcement, ok2 = versionJSON["announcement"].(string) + info.AnnouncementURL, ok3 = versionJSON["announcement_url"].(string) + info.SelfUpdateMinVersion, ok4 = versionJSON["selfupdate_min_version"].(string) + if !ok1 || !ok2 || !ok3 || !ok4 { + return info, fmt.Errorf("version.json: invalid data") + } + + packageURL, ok := u.downloadURL(versionJSON) + if ok && + info.NewVersion != u.version && + strings.TrimPrefix(u.version, "v") >= strings.TrimPrefix(info.SelfUpdateMinVersion, "v") { + info.CanAutoUpdate = true + } + + u.newVersion = info.NewVersion + u.packageURL = packageURL + + return info, nil +} + +// downloadURL returns the download URL for current build. +func (u *Updater) downloadURL(json map[string]interface{}) (string, bool) { + var key string + + if u.goarch == "arm" && u.goarm != "" { + key = fmt.Sprintf("download_%s_%sv%s", u.goos, u.goarch, u.goarm) + } else if u.goarch == "mips" && u.gomips != "" { + key = fmt.Sprintf("download_%s_%s_%s", u.goos, u.goarch, u.gomips) + } + + val, ok := json[key] + if !ok { + key = fmt.Sprintf("download_%s_%s", u.goos, u.goarch) + val, ok = json[key] + } + + if !ok { + return "", false + } + + return val.(string), true +} diff --git a/internal/update/test/AdGuardHome.tar.gz b/internal/updater/testdata/AdGuardHome.tar.gz similarity index 100% rename from internal/update/test/AdGuardHome.tar.gz rename to internal/updater/testdata/AdGuardHome.tar.gz diff --git a/internal/update/test/AdGuardHome.zip b/internal/updater/testdata/AdGuardHome.zip similarity index 100% rename from internal/update/test/AdGuardHome.zip rename to internal/updater/testdata/AdGuardHome.zip diff --git a/internal/update/updater.go b/internal/updater/updater.go similarity index 70% rename from internal/update/updater.go rename to internal/updater/updater.go index b6901889..8d0bee5f 100644 --- a/internal/update/updater.go +++ b/internal/updater/updater.go @@ -1,5 +1,5 @@ -// Package update provides an updater for AdGuardHome. -package update +// Package updater provides an updater for AdGuardHome. +package updater import ( "archive/tar" @@ -9,62 +9,106 @@ import ( "io" "io/ioutil" "net/http" + "net/url" "os" "os/exec" + "path" "path/filepath" "strings" + "sync" "time" "github.com/AdguardTeam/AdGuardHome/internal/aghio" "github.com/AdguardTeam/AdGuardHome/internal/util" + "github.com/AdguardTeam/AdGuardHome/internal/version" "github.com/AdguardTeam/golibs/log" ) -// Updater - Updater +// Updater is the AdGuard Home updater. type Updater struct { - Config // Updater configuration + client *http.Client + version string + channel string + goarch string + goos string + goarm string + gomips string + + workDir string + confName string + versionCheckURL string + + // mu protects all fields below. + mu *sync.RWMutex + + // TODO(a.garipov): See if all of these fields actually have to be in + // this struct. currentExeName string // current binary executable - updateDir string // "work_dir/agh-update-v0.103.0" - packageName string // "work_dir/agh-update-v0.103.0/pkg_name.tar.gz" - backupDir string // "work_dir/agh-backup" - backupExeName string // "work_dir/agh-backup/AdGuardHome[.exe]" - updateExeName string // "work_dir/agh-update-v0.103.0/AdGuardHome[.exe]" + updateDir string // "workDir/agh-update-v0.103.0" + packageName string // "workDir/agh-update-v0.103.0/pkg_name.tar.gz" + backupDir string // "workDir/agh-backup" + backupExeName string // "workDir/agh-backup/AdGuardHome[.exe]" + updateExeName string // "workDir/agh-update-v0.103.0/AdGuardHome[.exe]" unpackedFiles []string - // cached version.json to avoid hammering github.io for each page reload - versionJSON []byte - versionCheckLastTime time.Time + newVersion string + packageURL string + + // Cached fields to prevent too many API requests. + prevCheckError error + prevCheckTime time.Time + prevCheckResult VersionInfo } -// Config - updater config +// Config is the AdGuard Home updater configuration. type Config struct { Client *http.Client - VersionURL string // version.json URL - VersionString string - OS string // GOOS - Arch string // GOARCH - ARMVersion string // ARM version, e.g. "6" - NewVersion string // VersionInfo.NewVersion - PackageURL string // VersionInfo.PackageURL - ConfigName string // current config file ".../AdGuardHome.yaml" - WorkDir string // updater work dir (where backup/upd dirs will be created) + Version string + Channel string + GOARCH string + GOOS string + GOARM string + GOMIPS string + + // ConfName is the name of the current configuration file. Typically, + // "AdGuardHome.yaml". + ConfName string + // WorkDir is the working directory that is used for temporary files. + WorkDir string } -// NewUpdater - creates a new instance of the Updater -func NewUpdater(cfg Config) *Updater { +// NewUpdater creates a new Updater. +func NewUpdater(conf *Config) *Updater { + u := &url.URL{ + Scheme: "https", + Host: "static.adguard.com", + Path: path.Join("adguardhome", conf.Channel, "version.json"), + } return &Updater{ - Config: cfg, + client: conf.Client, + + version: conf.Version, + channel: conf.Channel, + goarch: conf.GOARCH, + goos: conf.GOOS, + goarm: conf.GOARM, + gomips: conf.GOMIPS, + + confName: conf.ConfName, + workDir: conf.WorkDir, + versionCheckURL: u.String(), + + mu: &sync.RWMutex{}, } } -// DoUpdate - conducts the auto-update -// 1. Downloads the update file -// 2. Unpacks it and checks the contents -// 3. Backups the current version and configuration -// 4. Replaces the old files -func (u *Updater) DoUpdate() error { +// Update performs the auto-update. +func (u *Updater) Update() error { + u.mu.Lock() + defer u.mu.Unlock() + err := u.prepare() if err != nil { return err @@ -72,7 +116,7 @@ func (u *Updater) DoUpdate() error { defer u.clean() - err = u.downloadPackageFile(u.PackageURL, u.packageName) + err = u.downloadPackageFile(u.packageURL, u.packageName) if err != nil { return err } @@ -84,7 +128,6 @@ func (u *Updater) DoUpdate() error { err = u.check() if err != nil { - u.clean() return err } @@ -101,40 +144,57 @@ func (u *Updater) DoUpdate() error { return nil } -func (u *Updater) prepare() error { - u.updateDir = filepath.Join(u.WorkDir, fmt.Sprintf("agh-update-%s", u.NewVersion)) +// NewVersion returns the available new version. +func (u *Updater) NewVersion() (nv string) { + u.mu.RLock() + defer u.mu.RUnlock() - _, pkgNameOnly := filepath.Split(u.PackageURL) - if len(pkgNameOnly) == 0 { + return u.newVersion +} + +// VersionCheckURL returns the version check URL. +func (u *Updater) VersionCheckURL() (vcu string) { + u.mu.RLock() + defer u.mu.RUnlock() + + return u.versionCheckURL +} + +func (u *Updater) prepare() error { + u.updateDir = filepath.Join(u.workDir, fmt.Sprintf("agh-update-%s", u.newVersion)) + + _, pkgNameOnly := filepath.Split(u.packageURL) + if pkgNameOnly == "" { return fmt.Errorf("invalid PackageURL") } + u.packageName = filepath.Join(u.updateDir, pkgNameOnly) - u.backupDir = filepath.Join(u.WorkDir, "agh-backup") + u.backupDir = filepath.Join(u.workDir, "agh-backup") exeName := "AdGuardHome" - if u.OS == "windows" { + if u.goos == "windows" { exeName = "AdGuardHome.exe" } u.backupExeName = filepath.Join(u.backupDir, exeName) u.updateExeName = filepath.Join(u.updateDir, exeName) - log.Info("Updating from %s to %s. URL:%s", - u.VersionString, u.NewVersion, u.PackageURL) + log.Info("Updating from %s to %s. URL:%s", version.Version(), u.newVersion, u.packageURL) - // If the binary file isn't found in working directory, we won't be able to auto-update - // Getting the full path to the current binary file on UNIX and checking write permissions - // is more difficult. - u.currentExeName = filepath.Join(u.WorkDir, exeName) + // If the binary file isn't found in working directory, we won't be able + // to auto-update. Getting the full path to the current binary file on + // Unix and checking write permissions is more difficult. + u.currentExeName = filepath.Join(u.workDir, exeName) if !util.FileExists(u.currentExeName) { return fmt.Errorf("executable file %s doesn't exist", u.currentExeName) } + return nil } func (u *Updater) unpack() error { var err error - _, pkgNameOnly := filepath.Split(u.PackageURL) + _, pkgNameOnly := filepath.Split(u.packageURL) log.Debug("updater: unpacking the package") if strings.HasSuffix(pkgNameOnly, ".zip") { @@ -158,7 +218,7 @@ func (u *Updater) unpack() error { func (u *Updater) check() error { log.Debug("updater: checking configuration") - err := copyFile(u.ConfigName, filepath.Join(u.updateDir, "AdGuardHome.yaml")) + err := copyFile(u.confName, filepath.Join(u.updateDir, "AdGuardHome.yaml")) if err != nil { return fmt.Errorf("copyFile() failed: %w", err) } @@ -173,27 +233,25 @@ func (u *Updater) check() error { func (u *Updater) backup() error { log.Debug("updater: backing up the current configuration") _ = os.Mkdir(u.backupDir, 0o755) - err := copyFile(u.ConfigName, filepath.Join(u.backupDir, "AdGuardHome.yaml")) + err := copyFile(u.confName, filepath.Join(u.backupDir, "AdGuardHome.yaml")) if err != nil { return fmt.Errorf("copyFile() failed: %w", err) } - // workdir/README.md -> backup/README.md - err = copySupportingFiles(u.unpackedFiles, u.WorkDir, u.backupDir) + wd := u.workDir + err = copySupportingFiles(u.unpackedFiles, wd, u.backupDir) if err != nil { return fmt.Errorf("copySupportingFiles(%s, %s) failed: %s", - u.WorkDir, u.backupDir, err) + wd, u.backupDir, err) } return nil } func (u *Updater) replace() error { - // update/README.md -> workdir/README.md - err := copySupportingFiles(u.unpackedFiles, u.updateDir, u.WorkDir) + err := copySupportingFiles(u.unpackedFiles, u.updateDir, u.workDir) if err != nil { - return fmt.Errorf("copySupportingFiles(%s, %s) failed: %s", - u.updateDir, u.WorkDir, err) + return fmt.Errorf("copySupportingFiles(%s, %s) failed: %s", u.updateDir, u.workDir, err) } log.Debug("updater: renaming: %s -> %s", u.currentExeName, u.backupExeName) @@ -202,7 +260,7 @@ func (u *Updater) replace() error { return err } - if u.OS == "windows" { + if u.goos == "windows" { // rename fails with "File in use" error err = copyFile(u.updateExeName, u.currentExeName) } else { @@ -211,7 +269,9 @@ func (u *Updater) replace() error { if err != nil { return err } + log.Debug("updater: renamed: %s -> %s", u.updateExeName, u.currentExeName) + return nil } @@ -226,7 +286,7 @@ const MaxPackageFileSize = 32 * 1024 * 1024 // Download package file and save it to disk func (u *Updater) downloadPackageFile(url, filename string) error { - resp, err := u.Client.Get(url) + resp, err := u.client.Get(url) if err != nil { return fmt.Errorf("http request failed: %w", err) } @@ -288,7 +348,7 @@ func tarGzFileUnpack(tarfile, outdir string) ([]string, error) { } _, inputNameOnly := filepath.Split(header.Name) - if len(inputNameOnly) == 0 { + if inputNameOnly == "" { continue } @@ -355,7 +415,7 @@ func zipFileUnpack(zipfile, outdir string) ([]string, error) { fi := zf.FileInfo() inputNameOnly := fi.Name() - if len(inputNameOnly) == 0 { + if inputNameOnly == "" { continue } diff --git a/internal/updater/updater_test.go b/internal/updater/updater_test.go new file mode 100644 index 00000000..abadc17b --- /dev/null +++ b/internal/updater/updater_test.go @@ -0,0 +1,316 @@ +package updater + +import ( + "io/ioutil" + "net" + "net/http" + "net/url" + "os" + "path" + "path/filepath" + "strconv" + "testing" + + "github.com/AdguardTeam/AdGuardHome/internal/testutil" + "github.com/stretchr/testify/assert" +) + +// TODO(a.garipov): Rewrite these tests. + +func TestMain(m *testing.M) { + testutil.DiscardLogOutput(m) +} + +func startHTTPServer(data string) (l net.Listener, portStr string) { + mux := http.NewServeMux() + mux.HandleFunc("/", func(w http.ResponseWriter, _ *http.Request) { + _, _ = w.Write([]byte(data)) + }) + + listener, err := net.Listen("tcp", ":0") + if err != nil { + panic(err) + } + + go func() { _ = http.Serve(listener, mux) }() + return listener, strconv.FormatUint(uint64(listener.Addr().(*net.TCPAddr).Port), 10) +} + +func TestUpdateGetVersion(t *testing.T) { + const jsonData = `{ + "version": "v0.103.0-beta.2", + "announcement": "AdGuard Home v0.103.0-beta.2 is now available!", + "announcement_url": "https://github.com/AdguardTeam/AdGuardHome/internal/releases", + "selfupdate_min_version": "v0.0", + "download_windows_amd64": "https://static.adguard.com/adguardhome/beta/AdGuardHome_windows_amd64.zip", + "download_windows_386": "https://static.adguard.com/adguardhome/beta/AdGuardHome_windows_386.zip", + "download_darwin_amd64": "https://static.adguard.com/adguardhome/beta/AdGuardHome_darwin_amd64.zip", + "download_darwin_386": "https://static.adguard.com/adguardhome/beta/AdGuardHome_darwin_386.zip", + "download_linux_amd64": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_amd64.tar.gz", + "download_linux_386": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_386.tar.gz", + "download_linux_arm": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv6.tar.gz", + "download_linux_armv5": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv5.tar.gz", + "download_linux_armv6": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv6.tar.gz", + "download_linux_armv7": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv7.tar.gz", + "download_linux_arm64": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_arm64.tar.gz", + "download_linux_mips": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mips_softfloat.tar.gz", + "download_linux_mipsle": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mipsle_softfloat.tar.gz", + "download_linux_mips64": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mips64_softfloat.tar.gz", + "download_linux_mips64le": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mips64le_softfloat.tar.gz", + "download_freebsd_386": "https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_386.tar.gz", + "download_freebsd_amd64": "https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_amd64.tar.gz", + "download_freebsd_arm": "https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_armv6.tar.gz", + "download_freebsd_armv5": "https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_armv5.tar.gz", + "download_freebsd_armv6": "https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_armv6.tar.gz", + "download_freebsd_armv7": "https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_armv7.tar.gz", + "download_freebsd_arm64": "https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_arm64.tar.gz" +}` + + l, lport := startHTTPServer(jsonData) + t.Cleanup(func() { assert.Nil(t, l.Close()) }) + + u := NewUpdater(&Config{ + Client: &http.Client{}, + Version: "v0.103.0-beta.1", + Channel: "beta", + GOARCH: "arm", + GOOS: "linux", + }) + + fakeURL := &url.URL{ + Scheme: "http", + Host: net.JoinHostPort("127.0.0.1", lport), + Path: path.Join("adguardhome", "beta", "version.json"), + } + u.versionCheckURL = fakeURL.String() + + info, err := u.VersionInfo(false) + assert.Nil(t, err) + assert.Equal(t, "v0.103.0-beta.2", info.NewVersion) + assert.Equal(t, "AdGuard Home v0.103.0-beta.2 is now available!", info.Announcement) + assert.Equal(t, "https://github.com/AdguardTeam/AdGuardHome/internal/releases", info.AnnouncementURL) + assert.Equal(t, "v0.0", info.SelfUpdateMinVersion) + assert.True(t, info.CanAutoUpdate) + + // check cached + _, err = u.VersionInfo(false) + assert.Nil(t, err) +} + +func TestUpdate(t *testing.T) { + // TODO(a.garipov): Uncomment and remove the code below in Go 1.15. + // + // wd := t.TempDir() + wd, err := ioutil.TempDir("", "aghtest") + assert.Nil(t, err) + t.Cleanup(func() { assert.Nil(t, os.RemoveAll(wd)) }) + + assert.Nil(t, ioutil.WriteFile(filepath.Join(wd, "AdGuardHome"), []byte("AdGuardHome"), 0o755)) + assert.Nil(t, ioutil.WriteFile(filepath.Join(wd, "README.md"), []byte("README.md"), 0o644)) + assert.Nil(t, ioutil.WriteFile(filepath.Join(wd, "LICENSE.txt"), []byte("LICENSE.txt"), 0o644)) + assert.Nil(t, ioutil.WriteFile(filepath.Join(wd, "AdGuardHome.yaml"), []byte("AdGuardHome.yaml"), 0o644)) + + // start server for returning package file + pkgData, err := ioutil.ReadFile("testdata/AdGuardHome.tar.gz") + assert.Nil(t, err) + l, lport := startHTTPServer(string(pkgData)) + t.Cleanup(func() { assert.Nil(t, l.Close()) }) + + u := NewUpdater(&Config{ + Client: &http.Client{}, + Version: "v0.103.0", + }) + + fakeURL := &url.URL{ + Scheme: "http", + Host: net.JoinHostPort("127.0.0.1", lport), + Path: "AdGuardHome.tar.gz", + } + + u.workDir = wd + u.confName = filepath.Join(u.workDir, "AdGuardHome.yaml") + u.newVersion = "v0.103.1" + u.packageURL = fakeURL.String() + + assert.Nil(t, u.prepare()) + u.currentExeName = filepath.Join(wd, "AdGuardHome") + assert.Nil(t, u.downloadPackageFile(u.packageURL, u.packageName)) + assert.Nil(t, u.unpack()) + // assert.Nil(t, u.check()) + assert.Nil(t, u.backup()) + assert.Nil(t, u.replace()) + u.clean() + + // check backup files + d, err := ioutil.ReadFile(filepath.Join(wd, "agh-backup", "AdGuardHome.yaml")) + assert.Nil(t, err) + assert.Equal(t, "AdGuardHome.yaml", string(d)) + + d, err = ioutil.ReadFile(filepath.Join(wd, "agh-backup", "AdGuardHome")) + assert.Nil(t, err) + assert.Equal(t, "AdGuardHome", string(d)) + + // check updated files + d, err = ioutil.ReadFile(filepath.Join(wd, "AdGuardHome")) + assert.Nil(t, err) + assert.Equal(t, "1", string(d)) + + d, err = ioutil.ReadFile(filepath.Join(wd, "README.md")) + assert.Nil(t, err) + assert.Equal(t, "2", string(d)) + + d, err = ioutil.ReadFile(filepath.Join(wd, "LICENSE.txt")) + assert.Nil(t, err) + assert.Equal(t, "3", string(d)) + + d, err = ioutil.ReadFile(filepath.Join(wd, "AdGuardHome.yaml")) + assert.Nil(t, err) + assert.Equal(t, "AdGuardHome.yaml", string(d)) +} + +func TestUpdateWindows(t *testing.T) { + // TODO(a.garipov): Uncomment and remove the code below in Go 1.15. + // + // wd := t.TempDir() + wd, err := ioutil.TempDir("", "aghtest") + assert.Nil(t, err) + t.Cleanup(func() { assert.Nil(t, os.RemoveAll(wd)) }) + + assert.Nil(t, ioutil.WriteFile(filepath.Join(wd, "AdGuardHome.exe"), []byte("AdGuardHome.exe"), 0o755)) + assert.Nil(t, ioutil.WriteFile(filepath.Join(wd, "README.md"), []byte("README.md"), 0o644)) + assert.Nil(t, ioutil.WriteFile(filepath.Join(wd, "LICENSE.txt"), []byte("LICENSE.txt"), 0o644)) + assert.Nil(t, ioutil.WriteFile(filepath.Join(wd, "AdGuardHome.yaml"), []byte("AdGuardHome.yaml"), 0o644)) + + // start server for returning package file + pkgData, err := ioutil.ReadFile("testdata/AdGuardHome.zip") + assert.Nil(t, err) + + l, lport := startHTTPServer(string(pkgData)) + t.Cleanup(func() { assert.Nil(t, l.Close()) }) + + u := NewUpdater(&Config{ + Client: &http.Client{}, + GOOS: "windows", + Version: "v0.103.0", + }) + + fakeURL := &url.URL{ + Scheme: "http", + Host: net.JoinHostPort("127.0.0.1", lport), + Path: "AdGuardHome.zip", + } + + u.workDir = wd + u.confName = filepath.Join(u.workDir, "AdGuardHome.yaml") + u.newVersion = "v0.103.1" + u.packageURL = fakeURL.String() + + assert.Nil(t, u.prepare()) + u.currentExeName = filepath.Join(wd, "AdGuardHome.exe") + assert.Nil(t, u.downloadPackageFile(u.packageURL, u.packageName)) + assert.Nil(t, u.unpack()) + // assert.Nil(t, u.check()) + assert.Nil(t, u.backup()) + assert.Nil(t, u.replace()) + u.clean() + + // check backup files + d, err := ioutil.ReadFile(filepath.Join(wd, "agh-backup", "AdGuardHome.yaml")) + assert.Nil(t, err) + assert.Equal(t, "AdGuardHome.yaml", string(d)) + + d, err = ioutil.ReadFile(filepath.Join(wd, "agh-backup", "AdGuardHome.exe")) + assert.Nil(t, err) + assert.Equal(t, "AdGuardHome.exe", string(d)) + + // check updated files + d, err = ioutil.ReadFile(filepath.Join(wd, "AdGuardHome.exe")) + assert.Nil(t, err) + assert.Equal(t, "1", string(d)) + + d, err = ioutil.ReadFile(filepath.Join(wd, "README.md")) + assert.Nil(t, err) + assert.Equal(t, "2", string(d)) + + d, err = ioutil.ReadFile(filepath.Join(wd, "LICENSE.txt")) + assert.Nil(t, err) + assert.Equal(t, "3", string(d)) + + d, err = ioutil.ReadFile(filepath.Join(wd, "AdGuardHome.yaml")) + assert.Nil(t, err) + assert.Equal(t, "AdGuardHome.yaml", string(d)) +} + +func TestUpdater_VersionInto_ARM(t *testing.T) { + const jsonData = `{ + "version": "v0.103.0-beta.2", + "announcement": "AdGuard Home v0.103.0-beta.2 is now available!", + "announcement_url": "https://github.com/AdguardTeam/AdGuardHome/internal/releases", + "selfupdate_min_version": "v0.0", + "download_linux_armv7": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv7.tar.gz" +}` + + l, lport := startHTTPServer(jsonData) + t.Cleanup(func() { assert.Nil(t, l.Close()) }) + + u := NewUpdater(&Config{ + Client: &http.Client{}, + Version: "v0.103.0-beta.1", + Channel: "beta", + GOARCH: "arm", + GOOS: "linux", + GOARM: "7", + }) + + fakeURL := &url.URL{ + Scheme: "http", + Host: net.JoinHostPort("127.0.0.1", lport), + Path: path.Join("adguardhome", "beta", "version.json"), + } + u.versionCheckURL = fakeURL.String() + + info, err := u.VersionInfo(false) + assert.Nil(t, err) + assert.Equal(t, "v0.103.0-beta.2", info.NewVersion) + assert.Equal(t, "AdGuard Home v0.103.0-beta.2 is now available!", info.Announcement) + assert.Equal(t, "https://github.com/AdguardTeam/AdGuardHome/internal/releases", info.AnnouncementURL) + assert.Equal(t, "v0.0", info.SelfUpdateMinVersion) + assert.True(t, info.CanAutoUpdate) +} + +func TestUpdater_VersionInto_MIPS(t *testing.T) { + const jsonData = `{ + "version": "v0.103.0-beta.2", + "announcement": "AdGuard Home v0.103.0-beta.2 is now available!", + "announcement_url": "https://github.com/AdguardTeam/AdGuardHome/internal/releases", + "selfupdate_min_version": "v0.0", + "download_linux_mips_softfloat": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mips_softfloat.tar.gz" +}` + + l, lport := startHTTPServer(jsonData) + t.Cleanup(func() { assert.Nil(t, l.Close()) }) + + u := NewUpdater(&Config{ + Client: &http.Client{}, + Version: "v0.103.0-beta.1", + Channel: "beta", + GOARCH: "mips", + GOOS: "linux", + GOMIPS: "softfloat", + }) + + fakeURL := &url.URL{ + Scheme: "http", + Host: net.JoinHostPort("127.0.0.1", lport), + Path: path.Join("adguardhome", "beta", "version.json"), + } + u.versionCheckURL = fakeURL.String() + + info, err := u.VersionInfo(false) + assert.Nil(t, err) + assert.Equal(t, "v0.103.0-beta.2", info.NewVersion) + assert.Equal(t, "AdGuard Home v0.103.0-beta.2 is now available!", info.Announcement) + assert.Equal(t, "https://github.com/AdguardTeam/AdGuardHome/internal/releases", info.AnnouncementURL) + assert.Equal(t, "v0.0", info.SelfUpdateMinVersion) + assert.True(t, info.CanAutoUpdate) +} diff --git a/internal/util/helpers.go b/internal/util/helpers.go index 2770fa44..b575b269 100644 --- a/internal/util/helpers.go +++ b/internal/util/helpers.go @@ -26,7 +26,7 @@ func ContainsString(strs []string, str string) bool { // FileExists returns true if file exists. func FileExists(fn string) bool { _, err := os.Stat(fn) - return err == nil + return err == nil || !os.IsNotExist(err) } // RunCommand runs shell command. diff --git a/internal/version/version.go b/internal/version/version.go new file mode 100644 index 00000000..25fff528 --- /dev/null +++ b/internal/version/version.go @@ -0,0 +1,53 @@ +// Package version contains AdGuard Home version information. +package version + +import ( + "fmt" + "runtime" +) + +// These are set by the linker. Unfortunately we cannot set constants during +// linking, and Go doesn't have a concept of immutable variables, so to be +// thorough we have to only export them through getters. +// +// TODO(a.garipov): Find out if we can get GOARM and GOMIPS values the same way +// we can GOARCH and GOOS. +var ( + channel string + goarm string + gomips string + version string +) + +// Channel returns the current AdGuard Home release channel. +func Channel() (v string) { + return channel +} + +// Full returns the full current version of AdGuard Home. +func Full() (v string) { + msg := "AdGuard Home, version %s, channel %s, arch %s %s" + if goarm != "" { + msg = msg + " v" + goarm + } else if gomips != "" { + msg = msg + " " + gomips + } + + return fmt.Sprintf(msg, version, channel, runtime.GOOS, runtime.GOARCH) +} + +// GOARM returns the GOARM value used to build the current AdGuard Home release. +func GOARM() (v string) { + return goarm +} + +// GOMIPS returns the GOMIPS value used to build the current AdGuard Home +// release. +func GOMIPS() (v string) { + return gomips +} + +// Version returns the AdGuard Home build version. +func Version() (v string) { + return version +} diff --git a/main.go b/main.go index be08b192..cf69ed04 100644 --- a/main.go +++ b/main.go @@ -6,18 +6,6 @@ import ( "github.com/AdguardTeam/AdGuardHome/internal/home" ) -// version is the release version. It is set by the linker. -var version = "undefined" - -// channel is the release channel. It is set by the linker. -var channel = "release" - -// goarm is the GOARM value. It is set by the linker. -var goarm = "" - -// gomips is the GOMIPS value. It is set by the linker. -var gomips = "" - func main() { - home.Main(version, channel, goarm, gomips) + home.Main() } diff --git a/scripts/make/build-release.sh b/scripts/make/build-release.sh index 4be45616..b191ac9b 100644 --- a/scripts/make/build-release.sh +++ b/scripts/make/build-release.sh @@ -319,6 +319,17 @@ echo "{ \"selfupdate_min_version\": \"0.0\", " >> "$version_json" +# Add the old object keys for compatibility with pre-v0.105.0 MIPS that +# did not mention the softfloat variant. +# +# TODO(a.garipov): Remove this around the time we hit v0.107.0. +echo " + \"download_linux_mips\": \"${version_download_url}/AdGuardHome_linux_mips_softfloat.tar.gz\", + \"download_linux_mipsle\": \"${version_download_url}/AdGuardHome_linux_mipsle_softfloat.tar.gz\", + \"download_linux_mips64\": \"${version_download_url}/AdGuardHome_linux_mips64_softfloat.tar.gz\", + \"download_linux_mips64le\": \"${version_download_url}/AdGuardHome_linux_mips64le_softfloat.tar.gz\", +" >> "$version_json" + ( # Use +f here so that ls works and we don't need to use find. set +f diff --git a/scripts/make/go-build.sh b/scripts/make/go-build.sh index a2a4a93a..f9702c35 100644 --- a/scripts/make/go-build.sh +++ b/scripts/make/go-build.sh @@ -54,17 +54,18 @@ esac # TODO(a.garipov): Additional validation? version="$VERSION" -# Set the linker flags accordingly: set the channel and the versio as -# well as goarm and gomips variable values, if the variables are set and -# are not empty. -ldflags="-s -w -X main.version=${version}" -ldflags="${ldflags} -X main.channel=${channel}" +# Set the linker flags accordingly: set the realease channel and the +# current version as well as goarm and gomips variable values, if the +# variables are set and are not empty. +readonly version_pkg='github.com/AdguardTeam/AdGuardHome/internal/version' +ldflags="-s -w -X ${version_pkg}.version=${version}" +ldflags="${ldflags} -X ${version_pkg}.channel=${channel}" if [ "${GOARM:-}" != '' ] then - ldflags="${ldflags} -X main.goarm=${GOARM}" + ldflags="${ldflags} -X ${version_pkg}.goarm=${GOARM}" elif [ "${GOMIPS:-}" != '' ] then - ldflags="${ldflags} -X main.gomips=${GOMIPS}" + ldflags="${ldflags} -X ${version_pkg}.gomips=${GOMIPS}" fi # Allow users to limit the build's parallelism. From e8c1f5c8d3d511340662a406b238267fdb815248 Mon Sep 17 00:00:00 2001 From: Eugene Burkov Date: Wed, 13 Jan 2021 16:56:05 +0300 Subject: [PATCH 044/141] Pull request: 2508 ip conversion vol.1 Merge in DNS/adguard-home from 2508-ip-conversion to master Updates #2508. Squashed commit of the following: commit 3f64709fbc73ef74c11b910997be1e9bc337193c Merge: 5ac7faaaa 0d67aa251 Author: Eugene Burkov Date: Wed Jan 13 16:21:34 2021 +0300 Merge branch 'master' into 2508-ip-conversion commit 5ac7faaaa9dda570fdb872acad5d13d078f46b64 Author: Eugene Burkov Date: Wed Jan 13 12:00:11 2021 +0300 all: replace conditions with appropriate functions in tests commit 9e3fa9a115ed23024c57dd5192d5173477ddbf71 Merge: db992a42a bba74859e Author: Eugene Burkov Date: Wed Jan 13 10:47:10 2021 +0300 Merge branch 'master' into 2508-ip-conversion commit db992a42a2c6f315421e78a6a0492e2bfb3ce89d Author: Eugene Burkov Date: Tue Jan 12 18:55:53 2021 +0300 sysutil: fix linux tests commit f629b15d62349323ce2da05e68dc9cc0b5f6e194 Author: Eugene Burkov Date: Tue Jan 12 18:41:20 2021 +0300 all: improve code quality commit 3bf03a75524040738562298bd1de6db536af130f Author: Eugene Burkov Date: Tue Jan 12 17:33:26 2021 +0300 sysutil: fix linux net.IP conversion commit 5d5b6994916923636e635588631b63b7e7b74e5f Author: Eugene Burkov Date: Tue Jan 12 14:57:26 2021 +0300 dnsforward: remove redundant net.IP <-> string conversion commit 0b955d99b7fad40942f21d1dd8734adb99126195 Author: Eugene Burkov Date: Mon Jan 11 18:04:25 2021 +0300 dhcpd: remove net.IP <-> string conversion --- internal/dhcpd/dhcpd_test.go | 38 ++++---- internal/dhcpd/dhcphttp.go | 120 ++++++++++++------------ internal/dhcpd/helpers.go | 16 ++-- internal/dhcpd/server.go | 8 +- internal/dhcpd/v4.go | 15 ++- internal/dhcpd/v4_test.go | 108 ++++++++++----------- internal/dhcpd/v6_test.go | 48 +++++----- internal/dnsfilter/dnsfilter_test.go | 32 ++++--- internal/dnsfilter/dnsrewrite_test.go | 6 +- internal/dnsfilter/rewrites_test.go | 42 ++++----- internal/dnsfilter/safebrowsing.go | 4 +- internal/dnsfilter/safebrowsing_test.go | 12 +-- internal/dnsforward/access_test.go | 22 ++--- internal/dnsforward/config.go | 11 +-- internal/dnsforward/dns.go | 6 +- internal/dnsforward/dnsforward.go | 4 +- internal/dnsforward/dnsforward_test.go | 60 ++++++------ internal/dnsforward/filter.go | 4 +- internal/dnsforward/http.go | 24 ++--- internal/dnsforward/msg.go | 4 +- internal/dnsforward/stats.go | 2 +- internal/dnsforward/util.go | 51 ++++------ internal/home/auth_test.go | 14 +-- internal/home/clients_test.go | 27 +++--- internal/home/dns.go | 2 +- internal/home/filter_test.go | 7 +- internal/home/home_test.go | 16 ++-- internal/home/mobileconfig_test.go | 8 +- internal/home/rdns_test.go | 4 +- internal/querylog/decode_test.go | 4 +- internal/querylog/qlog_test.go | 26 ++--- internal/querylog/qlogfile_test.go | 30 +++--- internal/querylog/qlogreader_test.go | 4 +- internal/stats/stats_test.go | 30 +++--- internal/sysutil/net.go | 13 +-- internal/sysutil/net_linux.go | 6 +- internal/sysutil/net_linux_test.go | 5 +- internal/util/autohosts_test.go | 4 +- internal/util/helpers_test.go | 7 +- 39 files changed, 409 insertions(+), 435 deletions(-) diff --git a/internal/dhcpd/dhcpd_test.go b/internal/dhcpd/dhcpd_test.go index 58b88bda..1d3aab21 100644 --- a/internal/dhcpd/dhcpd_test.go +++ b/internal/dhcpd/dhcpd_test.go @@ -28,27 +28,27 @@ func TestDB(t *testing.T) { conf := V4ServerConf{ Enabled: true, - RangeStart: "192.168.10.100", - RangeEnd: "192.168.10.200", - GatewayIP: "192.168.10.1", - SubnetMask: "255.255.255.0", + RangeStart: net.IP{192, 168, 10, 100}, + RangeEnd: net.IP{192, 168, 10, 200}, + GatewayIP: net.IP{192, 168, 10, 1}, + SubnetMask: net.IP{255, 255, 255, 0}, notify: testNotify, } s.srv4, err = v4Create(conf) - assert.True(t, err == nil) + assert.Nil(t, err) s.srv6, err = v6Create(V6ServerConf{}) - assert.True(t, err == nil) + assert.Nil(t, err) l := Lease{} - l.IP = net.ParseIP("192.168.10.100").To4() + l.IP = net.IP{192, 168, 10, 100} l.HWAddr, _ = net.ParseMAC("aa:aa:aa:aa:aa:aa") exp1 := time.Now().Add(time.Hour) l.Expiry = exp1 s.srv4.(*v4Server).addLease(&l) l2 := Lease{} - l2.IP = net.ParseIP("192.168.10.101").To4() + l2.IP = net.IP{192, 168, 10, 101} l2.HWAddr, _ = net.ParseMAC("aa:aa:aa:aa:aa:bb") s.srv4.AddStaticLease(l2) @@ -62,7 +62,7 @@ func TestDB(t *testing.T) { assert.Equal(t, "aa:aa:aa:aa:aa:bb", ll[0].HWAddr.String()) assert.Equal(t, "192.168.10.101", ll[0].IP.String()) - assert.Equal(t, int64(leaseExpireStatic), ll[0].Expiry.Unix()) + assert.EqualValues(t, leaseExpireStatic, ll[0].Expiry.Unix()) assert.Equal(t, "aa:aa:aa:aa:aa:aa", ll[1].HWAddr.String()) assert.Equal(t, "192.168.10.100", ll[1].IP.String()) @@ -75,8 +75,8 @@ func TestIsValidSubnetMask(t *testing.T) { assert.True(t, isValidSubnetMask([]byte{255, 255, 255, 0})) assert.True(t, isValidSubnetMask([]byte{255, 255, 254, 0})) assert.True(t, isValidSubnetMask([]byte{255, 255, 252, 0})) - assert.True(t, !isValidSubnetMask([]byte{255, 255, 253, 0})) - assert.True(t, !isValidSubnetMask([]byte{255, 255, 255, 1})) + assert.False(t, isValidSubnetMask([]byte{255, 255, 253, 0})) + assert.False(t, isValidSubnetMask([]byte{255, 255, 255, 1})) } func TestNormalizeLeases(t *testing.T) { @@ -100,7 +100,7 @@ func TestNormalizeLeases(t *testing.T) { leases := normalizeLeases(staticLeases, dynLeases) - assert.True(t, len(leases) == 3) + assert.Len(t, leases, 3) assert.True(t, bytes.Equal(leases[0].HWAddr, []byte{1, 2, 3, 4})) assert.True(t, bytes.Equal(leases[0].IP, []byte{0, 2, 3, 4})) assert.True(t, bytes.Equal(leases[1].HWAddr, []byte{2, 2, 3, 4})) @@ -109,22 +109,22 @@ func TestNormalizeLeases(t *testing.T) { func TestOptions(t *testing.T) { code, val := parseOptionString(" 12 hex abcdef ") - assert.Equal(t, uint8(12), code) + assert.EqualValues(t, 12, code) assert.True(t, bytes.Equal([]byte{0xab, 0xcd, 0xef}, val)) code, _ = parseOptionString(" 12 hex abcdef1 ") - assert.Equal(t, uint8(0), code) + assert.EqualValues(t, 0, code) code, val = parseOptionString("123 ip 1.2.3.4") - assert.Equal(t, uint8(123), code) + assert.EqualValues(t, 123, code) assert.Equal(t, "1.2.3.4", net.IP(string(val)).String()) code, _ = parseOptionString("256 ip 1.1.1.1") - assert.Equal(t, uint8(0), code) + assert.EqualValues(t, 0, code) code, _ = parseOptionString("-1 ip 1.1.1.1") - assert.Equal(t, uint8(0), code) + assert.EqualValues(t, 0, code) code, _ = parseOptionString("12 ip 1.1.1.1x") - assert.Equal(t, uint8(0), code) + assert.EqualValues(t, 0, code) code, _ = parseOptionString("12 x 1.1.1.1") - assert.Equal(t, uint8(0), code) + assert.EqualValues(t, 0, code) } diff --git a/internal/dhcpd/dhcphttp.go b/internal/dhcpd/dhcphttp.go index 06a4cc79..e35322f8 100644 --- a/internal/dhcpd/dhcphttp.go +++ b/internal/dhcpd/dhcphttp.go @@ -42,10 +42,10 @@ func convertLeases(inputLeases []Lease, includeExpires bool) []map[string]string } type v4ServerConfJSON struct { - GatewayIP string `json:"gateway_ip"` - SubnetMask string `json:"subnet_mask"` - RangeStart string `json:"range_start"` - RangeEnd string `json:"range_end"` + GatewayIP net.IP `json:"gateway_ip"` + SubnetMask net.IP `json:"subnet_mask"` + RangeStart net.IP `json:"range_start"` + RangeEnd net.IP `json:"range_end"` LeaseDuration uint32 `json:"lease_duration"` } @@ -61,10 +61,10 @@ func v4ServerConfToJSON(c V4ServerConf) v4ServerConfJSON { func v4JSONToServerConf(j v4ServerConfJSON) V4ServerConf { return V4ServerConf{ - GatewayIP: j.GatewayIP, - SubnetMask: j.SubnetMask, - RangeStart: j.RangeStart, - RangeEnd: j.RangeEnd, + GatewayIP: j.GatewayIP.To4(), + SubnetMask: j.SubnetMask.To4(), + RangeStart: j.RangeStart.To4(), + RangeEnd: j.RangeEnd.To4(), LeaseDuration: j.LeaseDuration, } } @@ -117,7 +117,7 @@ func (s *Server) handleDHCPStatus(w http.ResponseWriter, r *http.Request) { type staticLeaseJSON struct { HWAddr string `json:"mac"` - IP string `json:"ip"` + IP net.IP `json:"ip"` Hostname string `json:"hostname"` } @@ -225,10 +225,10 @@ func (s *Server) handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) { type netInterfaceJSON struct { Name string `json:"name"` - GatewayIP string `json:"gateway_ip"` + GatewayIP net.IP `json:"gateway_ip"` HardwareAddr string `json:"hardware_address"` - Addrs4 []string `json:"ipv4_addresses"` - Addrs6 []string `json:"ipv6_addresses"` + Addrs4 []net.IP `json:"ipv4_addresses"` + Addrs6 []net.IP `json:"ipv6_addresses"` Flags string `json:"flags"` } @@ -277,9 +277,9 @@ func (s *Server) handleDHCPInterfaces(w http.ResponseWriter, r *http.Request) { continue } if ipnet.IP.To4() != nil { - jsonIface.Addrs4 = append(jsonIface.Addrs4, ipnet.IP.String()) + jsonIface.Addrs4 = append(jsonIface.Addrs4, ipnet.IP) } else { - jsonIface.Addrs6 = append(jsonIface.Addrs6, ipnet.IP.String()) + jsonIface.Addrs6 = append(jsonIface.Addrs6, ipnet.IP) } } if len(jsonIface.Addrs4)+len(jsonIface.Addrs6) != 0 { @@ -375,50 +375,46 @@ func (s *Server) handleDHCPAddStaticLease(w http.ResponseWriter, r *http.Request err := json.NewDecoder(r.Body).Decode(&lj) if err != nil { httpError(r, w, http.StatusBadRequest, "json.Decode: %s", err) + return } - ip := net.ParseIP(lj.IP) - if ip != nil && ip.To4() == nil { - mac, err := net.ParseMAC(lj.HWAddr) + if lj.IP == nil { + httpError(r, w, http.StatusBadRequest, "invalid IP") + + return + } + + ip4 := lj.IP.To4() + + mac, err := net.ParseMAC(lj.HWAddr) + lease := Lease{ + HWAddr: mac, + } + + if ip4 == nil { + lease.IP = lj.IP.To16() + if err != nil { httpError(r, w, http.StatusBadRequest, "invalid MAC") - return - } - lease := Lease{ - IP: ip, - HWAddr: mac, + return } err = s.srv6.AddStaticLease(lease) if err != nil { httpError(r, w, http.StatusBadRequest, "%s", err) - return } + return } - ip, _ = parseIPv4(lj.IP) - if ip == nil { - httpError(r, w, http.StatusBadRequest, "invalid IP") - return - } - - mac, err := net.ParseMAC(lj.HWAddr) - if err != nil { - httpError(r, w, http.StatusBadRequest, "invalid MAC") - return - } - - lease := Lease{ - IP: ip, - HWAddr: mac, - Hostname: lj.Hostname, - } + lease.IP = ip4 + lease.Hostname = lj.Hostname err = s.srv4.AddStaticLease(lease) if err != nil { httpError(r, w, http.StatusBadRequest, "%s", err) + return } } @@ -428,46 +424,46 @@ func (s *Server) handleDHCPRemoveStaticLease(w http.ResponseWriter, r *http.Requ err := json.NewDecoder(r.Body).Decode(&lj) if err != nil { httpError(r, w, http.StatusBadRequest, "json.Decode: %s", err) + return } - ip := net.ParseIP(lj.IP) - if ip != nil && ip.To4() == nil { - mac, err := net.ParseMAC(lj.HWAddr) + if lj.IP == nil { + httpError(r, w, http.StatusBadRequest, "invalid IP") + + return + } + + ip4 := lj.IP.To4() + + mac, err := net.ParseMAC(lj.HWAddr) + lease := Lease{ + HWAddr: mac, + } + + if ip4 == nil { + lease.IP = lj.IP.To16() + if err != nil { httpError(r, w, http.StatusBadRequest, "invalid MAC") - return - } - lease := Lease{ - IP: ip, - HWAddr: mac, + return } err = s.srv6.RemoveStaticLease(lease) if err != nil { httpError(r, w, http.StatusBadRequest, "%s", err) - return } + return } - ip, _ = parseIPv4(lj.IP) - if ip == nil { - httpError(r, w, http.StatusBadRequest, "invalid IP") - return - } - - mac, _ := net.ParseMAC(lj.HWAddr) - - lease := Lease{ - IP: ip, - HWAddr: mac, - Hostname: lj.Hostname, - } + lease.IP = ip4 + lease.Hostname = lj.Hostname err = s.srv4.RemoveStaticLease(lease) if err != nil { httpError(r, w, http.StatusBadRequest, "%s", err) + return } } diff --git a/internal/dhcpd/helpers.go b/internal/dhcpd/helpers.go index aafda988..28856b5f 100644 --- a/internal/dhcpd/helpers.go +++ b/internal/dhcpd/helpers.go @@ -14,15 +14,17 @@ func isTimeout(err error) bool { return operr.Timeout() } -func parseIPv4(text string) (net.IP, error) { - result := net.ParseIP(text) - if result == nil { - return nil, fmt.Errorf("%s is not an IP address", text) +func tryTo4(ip net.IP) (ip4 net.IP, err error) { + if ip == nil { + return nil, fmt.Errorf("%v is not an IP address", ip) } - if result.To4() == nil { - return nil, fmt.Errorf("%s is not an IPv4 address", text) + + ip4 = ip.To4() + if ip4 == nil { + return nil, fmt.Errorf("%v is not an IPv4 address", ip) } - return result.To4(), nil + + return ip4, nil } // Return TRUE if subnet mask is correct (e.g. 255.255.255.0) diff --git a/internal/dhcpd/server.go b/internal/dhcpd/server.go index 240715ca..20f6cad3 100644 --- a/internal/dhcpd/server.go +++ b/internal/dhcpd/server.go @@ -36,13 +36,13 @@ type V4ServerConf struct { Enabled bool `yaml:"-"` InterfaceName string `yaml:"-"` - GatewayIP string `yaml:"gateway_ip"` - SubnetMask string `yaml:"subnet_mask"` + GatewayIP net.IP `yaml:"gateway_ip"` + SubnetMask net.IP `yaml:"subnet_mask"` // The first & the last IP address for dynamic leases // Bytes [0..2] of the last allowed IP address must match the first IP - RangeStart string `yaml:"range_start"` - RangeEnd string `yaml:"range_end"` + RangeStart net.IP `yaml:"range_start"` + RangeEnd net.IP `yaml:"range_end"` LeaseDuration uint32 `yaml:"lease_duration"` // in seconds diff --git a/internal/dhcpd/v4.go b/internal/dhcpd/v4.go index 81ba3a1d..038d8f9c 100644 --- a/internal/dhcpd/v4.go +++ b/internal/dhcpd/v4.go @@ -589,7 +589,7 @@ func (s *v4Server) Start() error { s.conf.dnsIPAddrs = dnsIPAddrs laddr := &net.UDPAddr{ - IP: net.ParseIP("0.0.0.0"), + IP: net.IP{0, 0, 0, 0}, Port: dhcpv4.ServerPort, } s.srv, err = server4.NewServer(iface.Name, laddr, s.packetHandler, server4.WithDebugLogger()) @@ -632,19 +632,18 @@ func v4Create(conf V4ServerConf) (DHCPServer, error) { } var err error - s.conf.routerIP, err = parseIPv4(s.conf.GatewayIP) + s.conf.routerIP, err = tryTo4(s.conf.GatewayIP) if err != nil { return s, fmt.Errorf("dhcpv4: %w", err) } - subnet, err := parseIPv4(s.conf.SubnetMask) - if err != nil || !isValidSubnetMask(subnet) { - return s, fmt.Errorf("dhcpv4: invalid subnet mask: %s", s.conf.SubnetMask) + if s.conf.SubnetMask == nil { + return s, fmt.Errorf("dhcpv4: invalid subnet mask: %v", s.conf.SubnetMask) } s.conf.subnetMask = make([]byte, 4) - copy(s.conf.subnetMask, subnet) + copy(s.conf.subnetMask, s.conf.SubnetMask.To4()) - s.conf.ipStart, err = parseIPv4(conf.RangeStart) + s.conf.ipStart, err = tryTo4(conf.RangeStart) if s.conf.ipStart == nil { return s, fmt.Errorf("dhcpv4: %w", err) } @@ -652,7 +651,7 @@ func v4Create(conf V4ServerConf) (DHCPServer, error) { return s, fmt.Errorf("dhcpv4: invalid range start IP") } - s.conf.ipEnd, err = parseIPv4(conf.RangeEnd) + s.conf.ipEnd, err = tryTo4(conf.RangeEnd) if s.conf.ipEnd == nil { return s, fmt.Errorf("dhcpv4: %w", err) } diff --git a/internal/dhcpd/v4_test.go b/internal/dhcpd/v4_test.go index fe3ac2dd..e3086026 100644 --- a/internal/dhcpd/v4_test.go +++ b/internal/dhcpd/v4_test.go @@ -16,119 +16,119 @@ func notify4(flags uint32) { func TestV4StaticLeaseAddRemove(t *testing.T) { conf := V4ServerConf{ Enabled: true, - RangeStart: "192.168.10.100", - RangeEnd: "192.168.10.200", - GatewayIP: "192.168.10.1", - SubnetMask: "255.255.255.0", + RangeStart: net.IP{192, 168, 10, 100}, + RangeEnd: net.IP{192, 168, 10, 200}, + GatewayIP: net.IP{192, 168, 10, 1}, + SubnetMask: net.IP{255, 255, 255, 0}, notify: notify4, } s, err := v4Create(conf) - assert.True(t, err == nil) + assert.Nil(t, err) ls := s.GetLeases(LeasesStatic) - assert.Equal(t, 0, len(ls)) + assert.Empty(t, ls) // add static lease l := Lease{} - l.IP = net.ParseIP("192.168.10.150").To4() + l.IP = net.IP{192, 168, 10, 150} l.HWAddr, _ = net.ParseMAC("aa:aa:aa:aa:aa:aa") - assert.True(t, s.AddStaticLease(l) == nil) + assert.Nil(t, s.AddStaticLease(l)) // try to add the same static lease - fail - assert.True(t, s.AddStaticLease(l) != nil) + assert.NotNil(t, s.AddStaticLease(l)) // check ls = s.GetLeases(LeasesStatic) - assert.Equal(t, 1, len(ls)) + assert.Len(t, ls, 1) assert.Equal(t, "192.168.10.150", ls[0].IP.String()) assert.Equal(t, "aa:aa:aa:aa:aa:aa", ls[0].HWAddr.String()) - assert.True(t, ls[0].Expiry.Unix() == leaseExpireStatic) + assert.EqualValues(t, leaseExpireStatic, ls[0].Expiry.Unix()) // try to remove static lease - fail - l.IP = net.ParseIP("192.168.10.110").To4() + l.IP = net.IP{192, 168, 10, 110} l.HWAddr, _ = net.ParseMAC("aa:aa:aa:aa:aa:aa") - assert.True(t, s.RemoveStaticLease(l) != nil) + assert.NotNil(t, s.RemoveStaticLease(l)) // remove static lease - l.IP = net.ParseIP("192.168.10.150").To4() + l.IP = net.IP{192, 168, 10, 150} l.HWAddr, _ = net.ParseMAC("aa:aa:aa:aa:aa:aa") - assert.True(t, s.RemoveStaticLease(l) == nil) + assert.Nil(t, s.RemoveStaticLease(l)) // check ls = s.GetLeases(LeasesStatic) - assert.Equal(t, 0, len(ls)) + assert.Empty(t, ls) } func TestV4StaticLeaseAddReplaceDynamic(t *testing.T) { conf := V4ServerConf{ Enabled: true, - RangeStart: "192.168.10.100", - RangeEnd: "192.168.10.200", - GatewayIP: "192.168.10.1", - SubnetMask: "255.255.255.0", + RangeStart: net.IP{192, 168, 10, 100}, + RangeEnd: net.IP{192, 168, 10, 200}, + GatewayIP: net.IP{192, 168, 10, 1}, + SubnetMask: net.IP{255, 255, 255, 0}, notify: notify4, } sIface, err := v4Create(conf) s := sIface.(*v4Server) - assert.True(t, err == nil) + assert.Nil(t, err) // add dynamic lease ld := Lease{} - ld.IP = net.ParseIP("192.168.10.150").To4() + ld.IP = net.IP{192, 168, 10, 150} ld.HWAddr, _ = net.ParseMAC("11:aa:aa:aa:aa:aa") s.addLease(&ld) // add dynamic lease { ld := Lease{} - ld.IP = net.ParseIP("192.168.10.151").To4() + ld.IP = net.IP{192, 168, 10, 151} ld.HWAddr, _ = net.ParseMAC("22:aa:aa:aa:aa:aa") s.addLease(&ld) } // add static lease with the same IP l := Lease{} - l.IP = net.ParseIP("192.168.10.150").To4() + l.IP = net.IP{192, 168, 10, 150} l.HWAddr, _ = net.ParseMAC("33:aa:aa:aa:aa:aa") - assert.True(t, s.AddStaticLease(l) == nil) + assert.Nil(t, s.AddStaticLease(l)) // add static lease with the same MAC l = Lease{} - l.IP = net.ParseIP("192.168.10.152").To4() + l.IP = net.IP{192, 168, 10, 152} l.HWAddr, _ = net.ParseMAC("22:aa:aa:aa:aa:aa") - assert.True(t, s.AddStaticLease(l) == nil) + assert.Nil(t, s.AddStaticLease(l)) // check ls := s.GetLeases(LeasesStatic) - assert.Equal(t, 2, len(ls)) + assert.Len(t, ls, 2) assert.Equal(t, "192.168.10.150", ls[0].IP.String()) assert.Equal(t, "33:aa:aa:aa:aa:aa", ls[0].HWAddr.String()) - assert.True(t, ls[0].Expiry.Unix() == leaseExpireStatic) + assert.EqualValues(t, leaseExpireStatic, ls[0].Expiry.Unix()) assert.Equal(t, "192.168.10.152", ls[1].IP.String()) assert.Equal(t, "22:aa:aa:aa:aa:aa", ls[1].HWAddr.String()) - assert.True(t, ls[1].Expiry.Unix() == leaseExpireStatic) + assert.EqualValues(t, leaseExpireStatic, ls[1].Expiry.Unix()) } func TestV4StaticLeaseGet(t *testing.T) { conf := V4ServerConf{ Enabled: true, - RangeStart: "192.168.10.100", - RangeEnd: "192.168.10.200", - GatewayIP: "192.168.10.1", - SubnetMask: "255.255.255.0", + RangeStart: net.IP{192, 168, 10, 100}, + RangeEnd: net.IP{192, 168, 10, 200}, + GatewayIP: net.IP{192, 168, 10, 1}, + SubnetMask: net.IP{255, 255, 255, 0}, notify: notify4, } sIface, err := v4Create(conf) s := sIface.(*v4Server) - assert.True(t, err == nil) - s.conf.dnsIPAddrs = []net.IP{net.ParseIP("192.168.10.1").To4()} + assert.Nil(t, err) + s.conf.dnsIPAddrs = []net.IP{{192, 168, 10, 1}} l := Lease{} - l.IP = net.ParseIP("192.168.10.150").To4() + l.IP = net.IP{192, 168, 10, 150} l.HWAddr, _ = net.ParseMAC("aa:aa:aa:aa:aa:aa") - assert.True(t, s.AddStaticLease(l) == nil) + assert.Nil(t, s.AddStaticLease(l)) // "Discover" mac, _ := net.ParseMAC("aa:aa:aa:aa:aa:aa") @@ -160,12 +160,12 @@ func TestV4StaticLeaseGet(t *testing.T) { assert.Equal(t, s.conf.leaseTime.Seconds(), resp.IPAddressLeaseTime(-1).Seconds()) dnsAddrs := resp.DNS() - assert.Equal(t, 1, len(dnsAddrs)) + assert.Len(t, dnsAddrs, 1) assert.Equal(t, "192.168.10.1", dnsAddrs[0].String()) // check lease ls := s.GetLeases(LeasesStatic) - assert.Equal(t, 1, len(ls)) + assert.Len(t, ls, 1) assert.Equal(t, "192.168.10.150", ls[0].IP.String()) assert.Equal(t, "aa:aa:aa:aa:aa:aa", ls[0].HWAddr.String()) } @@ -173,10 +173,10 @@ func TestV4StaticLeaseGet(t *testing.T) { func TestV4DynamicLeaseGet(t *testing.T) { conf := V4ServerConf{ Enabled: true, - RangeStart: "192.168.10.100", - RangeEnd: "192.168.10.200", - GatewayIP: "192.168.10.1", - SubnetMask: "255.255.255.0", + RangeStart: net.IP{192, 168, 10, 100}, + RangeEnd: net.IP{192, 168, 10, 200}, + GatewayIP: net.IP{192, 168, 10, 1}, + SubnetMask: net.IP{255, 255, 255, 0}, notify: notify4, Options: []string{ "81 hex 303132", @@ -185,8 +185,8 @@ func TestV4DynamicLeaseGet(t *testing.T) { } sIface, err := v4Create(conf) s := sIface.(*v4Server) - assert.True(t, err == nil) - s.conf.dnsIPAddrs = []net.IP{net.ParseIP("192.168.10.1").To4()} + assert.Nil(t, err) + s.conf.dnsIPAddrs = []net.IP{{192, 168, 10, 1}} // "Discover" mac, _ := net.ParseMAC("aa:aa:aa:aa:aa:aa") @@ -220,19 +220,19 @@ func TestV4DynamicLeaseGet(t *testing.T) { assert.Equal(t, s.conf.leaseTime.Seconds(), resp.IPAddressLeaseTime(-1).Seconds()) dnsAddrs := resp.DNS() - assert.Equal(t, 1, len(dnsAddrs)) + assert.Len(t, dnsAddrs, 1) assert.Equal(t, "192.168.10.1", dnsAddrs[0].String()) // check lease ls := s.GetLeases(LeasesDynamic) - assert.Equal(t, 1, len(ls)) + assert.Len(t, ls, 1) assert.Equal(t, "192.168.10.100", ls[0].IP.String()) assert.Equal(t, "aa:aa:aa:aa:aa:aa", ls[0].HWAddr.String()) - start := net.ParseIP("192.168.10.100").To4() - stop := net.ParseIP("192.168.10.200").To4() - assert.True(t, !ip4InRange(start, stop, net.ParseIP("192.168.10.99").To4())) - assert.True(t, !ip4InRange(start, stop, net.ParseIP("192.168.11.100").To4())) - assert.True(t, !ip4InRange(start, stop, net.ParseIP("192.168.11.201").To4())) - assert.True(t, ip4InRange(start, stop, net.ParseIP("192.168.10.100").To4())) + start := net.IP{192, 168, 10, 100} + stop := net.IP{192, 168, 10, 200} + assert.False(t, ip4InRange(start, stop, net.IP{192, 168, 10, 99})) + assert.False(t, ip4InRange(start, stop, net.IP{192, 168, 11, 100})) + assert.False(t, ip4InRange(start, stop, net.IP{192, 168, 11, 201})) + assert.True(t, ip4InRange(start, stop, net.IP{192, 168, 10, 100})) } diff --git a/internal/dhcpd/v6_test.go b/internal/dhcpd/v6_test.go index 7d7dd678..fd3dd89b 100644 --- a/internal/dhcpd/v6_test.go +++ b/internal/dhcpd/v6_test.go @@ -21,40 +21,40 @@ func TestV6StaticLeaseAddRemove(t *testing.T) { notify: notify6, } s, err := v6Create(conf) - assert.True(t, err == nil) + assert.Nil(t, err) ls := s.GetLeases(LeasesStatic) - assert.Equal(t, 0, len(ls)) + assert.Empty(t, ls) // add static lease l := Lease{} l.IP = net.ParseIP("2001::1") l.HWAddr, _ = net.ParseMAC("aa:aa:aa:aa:aa:aa") - assert.True(t, s.AddStaticLease(l) == nil) + assert.Nil(t, s.AddStaticLease(l)) // try to add static lease - fail - assert.True(t, s.AddStaticLease(l) != nil) + assert.NotNil(t, s.AddStaticLease(l)) // check ls = s.GetLeases(LeasesStatic) - assert.Equal(t, 1, len(ls)) + assert.Len(t, ls, 1) assert.Equal(t, "2001::1", ls[0].IP.String()) assert.Equal(t, "aa:aa:aa:aa:aa:aa", ls[0].HWAddr.String()) - assert.True(t, ls[0].Expiry.Unix() == leaseExpireStatic) + assert.EqualValues(t, leaseExpireStatic, ls[0].Expiry.Unix()) // try to remove static lease - fail l.IP = net.ParseIP("2001::2") l.HWAddr, _ = net.ParseMAC("aa:aa:aa:aa:aa:aa") - assert.True(t, s.RemoveStaticLease(l) != nil) + assert.NotNil(t, s.RemoveStaticLease(l)) // remove static lease l.IP = net.ParseIP("2001::1") l.HWAddr, _ = net.ParseMAC("aa:aa:aa:aa:aa:aa") - assert.True(t, s.RemoveStaticLease(l) == nil) + assert.Nil(t, s.RemoveStaticLease(l)) // check ls = s.GetLeases(LeasesStatic) - assert.Equal(t, 0, len(ls)) + assert.Empty(t, ls) } func TestV6StaticLeaseAddReplaceDynamic(t *testing.T) { @@ -65,7 +65,7 @@ func TestV6StaticLeaseAddReplaceDynamic(t *testing.T) { } sIface, err := v6Create(conf) s := sIface.(*v6Server) - assert.True(t, err == nil) + assert.Nil(t, err) // add dynamic lease ld := Lease{} @@ -85,25 +85,25 @@ func TestV6StaticLeaseAddReplaceDynamic(t *testing.T) { l := Lease{} l.IP = net.ParseIP("2001::1") l.HWAddr, _ = net.ParseMAC("33:aa:aa:aa:aa:aa") - assert.True(t, s.AddStaticLease(l) == nil) + assert.Nil(t, s.AddStaticLease(l)) // add static lease with the same MAC l = Lease{} l.IP = net.ParseIP("2001::3") l.HWAddr, _ = net.ParseMAC("22:aa:aa:aa:aa:aa") - assert.True(t, s.AddStaticLease(l) == nil) + assert.Nil(t, s.AddStaticLease(l)) // check ls := s.GetLeases(LeasesStatic) - assert.Equal(t, 2, len(ls)) + assert.Len(t, ls, 2) assert.Equal(t, "2001::1", ls[0].IP.String()) assert.Equal(t, "33:aa:aa:aa:aa:aa", ls[0].HWAddr.String()) - assert.True(t, ls[0].Expiry.Unix() == leaseExpireStatic) + assert.EqualValues(t, leaseExpireStatic, ls[0].Expiry.Unix()) assert.Equal(t, "2001::3", ls[1].IP.String()) assert.Equal(t, "22:aa:aa:aa:aa:aa", ls[1].HWAddr.String()) - assert.True(t, ls[1].Expiry.Unix() == leaseExpireStatic) + assert.EqualValues(t, leaseExpireStatic, ls[1].Expiry.Unix()) } func TestV6GetLease(t *testing.T) { @@ -114,7 +114,7 @@ func TestV6GetLease(t *testing.T) { } sIface, err := v6Create(conf) s := sIface.(*v6Server) - assert.True(t, err == nil) + assert.Nil(t, err) s.conf.dnsIPAddrs = []net.IP{net.ParseIP("2000::1")} s.sid = dhcpv6.Duid{ Type: dhcpv6.DUID_LLT, @@ -125,7 +125,7 @@ func TestV6GetLease(t *testing.T) { l := Lease{} l.IP = net.ParseIP("2001::1") l.HWAddr, _ = net.ParseMAC("aa:aa:aa:aa:aa:aa") - assert.True(t, s.AddStaticLease(l) == nil) + assert.Nil(t, s.AddStaticLease(l)) // "Solicit" mac, _ := net.ParseMAC("aa:aa:aa:aa:aa:aa") @@ -156,12 +156,12 @@ func TestV6GetLease(t *testing.T) { assert.Equal(t, s.conf.leaseTime.Seconds(), oiaAddr.ValidLifetime.Seconds()) dnsAddrs := resp.Options.DNS() - assert.Equal(t, 1, len(dnsAddrs)) + assert.Len(t, dnsAddrs, 1) assert.Equal(t, "2000::1", dnsAddrs[0].String()) // check lease ls := s.GetLeases(LeasesStatic) - assert.Equal(t, 1, len(ls)) + assert.Len(t, ls, 1) assert.Equal(t, "2001::1", ls[0].IP.String()) assert.Equal(t, "aa:aa:aa:aa:aa:aa", ls[0].HWAddr.String()) } @@ -174,7 +174,7 @@ func TestV6GetDynamicLease(t *testing.T) { } sIface, err := v6Create(conf) s := sIface.(*v6Server) - assert.True(t, err == nil) + assert.Nil(t, err) s.conf.dnsIPAddrs = []net.IP{net.ParseIP("2000::1")} s.sid = dhcpv6.Duid{ Type: dhcpv6.DUID_LLT, @@ -209,17 +209,17 @@ func TestV6GetDynamicLease(t *testing.T) { assert.Equal(t, "2001::2", oiaAddr.IPv6Addr.String()) dnsAddrs := resp.Options.DNS() - assert.Equal(t, 1, len(dnsAddrs)) + assert.Len(t, dnsAddrs, 1) assert.Equal(t, "2000::1", dnsAddrs[0].String()) // check lease ls := s.GetLeases(LeasesDynamic) - assert.Equal(t, 1, len(ls)) + assert.Len(t, ls, 1) assert.Equal(t, "2001::2", ls[0].IP.String()) assert.Equal(t, "aa:aa:aa:aa:aa:aa", ls[0].HWAddr.String()) - assert.True(t, !ip6InRange(net.ParseIP("2001::2"), net.ParseIP("2001::1"))) - assert.True(t, !ip6InRange(net.ParseIP("2001::2"), net.ParseIP("2002::2"))) + assert.False(t, ip6InRange(net.ParseIP("2001::2"), net.ParseIP("2001::1"))) + assert.False(t, ip6InRange(net.ParseIP("2001::2"), net.ParseIP("2002::2"))) assert.True(t, ip6InRange(net.ParseIP("2001::2"), net.ParseIP("2001::2"))) assert.True(t, ip6InRange(net.ParseIP("2001::2"), net.ParseIP("2001::3"))) } diff --git a/internal/dnsfilter/dnsfilter_test.go b/internal/dnsfilter/dnsfilter_test.go index 2bae12de..abac1fb9 100644 --- a/internal/dnsfilter/dnsfilter_test.go +++ b/internal/dnsfilter/dnsfilter_test.go @@ -4,7 +4,6 @@ import ( "bytes" "fmt" "net" - "strings" "testing" "github.com/AdguardTeam/AdGuardHome/internal/testutil" @@ -135,7 +134,7 @@ func TestEtcHostsMatching(t *testing.T) { assert.True(t, res.IsFiltered) if assert.Len(t, res.Rules, 1) { assert.Equal(t, "0.0.0.0 block.com", res.Rules[0].Text) - assert.Len(t, res.Rules[0].IP, 0) + assert.Empty(t, res.Rules[0].IP) } // IPv6 @@ -147,7 +146,7 @@ func TestEtcHostsMatching(t *testing.T) { assert.True(t, res.IsFiltered) if assert.Len(t, res.Rules, 1) { assert.Equal(t, "::1 ipv6.com", res.Rules[0].Text) - assert.Len(t, res.Rules[0].IP, 0) + assert.Empty(t, res.Rules[0].IP) } // 2 IPv4 (return only the first one) @@ -180,7 +179,7 @@ func TestSafeBrowsing(t *testing.T) { defer d.Close() d.checkMatch(t, "wmconvirus.narod.ru") - assert.True(t, strings.Contains(logOutput.String(), "SafeBrowsing lookup for wmconvirus.narod.ru")) + assert.Contains(t, logOutput.String(), "SafeBrowsing lookup for wmconvirus.narod.ru") d.checkMatch(t, "test.wmconvirus.narod.ru") d.checkMatchEmpty(t, "yandex.ru") @@ -268,7 +267,7 @@ func TestSafeSearchCacheYandex(t *testing.T) { res, err := d.CheckHost(domain, dns.TypeA, &setts) assert.Nil(t, err) assert.False(t, res.IsFiltered) - assert.Len(t, res.Rules, 0) + assert.Empty(t, res.Rules) d = NewForTest(&Config{SafeSearchEnabled: true}, nil) defer d.Close() @@ -298,7 +297,7 @@ func TestSafeSearchCacheGoogle(t *testing.T) { res, err := d.CheckHost(domain, dns.TypeA, &setts) assert.Nil(t, err) assert.False(t, res.IsFiltered) - assert.Len(t, res.Rules, 0) + assert.Empty(t, res.Rules) d = NewForTest(&Config{SafeSearchEnabled: true}, nil) defer d.Close() @@ -346,7 +345,7 @@ func TestParentalControl(t *testing.T) { d := NewForTest(&Config{ParentalEnabled: true}, nil) defer d.Close() d.checkMatch(t, "pornhub.com") - assert.True(t, strings.Contains(logOutput.String(), "Parental lookup for pornhub.com")) + assert.Contains(t, logOutput.String(), "Parental lookup for pornhub.com") d.checkMatch(t, "www.pornhub.com") d.checkMatchEmpty(t, "www.yandex.ru") d.checkMatchEmpty(t, "yandex.ru") @@ -468,18 +467,20 @@ func TestWhitelist(t *testing.T) { // matched by white filter res, err := d.CheckHost("host1", dns.TypeA, &setts) - assert.True(t, err == nil) - assert.True(t, !res.IsFiltered && res.Reason == NotFilteredAllowList) + assert.Nil(t, err) + assert.False(t, res.IsFiltered) + assert.Equal(t, res.Reason, NotFilteredAllowList) if assert.Len(t, res.Rules, 1) { - assert.True(t, res.Rules[0].Text == "||host1^") + assert.Equal(t, "||host1^", res.Rules[0].Text) } // not matched by white filter, but matched by block filter res, err = d.CheckHost("host2", dns.TypeA, &setts) - assert.True(t, err == nil) - assert.True(t, res.IsFiltered && res.Reason == FilteredBlockList) + assert.Nil(t, err) + assert.True(t, res.IsFiltered) + assert.Equal(t, res.Reason, FilteredBlockList) if assert.Len(t, res.Rules, 1) { - assert.True(t, res.Rules[0].Text == "||host2^") + assert.Equal(t, "||host2^", res.Rules[0].Text) } } @@ -529,7 +530,7 @@ func TestClientSettings(t *testing.T) { // not blocked r, _ = d.CheckHost("facebook.com", dns.TypeA, &setts) - assert.True(t, !r.IsFiltered) + assert.False(t, r.IsFiltered) // override client settings: applyClientSettings(&setts) @@ -554,7 +555,8 @@ func TestClientSettings(t *testing.T) { // blocked by additional rules r, _ = d.CheckHost("facebook.com", dns.TypeA, &setts) - assert.True(t, r.IsFiltered && r.Reason == FilteredBlockedService) + assert.True(t, r.IsFiltered) + assert.Equal(t, r.Reason, FilteredBlockedService) } // BENCHMARKS diff --git a/internal/dnsfilter/dnsrewrite_test.go b/internal/dnsfilter/dnsrewrite_test.go index dadef406..201de44f 100644 --- a/internal/dnsfilter/dnsrewrite_test.go +++ b/internal/dnsfilter/dnsrewrite_test.go @@ -171,7 +171,7 @@ func TestDNSFilter_CheckHostRules_dnsrewrite(t *testing.T) { res, err := f.CheckHostRules(host, dtyp, setts) assert.Nil(t, err) - assert.Equal(t, "", res.CanonName) + assert.Empty(t, res.CanonName) if dnsrr := res.DNSRewriteResult; assert.NotNil(t, dnsrr) { assert.Equal(t, dns.RcodeSuccess, dnsrr.RCode) @@ -197,7 +197,7 @@ func TestDNSFilter_CheckHostRules_dnsrewrite(t *testing.T) { res, err := f.CheckHostRules(host, dtyp, setts) assert.Nil(t, err) - assert.Equal(t, "", res.CanonName) - assert.Len(t, res.Rules, 0) + assert.Empty(t, res.CanonName) + assert.Empty(t, res.Rules) }) } diff --git a/internal/dnsfilter/rewrites_test.go b/internal/dnsfilter/rewrites_test.go index 3a3284ec..a38f3f9d 100644 --- a/internal/dnsfilter/rewrites_test.go +++ b/internal/dnsfilter/rewrites_test.go @@ -27,14 +27,14 @@ func TestRewrites(t *testing.T) { r = d.processRewrites("www.host.com", dns.TypeA) assert.Equal(t, Rewritten, r.Reason) assert.Equal(t, "host.com", r.CanonName) - assert.Equal(t, 2, len(r.IPList)) - assert.True(t, r.IPList[0].Equal(net.ParseIP("1.2.3.4"))) - assert.True(t, r.IPList[1].Equal(net.ParseIP("1.2.3.5"))) + assert.Len(t, r.IPList, 2) + assert.True(t, r.IPList[0].Equal(net.IP{1, 2, 3, 4})) + assert.True(t, r.IPList[1].Equal(net.IP{1, 2, 3, 5})) r = d.processRewrites("www.host.com", dns.TypeAAAA) assert.Equal(t, Rewritten, r.Reason) assert.Equal(t, "host.com", r.CanonName) - assert.Equal(t, 1, len(r.IPList)) + assert.Len(t, r.IPList, 1) assert.True(t, r.IPList[0].Equal(net.ParseIP("1:2:3::4"))) // wildcard @@ -45,11 +45,11 @@ func TestRewrites(t *testing.T) { d.prepareRewrites() r = d.processRewrites("host.com", dns.TypeA) assert.Equal(t, Rewritten, r.Reason) - assert.True(t, r.IPList[0].Equal(net.ParseIP("1.2.3.4"))) + assert.True(t, r.IPList[0].Equal(net.IP{1, 2, 3, 4})) r = d.processRewrites("www.host.com", dns.TypeA) assert.Equal(t, Rewritten, r.Reason) - assert.True(t, r.IPList[0].Equal(net.ParseIP("1.2.3.5"))) + assert.True(t, r.IPList[0].Equal(net.IP{1, 2, 3, 5})) r = d.processRewrites("www.host2.com", dns.TypeA) assert.Equal(t, NotFilteredNotFound, r.Reason) @@ -62,8 +62,8 @@ func TestRewrites(t *testing.T) { d.prepareRewrites() r = d.processRewrites("a.host.com", dns.TypeA) assert.Equal(t, Rewritten, r.Reason) - assert.True(t, len(r.IPList) == 1) - assert.True(t, r.IPList[0].Equal(net.ParseIP("1.2.3.4"))) + assert.Len(t, r.IPList, 1) + assert.True(t, r.IPList[0].Equal(net.IP{1, 2, 3, 4})) // wildcard + CNAME d.Rewrites = []RewriteEntry{ @@ -74,7 +74,7 @@ func TestRewrites(t *testing.T) { r = d.processRewrites("www.host.com", dns.TypeA) assert.Equal(t, Rewritten, r.Reason) assert.Equal(t, "host.com", r.CanonName) - assert.True(t, r.IPList[0].Equal(net.ParseIP("1.2.3.4"))) + assert.True(t, r.IPList[0].Equal(net.IP{1, 2, 3, 4})) // 2 CNAMEs d.Rewrites = []RewriteEntry{ @@ -86,8 +86,8 @@ func TestRewrites(t *testing.T) { r = d.processRewrites("b.host.com", dns.TypeA) assert.Equal(t, Rewritten, r.Reason) assert.Equal(t, "host.com", r.CanonName) - assert.True(t, len(r.IPList) == 1) - assert.True(t, r.IPList[0].Equal(net.ParseIP("1.2.3.4"))) + assert.Len(t, r.IPList, 1) + assert.True(t, r.IPList[0].Equal(net.IP{1, 2, 3, 4})) // 2 CNAMEs + wildcard d.Rewrites = []RewriteEntry{ @@ -99,8 +99,8 @@ func TestRewrites(t *testing.T) { r = d.processRewrites("b.host.com", dns.TypeA) assert.Equal(t, Rewritten, r.Reason) assert.Equal(t, "x.somehost.com", r.CanonName) - assert.True(t, len(r.IPList) == 1) - assert.True(t, r.IPList[0].Equal(net.ParseIP("1.2.3.4"))) + assert.Len(t, r.IPList, 1) + assert.True(t, r.IPList[0].Equal(net.IP{1, 2, 3, 4})) } func TestRewritesLevels(t *testing.T) { @@ -116,19 +116,19 @@ func TestRewritesLevels(t *testing.T) { // match exact r := d.processRewrites("host.com", dns.TypeA) assert.Equal(t, Rewritten, r.Reason) - assert.Equal(t, 1, len(r.IPList)) + assert.Len(t, r.IPList, 1) assert.Equal(t, "1.1.1.1", r.IPList[0].String()) // match L2 r = d.processRewrites("sub.host.com", dns.TypeA) assert.Equal(t, Rewritten, r.Reason) - assert.Equal(t, 1, len(r.IPList)) + assert.Len(t, r.IPList, 1) assert.Equal(t, "2.2.2.2", r.IPList[0].String()) // match L3 r = d.processRewrites("my.sub.host.com", dns.TypeA) assert.Equal(t, Rewritten, r.Reason) - assert.Equal(t, 1, len(r.IPList)) + assert.Len(t, r.IPList, 1) assert.Equal(t, "3.3.3.3", r.IPList[0].String()) } @@ -144,7 +144,7 @@ func TestRewritesExceptionCNAME(t *testing.T) { // match sub-domain r := d.processRewrites("my.host.com", dns.TypeA) assert.Equal(t, Rewritten, r.Reason) - assert.Equal(t, 1, len(r.IPList)) + assert.Len(t, r.IPList, 1) assert.Equal(t, "2.2.2.2", r.IPList[0].String()) // match sub-domain, but handle exception @@ -164,7 +164,7 @@ func TestRewritesExceptionWC(t *testing.T) { // match sub-domain r := d.processRewrites("my.host.com", dns.TypeA) assert.Equal(t, Rewritten, r.Reason) - assert.Equal(t, 1, len(r.IPList)) + assert.Len(t, r.IPList, 1) assert.Equal(t, "2.2.2.2", r.IPList[0].String()) // match sub-domain, but handle exception @@ -187,7 +187,7 @@ func TestRewritesExceptionIP(t *testing.T) { // match domain r := d.processRewrites("host.com", dns.TypeA) assert.Equal(t, Rewritten, r.Reason) - assert.Equal(t, 1, len(r.IPList)) + assert.Len(t, r.IPList, 1) assert.Equal(t, "1.2.3.4", r.IPList[0].String()) // match exception @@ -201,7 +201,7 @@ func TestRewritesExceptionIP(t *testing.T) { // match domain r = d.processRewrites("host2.com", dns.TypeAAAA) assert.Equal(t, Rewritten, r.Reason) - assert.Equal(t, 1, len(r.IPList)) + assert.Len(t, r.IPList, 1) assert.Equal(t, "::1", r.IPList[0].String()) // match exception @@ -211,5 +211,5 @@ func TestRewritesExceptionIP(t *testing.T) { // match domain r = d.processRewrites("host3.com", dns.TypeAAAA) assert.Equal(t, Rewritten, r.Reason) - assert.Equal(t, 0, len(r.IPList)) + assert.Empty(t, r.IPList) } diff --git a/internal/dnsfilter/safebrowsing.go b/internal/dnsfilter/safebrowsing.go index f5aaca9f..8f4c8f30 100644 --- a/internal/dnsfilter/safebrowsing.go +++ b/internal/dnsfilter/safebrowsing.go @@ -37,8 +37,8 @@ func (d *DNSFilter) initSecurityServices() error { opts := upstream.Options{ Timeout: dnsTimeout, ServerIPAddrs: []net.IP{ - net.ParseIP("94.140.14.15"), - net.ParseIP("94.140.15.16"), + {94, 140, 14, 15}, + {94, 140, 15, 16}, net.ParseIP("2a10:50c0::bad1:ff"), net.ParseIP("2a10:50c0::bad2:ff"), }, diff --git a/internal/dnsfilter/safebrowsing_test.go b/internal/dnsfilter/safebrowsing_test.go index 71e59446..060664b5 100644 --- a/internal/dnsfilter/safebrowsing_test.go +++ b/internal/dnsfilter/safebrowsing_test.go @@ -14,7 +14,7 @@ import ( func TestSafeBrowsingHash(t *testing.T) { // test hostnameToHashes() hashes := hostnameToHashes("1.2.3.sub.host.com") - assert.Equal(t, 3, len(hashes)) + assert.Len(t, hashes, 3) _, ok := hashes[sha256.Sum256([]byte("3.sub.host.com"))] assert.True(t, ok) _, ok = hashes[sha256.Sum256([]byte("sub.host.com"))] @@ -31,9 +31,9 @@ func TestSafeBrowsingHash(t *testing.T) { q := c.getQuestion() - assert.True(t, strings.Contains(q, "7a1b.")) - assert.True(t, strings.Contains(q, "af5a.")) - assert.True(t, strings.Contains(q, "eb11.")) + assert.Contains(t, q, "7a1b.") + assert.Contains(t, q, "af5a.") + assert.Contains(t, q, "eb11.") assert.True(t, strings.HasSuffix(q, "sb.dns.adguard.com.")) } @@ -81,7 +81,7 @@ func TestSafeBrowsingCache(t *testing.T) { c.hashToHost[hash] = "sub.host.com" hash = sha256.Sum256([]byte("nonexisting.com")) c.hashToHost[hash] = "nonexisting.com" - assert.Equal(t, 0, c.getCached()) + assert.Empty(t, c.getCached()) hash = sha256.Sum256([]byte("sub.host.com")) _, ok := c.hashToHost[hash] @@ -103,7 +103,7 @@ func TestSafeBrowsingCache(t *testing.T) { c.hashToHost[hash] = "sub.host.com" c.cache.Set(hash[0:2], make([]byte, 32)) - assert.Equal(t, 0, c.getCached()) + assert.Empty(t, c.getCached()) } // testErrUpstream implements upstream.Upstream interface for replacing real diff --git a/internal/dnsforward/access_test.go b/internal/dnsforward/access_test.go index 250b4931..5c225b21 100644 --- a/internal/dnsforward/access_test.go +++ b/internal/dnsforward/access_test.go @@ -8,28 +8,28 @@ import ( func TestIsBlockedIPAllowed(t *testing.T) { a := &accessCtx{} - assert.True(t, a.Init([]string{"1.1.1.1", "2.2.0.0/16"}, nil, nil) == nil) + assert.Nil(t, a.Init([]string{"1.1.1.1", "2.2.0.0/16"}, nil, nil)) disallowed, disallowedRule := a.IsBlockedIP("1.1.1.1") assert.False(t, disallowed) - assert.Equal(t, "", disallowedRule) + assert.Empty(t, disallowedRule) disallowed, disallowedRule = a.IsBlockedIP("1.1.1.2") assert.True(t, disallowed) - assert.Equal(t, "", disallowedRule) + assert.Empty(t, disallowedRule) disallowed, disallowedRule = a.IsBlockedIP("2.2.1.1") assert.False(t, disallowed) - assert.Equal(t, "", disallowedRule) + assert.Empty(t, disallowedRule) disallowed, disallowedRule = a.IsBlockedIP("2.3.1.1") assert.True(t, disallowed) - assert.Equal(t, "", disallowedRule) + assert.Empty(t, disallowedRule) } func TestIsBlockedIPDisallowed(t *testing.T) { a := &accessCtx{} - assert.True(t, a.Init(nil, []string{"1.1.1.1", "2.2.0.0/16"}, nil) == nil) + assert.Nil(t, a.Init(nil, []string{"1.1.1.1", "2.2.0.0/16"}, nil)) disallowed, disallowedRule := a.IsBlockedIP("1.1.1.1") assert.True(t, disallowed) @@ -37,7 +37,7 @@ func TestIsBlockedIPDisallowed(t *testing.T) { disallowed, disallowedRule = a.IsBlockedIP("1.1.1.2") assert.False(t, disallowed) - assert.Equal(t, "", disallowedRule) + assert.Empty(t, disallowedRule) disallowed, disallowedRule = a.IsBlockedIP("2.2.1.1") assert.True(t, disallowed) @@ -45,7 +45,7 @@ func TestIsBlockedIPDisallowed(t *testing.T) { disallowed, disallowedRule = a.IsBlockedIP("2.3.1.1") assert.False(t, disallowed) - assert.Equal(t, "", disallowedRule) + assert.Empty(t, disallowedRule) } func TestIsBlockedIPBlockedDomain(t *testing.T) { @@ -60,13 +60,13 @@ func TestIsBlockedIPBlockedDomain(t *testing.T) { // match by "host2.com" assert.True(t, a.IsBlockedDomain("host1")) assert.True(t, a.IsBlockedDomain("host2")) - assert.True(t, !a.IsBlockedDomain("host3")) + assert.False(t, a.IsBlockedDomain("host3")) // match by wildcard "*.host.com" - assert.True(t, !a.IsBlockedDomain("host.com")) + assert.False(t, a.IsBlockedDomain("host.com")) assert.True(t, a.IsBlockedDomain("asdf.host.com")) assert.True(t, a.IsBlockedDomain("qwer.asdf.host.com")) - assert.True(t, !a.IsBlockedDomain("asdf.zhost.com")) + assert.False(t, a.IsBlockedDomain("asdf.zhost.com")) // match by wildcard "||host3.com^" assert.True(t, a.IsBlockedDomain("host3.com")) diff --git a/internal/dnsforward/config.go b/internal/dnsforward/config.go index 881174d1..0ff078f7 100644 --- a/internal/dnsforward/config.go +++ b/internal/dnsforward/config.go @@ -29,17 +29,16 @@ type FilteringConfig struct { // GetCustomUpstreamByClient - a callback function that returns upstreams configuration // based on the client IP address. Returns nil if there are no custom upstreams for the client + // TODO(e.burkov): replace argument type with net.IP. GetCustomUpstreamByClient func(clientAddr string) *proxy.UpstreamConfig `yaml:"-"` // Protection configuration // -- - ProtectionEnabled bool `yaml:"protection_enabled"` // whether or not use any of dnsfilter features - BlockingMode string `yaml:"blocking_mode"` // mode how to answer filtered requests - BlockingIPv4 string `yaml:"blocking_ipv4"` // IP address to be returned for a blocked A request - BlockingIPv6 string `yaml:"blocking_ipv6"` // IP address to be returned for a blocked AAAA request - BlockingIPAddrv4 net.IP `yaml:"-"` - BlockingIPAddrv6 net.IP `yaml:"-"` + ProtectionEnabled bool `yaml:"protection_enabled"` // whether or not use any of dnsfilter features + BlockingMode string `yaml:"blocking_mode"` // mode how to answer filtered requests + BlockingIPv4 net.IP `yaml:"blocking_ipv4"` // IP address to be returned for a blocked A request + BlockingIPv6 net.IP `yaml:"blocking_ipv6"` // IP address to be returned for a blocked AAAA request BlockedResponseTTL uint32 `yaml:"blocked_response_ttl"` // if 0, then default is used (3600) // IP (or domain name) which is used to respond to DNS requests blocked by parental control or safe-browsing diff --git a/internal/dnsforward/dns.go b/internal/dnsforward/dns.go index 4a47cdc1..10a965e2 100644 --- a/internal/dnsforward/dns.go +++ b/internal/dnsforward/dns.go @@ -182,7 +182,7 @@ func processInternalHosts(ctx *dnsContext) int { return resultDone } - log.Debug("DNS: internal record: %s -> %s", req.Question[0].Name, ip.String()) + log.Debug("DNS: internal record: %s -> %s", req.Question[0].Name, ip) resp := s.makeResponse(req) @@ -278,7 +278,7 @@ func processFilteringBeforeRequest(ctx *dnsContext) int { return resultDone } -// Pass request to upstream servers; process the response +// processUpstream passes request to upstream servers and handles the response. func processUpstream(ctx *dnsContext) int { s := ctx.srv d := ctx.proxyCtx @@ -287,7 +287,7 @@ func processUpstream(ctx *dnsContext) int { } if d.Addr != nil && s.conf.GetCustomUpstreamByClient != nil { - clientIP := ipFromAddr(d.Addr) + clientIP := IPStringFromAddr(d.Addr) upstreamsConf := s.conf.GetCustomUpstreamByClient(clientIP) if upstreamsConf != nil { log.Debug("Using custom upstreams for %s", clientIP) diff --git a/internal/dnsforward/dnsforward.go b/internal/dnsforward/dnsforward.go index f1b7e7d2..ab6bea27 100644 --- a/internal/dnsforward/dnsforward.go +++ b/internal/dnsforward/dnsforward.go @@ -178,9 +178,7 @@ func (s *Server) Prepare(config *ServerConfig) error { if config != nil { s.conf = *config if s.conf.BlockingMode == "custom_ip" { - s.conf.BlockingIPAddrv4 = net.ParseIP(s.conf.BlockingIPv4) - s.conf.BlockingIPAddrv6 = net.ParseIP(s.conf.BlockingIPv6) - if s.conf.BlockingIPAddrv4 == nil || s.conf.BlockingIPAddrv6 == nil { + if s.conf.BlockingIPv4 == nil || s.conf.BlockingIPv6 == nil { return fmt.Errorf("dns: invalid custom blocking IP address specified") } } diff --git a/internal/dnsforward/dnsforward_test.go b/internal/dnsforward/dnsforward_test.go index fdee8648..ab9bb03c 100644 --- a/internal/dnsforward/dnsforward_test.go +++ b/internal/dnsforward/dnsforward_test.go @@ -286,7 +286,7 @@ func TestBlockedRequest(t *testing.T) { t.Fatalf("Couldn't talk to server %s: %s", addr, err) } assert.Equal(t, dns.RcodeSuccess, reply.Rcode) - assert.True(t, reply.Answer[0].(*dns.A).A.Equal(net.ParseIP("0.0.0.0"))) + assert.True(t, reply.Answer[0].(*dns.A).A.Equal(net.IP{0, 0, 0, 0})) err = s.Stop() if err != nil { @@ -300,7 +300,7 @@ func TestServerCustomClientUpstream(t *testing.T) { uc := &proxy.UpstreamConfig{} u := &testUpstream{} u.ipv4 = map[string][]net.IP{} - u.ipv4["host."] = []net.IP{net.ParseIP("192.168.0.1")} + u.ipv4["host."] = []net.IP{{192, 168, 0, 1}} uc.Upstreams = append(uc.Upstreams, u) return uc } @@ -425,7 +425,7 @@ func TestBlockCNAMEProtectionEnabled(t *testing.T) { testUpstm := &testUpstream{testCNAMEs, testIPv4, nil} s.conf.ProtectionEnabled = false err := s.startWithUpstream(testUpstm) - assert.True(t, err == nil) + assert.Nil(t, err) addr := s.dnsProxy.Addr(proxy.ProtoUDP) // 'badhost' has a canonical name 'null.example.org' which is blocked by filters: @@ -440,16 +440,16 @@ func TestBlockCNAME(t *testing.T) { s := createTestServer(t) testUpstm := &testUpstream{testCNAMEs, testIPv4, nil} err := s.startWithUpstream(testUpstm) - assert.True(t, err == nil) + assert.Nil(t, err) addr := s.dnsProxy.Addr(proxy.ProtoUDP) // 'badhost' has a canonical name 'null.example.org' which is blocked by filters: // response is blocked req := createTestMessage("badhost.") reply, err := dns.Exchange(req, addr.String()) - assert.Nil(t, err, nil) + assert.Nil(t, err) assert.Equal(t, dns.RcodeSuccess, reply.Rcode) - assert.True(t, reply.Answer[0].(*dns.A).A.Equal(net.ParseIP("0.0.0.0"))) + assert.True(t, reply.Answer[0].(*dns.A).A.Equal(net.IP{0, 0, 0, 0})) // 'whitelist.example.org' has a canonical name 'null.example.org' which is blocked by filters // but 'whitelist.example.org' is in a whitelist: @@ -465,7 +465,7 @@ func TestBlockCNAME(t *testing.T) { reply, err = dns.Exchange(req, addr.String()) assert.Nil(t, err) assert.Equal(t, dns.RcodeSuccess, reply.Rcode) - assert.True(t, reply.Answer[0].(*dns.A).A.Equal(net.ParseIP("0.0.0.0"))) + assert.True(t, reply.Answer[0].(*dns.A).A.Equal(net.IP{0, 0, 0, 0})) _ = s.Stop() } @@ -548,13 +548,13 @@ func TestBlockedCustomIP(t *testing.T) { conf.TCPListenAddr = &net.TCPAddr{Port: 0} conf.ProtectionEnabled = true conf.BlockingMode = "custom_ip" - conf.BlockingIPv4 = "bad IP" + conf.BlockingIPv4 = nil conf.UpstreamDNS = []string{"8.8.8.8:53", "8.8.4.4:53"} err := s.Prepare(&conf) - assert.True(t, err != nil) // invalid BlockingIPv4 + assert.NotNil(t, err) // invalid BlockingIPv4 - conf.BlockingIPv4 = "0.0.0.1" - conf.BlockingIPv6 = "::1" + conf.BlockingIPv4 = net.IP{0, 0, 0, 1} + conf.BlockingIPv6 = net.ParseIP("::1") err = s.Prepare(&conf) assert.Nil(t, err) err = s.Start() @@ -565,7 +565,7 @@ func TestBlockedCustomIP(t *testing.T) { req := createTestMessageWithType("null.example.org.", dns.TypeA) reply, err := dns.Exchange(req, addr.String()) assert.Nil(t, err) - assert.Equal(t, 1, len(reply.Answer)) + assert.Len(t, reply.Answer, 1) a, ok := reply.Answer[0].(*dns.A) assert.True(t, ok) assert.Equal(t, "0.0.0.1", a.A.String()) @@ -573,7 +573,7 @@ func TestBlockedCustomIP(t *testing.T) { req = createTestMessageWithType("null.example.org.", dns.TypeAAAA) reply, err = dns.Exchange(req, addr.String()) assert.Nil(t, err) - assert.Equal(t, 1, len(reply.Answer)) + assert.Len(t, reply.Answer, 1) a6, ok := reply.Answer[0].(*dns.AAAA) assert.True(t, ok) assert.Equal(t, "::1", a6.AAAA.String()) @@ -710,7 +710,7 @@ func TestRewrite(t *testing.T) { req := createTestMessageWithType("test.com.", dns.TypeA) reply, err := dns.Exchange(req, addr.String()) assert.Nil(t, err) - assert.Equal(t, 1, len(reply.Answer)) + assert.Len(t, reply.Answer, 1) a, ok := reply.Answer[0].(*dns.A) assert.True(t, ok) assert.Equal(t, "1.2.3.4", a.A.String()) @@ -718,12 +718,12 @@ func TestRewrite(t *testing.T) { req = createTestMessageWithType("test.com.", dns.TypeAAAA) reply, err = dns.Exchange(req, addr.String()) assert.Nil(t, err) - assert.Equal(t, 0, len(reply.Answer)) + assert.Empty(t, reply.Answer) req = createTestMessageWithType("alias.test.com.", dns.TypeA) reply, err = dns.Exchange(req, addr.String()) assert.Nil(t, err) - assert.Equal(t, 2, len(reply.Answer)) + assert.Len(t, reply.Answer, 2) assert.Equal(t, "test.com.", reply.Answer[0].(*dns.CNAME).Target) assert.Equal(t, "1.2.3.4", reply.Answer[1].(*dns.A).A.String()) @@ -731,7 +731,7 @@ func TestRewrite(t *testing.T) { reply, err = dns.Exchange(req, addr.String()) assert.Nil(t, err) assert.Equal(t, "my.alias.example.org.", reply.Question[0].Name) // the original question is restored - assert.Equal(t, 2, len(reply.Answer)) + assert.Len(t, reply.Answer, 2) assert.Equal(t, "example.org.", reply.Answer[0].(*dns.CNAME).Target) assert.Equal(t, dns.TypeA, reply.Answer[1].Header().Rrtype) @@ -765,7 +765,7 @@ func createTestServer(t *testing.T) *Server { s.conf.ConfigModified = func() {} err := s.Prepare(nil) - assert.True(t, err == nil) + assert.Nil(t, err) return s } @@ -1011,16 +1011,14 @@ func TestValidateUpstreamsSet(t *testing.T) { assert.NotNil(t, err, "there is an invalid upstream in set, but it pass through validation") } -func TestIpFromAddr(t *testing.T) { +func TestIPStringFromAddr(t *testing.T) { addr := net.UDPAddr{} addr.IP = net.ParseIP("1:2:3::4") addr.Port = 12345 addr.Zone = "eth0" - a := ipFromAddr(&addr) - assert.True(t, a == "1:2:3::4") + assert.Equal(t, IPStringFromAddr(&addr), net.ParseIP("1:2:3::4").String()) - a = ipFromAddr(nil) - assert.True(t, a == "") + assert.Empty(t, IPStringFromAddr(nil)) } func TestMatchDNSName(t *testing.T) { @@ -1030,9 +1028,9 @@ func TestMatchDNSName(t *testing.T) { assert.True(t, matchDNSName(dnsNames, "a.host2")) assert.True(t, matchDNSName(dnsNames, "b.a.host2")) assert.True(t, matchDNSName(dnsNames, "1.2.3.4")) - assert.True(t, !matchDNSName(dnsNames, "host2")) - assert.True(t, !matchDNSName(dnsNames, "")) - assert.True(t, !matchDNSName(dnsNames, "*.host2")) + assert.False(t, matchDNSName(dnsNames, "host2")) + assert.False(t, matchDNSName(dnsNames, "")) + assert.False(t, matchDNSName(dnsNames, "*.host2")) } type testDHCP struct { @@ -1040,7 +1038,7 @@ type testDHCP struct { func (d *testDHCP) Leases(flags int) []dhcpd.Lease { l := dhcpd.Lease{} - l.IP = net.ParseIP("127.0.0.1").To4() + l.IP = net.IP{127, 0, 0, 1} l.HWAddr, _ = net.ParseMAC("aa:aa:aa:aa:aa:aa") l.Hostname = "localhost" return []dhcpd.Lease{l} @@ -1058,7 +1056,7 @@ func TestPTRResponseFromDHCPLeases(t *testing.T) { s.conf.UpstreamDNS = []string{"127.0.0.1:53"} s.conf.FilteringConfig.ProtectionEnabled = true err := s.Prepare(nil) - assert.True(t, err == nil) + assert.Nil(t, err) assert.Nil(t, s.Start()) addr := s.dnsProxy.Addr(proxy.ProtoUDP) @@ -1067,7 +1065,7 @@ func TestPTRResponseFromDHCPLeases(t *testing.T) { resp, err := dns.Exchange(req, addr.String()) assert.Nil(t, err) - assert.Equal(t, 1, len(resp.Answer)) + assert.Len(t, resp.Answer, 1) assert.Equal(t, dns.TypePTR, resp.Answer[0].Header().Rrtype) assert.Equal(t, "1.0.0.127.in-addr.arpa.", resp.Answer[0].Header().Name) ptr := resp.Answer[0].(*dns.PTR) @@ -1100,7 +1098,7 @@ func TestPTRResponseFromHosts(t *testing.T) { s.conf.UpstreamDNS = []string{"127.0.0.1:53"} s.conf.FilteringConfig.ProtectionEnabled = true err := s.Prepare(nil) - assert.True(t, err == nil) + assert.Nil(t, err) assert.Nil(t, s.Start()) addr := s.dnsProxy.Addr(proxy.ProtoUDP) @@ -1109,7 +1107,7 @@ func TestPTRResponseFromHosts(t *testing.T) { resp, err := dns.Exchange(req, addr.String()) assert.Nil(t, err) - assert.Equal(t, 1, len(resp.Answer)) + assert.Len(t, resp.Answer, 1) assert.Equal(t, dns.TypePTR, resp.Answer[0].Header().Rrtype) assert.Equal(t, "1.0.0.127.in-addr.arpa.", resp.Answer[0].Header().Name) ptr := resp.Answer[0].(*dns.PTR) diff --git a/internal/dnsforward/filter.go b/internal/dnsforward/filter.go index 80cf26dd..77ae30a9 100644 --- a/internal/dnsforward/filter.go +++ b/internal/dnsforward/filter.go @@ -12,7 +12,7 @@ import ( ) func (s *Server) beforeRequestHandler(_ *proxy.Proxy, d *proxy.DNSContext) (bool, error) { - ip := ipFromAddr(d.Addr) + ip := IPStringFromAddr(d.Addr) disallowed, _ := s.access.IsBlockedIP(ip) if disallowed { log.Tracef("Client IP %s is blocked by settings", ip) @@ -36,7 +36,7 @@ func (s *Server) getClientRequestFilteringSettings(d *proxy.DNSContext) *dnsfilt setts := s.dnsFilter.GetConfig() setts.FilteringEnabled = true if s.conf.FilterHandler != nil { - clientAddr := ipFromAddr(d.Addr) + clientAddr := IPStringFromAddr(d.Addr) s.conf.FilterHandler(clientAddr, &setts) } return &setts diff --git a/internal/dnsforward/http.go b/internal/dnsforward/http.go index e24ba89e..1f5780a6 100644 --- a/internal/dnsforward/http.go +++ b/internal/dnsforward/http.go @@ -28,8 +28,8 @@ type dnsConfig struct { ProtectionEnabled *bool `json:"protection_enabled"` RateLimit *uint32 `json:"ratelimit"` BlockingMode *string `json:"blocking_mode"` - BlockingIPv4 *string `json:"blocking_ipv4"` - BlockingIPv6 *string `json:"blocking_ipv6"` + BlockingIPv4 net.IP `json:"blocking_ipv4"` + BlockingIPv6 net.IP `json:"blocking_ipv6"` EDNSCSEnabled *bool `json:"edns_cs_enabled"` DNSSECEnabled *bool `json:"dnssec_enabled"` DisableIPv6 *bool `json:"disable_ipv6"` @@ -68,8 +68,8 @@ func (s *Server) getDNSConfig() dnsConfig { Bootstraps: &bootstraps, ProtectionEnabled: &protectionEnabled, BlockingMode: &blockingMode, - BlockingIPv4: &BlockingIPv4, - BlockingIPv6: &BlockingIPv6, + BlockingIPv4: BlockingIPv4, + BlockingIPv6: BlockingIPv6, RateLimit: &Ratelimit, EDNSCSEnabled: &EnableEDNSClientSubnet, DNSSECEnabled: &EnableDNSSEC, @@ -100,17 +100,11 @@ func (req *dnsConfig) checkBlockingMode() bool { bm := *req.BlockingMode if bm == "custom_ip" { - if req.BlockingIPv4 == nil || req.BlockingIPv6 == nil { + if req.BlockingIPv4.To4() == nil { return false } - ip4 := net.ParseIP(*req.BlockingIPv4) - if ip4 == nil || ip4.To4() == nil { - return false - } - - ip6 := net.ParseIP(*req.BlockingIPv6) - return ip6 != nil + return req.BlockingIPv6 != nil } for _, valid := range []string{ @@ -247,10 +241,8 @@ func (s *Server) setConfig(dc dnsConfig) (restart bool) { if dc.BlockingMode != nil { s.conf.BlockingMode = *dc.BlockingMode if *dc.BlockingMode == "custom_ip" { - s.conf.BlockingIPv4 = *dc.BlockingIPv4 - s.conf.BlockingIPAddrv4 = net.ParseIP(*dc.BlockingIPv4) - s.conf.BlockingIPv6 = *dc.BlockingIPv6 - s.conf.BlockingIPAddrv6 = net.ParseIP(*dc.BlockingIPv6) + s.conf.BlockingIPv4 = dc.BlockingIPv4.To4() + s.conf.BlockingIPv6 = dc.BlockingIPv6.To16() } } diff --git a/internal/dnsforward/msg.go b/internal/dnsforward/msg.go index 71497a6c..ba95bbce 100644 --- a/internal/dnsforward/msg.go +++ b/internal/dnsforward/msg.go @@ -60,9 +60,9 @@ func (s *Server) genDNSFilterMessage(d *proxy.DNSContext, result *dnsfilter.Resu switch m.Question[0].Qtype { case dns.TypeA: - return s.genARecord(m, s.conf.BlockingIPAddrv4) + return s.genARecord(m, s.conf.BlockingIPv4) case dns.TypeAAAA: - return s.genAAAARecord(m, s.conf.BlockingIPAddrv6) + return s.genAAAARecord(m, s.conf.BlockingIPv6) } } else if s.conf.BlockingMode == "nxdomain" { // means that we should return NXDOMAIN for any blocked request diff --git a/internal/dnsforward/stats.go b/internal/dnsforward/stats.go index c447be05..822df6a0 100644 --- a/internal/dnsforward/stats.go +++ b/internal/dnsforward/stats.go @@ -36,7 +36,7 @@ func processQueryLogsAndStats(ctx *dnsContext) int { OrigAnswer: ctx.origResp, Result: ctx.result, Elapsed: elapsed, - ClientIP: getIP(d.Addr), + ClientIP: ipFromAddr(d.Addr), } switch d.Proto { diff --git a/internal/dnsforward/util.go b/internal/dnsforward/util.go index da87f810..3a8c1cb3 100644 --- a/internal/dnsforward/util.go +++ b/internal/dnsforward/util.go @@ -8,38 +8,8 @@ import ( "github.com/AdguardTeam/golibs/utils" ) -// GetIPString is a helper function that extracts IP address from net.Addr -func GetIPString(addr net.Addr) string { - switch addr := addr.(type) { - case *net.UDPAddr: - return addr.IP.String() - case *net.TCPAddr: - return addr.IP.String() - } - return "" -} - -func stringArrayDup(a []string) []string { - a2 := make([]string, len(a)) - copy(a2, a) - return a2 -} - -// Get IP address from net.Addr object -// Note: we can't use net.SplitHostPort(a.String()) because of IPv6 zone: -// https://github.com/AdguardTeam/AdGuardHome/internal/issues/1261 -func ipFromAddr(a net.Addr) string { - switch addr := a.(type) { - case *net.UDPAddr: - return addr.IP.String() - case *net.TCPAddr: - return addr.IP.String() - } - return "" -} - -// Get IP address from net.Addr -func getIP(addr net.Addr) net.IP { +// ipFromAddr gets IP address from addr. +func ipFromAddr(addr net.Addr) (ip net.IP) { switch addr := addr.(type) { case *net.UDPAddr: return addr.IP @@ -49,6 +19,23 @@ func getIP(addr net.Addr) net.IP { return nil } +// IPStringFromAddr extracts IP address from net.Addr. +// Note: we can't use net.SplitHostPort(a.String()) because of IPv6 zone: +// https://github.com/AdguardTeam/AdGuardHome/internal/issues/1261 +func IPStringFromAddr(addr net.Addr) (ipstr string) { + if ip := ipFromAddr(addr); ip != nil { + return ip.String() + } + + return "" +} + +func stringArrayDup(a []string) []string { + a2 := make([]string, len(a)) + copy(a2, a) + return a2 +} + // Find value in a sorted array func findSorted(ar []string, val string) int { i := sort.SearchStrings(ar, val) diff --git a/internal/home/auth_test.go b/internal/home/auth_test.go index 0998a2a6..3811b170 100644 --- a/internal/home/auth_test.go +++ b/internal/home/auth_test.go @@ -70,7 +70,7 @@ func TestAuth(t *testing.T) { a.Close() u := a.UserFind("name", "password") - assert.True(t, len(u.Name) != 0) + assert.NotEmpty(t, u.Name) time.Sleep(3 * time.Second) @@ -125,9 +125,9 @@ func TestAuthHTTP(t *testing.T) { r.URL = &url.URL{Path: "/"} handlerCalled = false handler2(&w, &r) - assert.True(t, w.statusCode == http.StatusFound) - assert.True(t, w.hdr.Get("Location") != "") - assert.True(t, !handlerCalled) + assert.Equal(t, http.StatusFound, w.statusCode) + assert.NotEmpty(t, w.hdr.Get("Location")) + assert.False(t, handlerCalled) // go to login page loginURL := w.hdr.Get("Location") @@ -139,7 +139,7 @@ func TestAuthHTTP(t *testing.T) { // perform login cookie, err := Context.auth.httpCookie(loginJSON{Name: "name", Password: "password"}) assert.Nil(t, err) - assert.True(t, cookie != "") + assert.NotEmpty(t, cookie) // get / handler2 = optionalAuth(handler) @@ -168,8 +168,8 @@ func TestAuthHTTP(t *testing.T) { r.URL = &url.URL{Path: loginURL} handlerCalled = false handler2(&w, &r) - assert.True(t, w.hdr.Get("Location") != "") - assert.True(t, !handlerCalled) + assert.NotEmpty(t, w.hdr.Get("Location")) + assert.False(t, handlerCalled) r.Header.Del("Cookie") // get login page with an invalid cookie diff --git a/internal/home/clients_test.go b/internal/home/clients_test.go index 9268c08f..69f2bada 100644 --- a/internal/home/clients_test.go +++ b/internal/home/clients_test.go @@ -37,15 +37,18 @@ func TestClients(t *testing.T) { assert.Nil(t, err) c, b = clients.Find("1.1.1.1") - assert.True(t, b && c.Name == "client1") + assert.True(t, b) + assert.Equal(t, c.Name, "client1") c, b = clients.Find("1:2:3::4") - assert.True(t, b && c.Name == "client1") + assert.True(t, b) + assert.Equal(t, c.Name, "client1") c, b = clients.Find("2.2.2.2") - assert.True(t, b && c.Name == "client2") + assert.True(t, b) + assert.Equal(t, c.Name, "client2") - assert.True(t, !clients.Exists("1.2.3.4", ClientSourceHostsFile)) + assert.False(t, clients.Exists("1.2.3.4", ClientSourceHostsFile)) assert.True(t, clients.Exists("1.1.1.1", ClientSourceHostsFile)) assert.True(t, clients.Exists("2.2.2.2", ClientSourceHostsFile)) }) @@ -109,7 +112,7 @@ func TestClients(t *testing.T) { err := clients.Update("client1", c) assert.Nil(t, err) - assert.True(t, !clients.Exists("1.1.1.1", ClientSourceHostsFile)) + assert.False(t, clients.Exists("1.1.1.1", ClientSourceHostsFile)) assert.True(t, clients.Exists("1.1.1.2", ClientSourceHostsFile)) c = Client{ @@ -123,8 +126,8 @@ func TestClients(t *testing.T) { c, b := clients.Find("1.1.1.2") assert.True(t, b) - assert.True(t, c.Name == "client1-renamed") - assert.True(t, c.IDs[0] == "1.1.1.2") + assert.Equal(t, "client1-renamed", c.Name) + assert.Equal(t, "1.1.1.2", c.IDs[0]) assert.True(t, c.UseOwnSettings) assert.Nil(t, clients.list["client1"]) }) @@ -172,12 +175,12 @@ func TestClientsWhois(t *testing.T) { whois := [][]string{{"orgname", "orgname-val"}, {"country", "country-val"}} // set whois info on new client clients.SetWhoisInfo("1.1.1.255", whois) - assert.True(t, clients.ipHost["1.1.1.255"].WhoisInfo[0][1] == "orgname-val") + assert.Equal(t, "orgname-val", clients.ipHost["1.1.1.255"].WhoisInfo[0][1]) // set whois info on existing auto-client _, _ = clients.AddHost("1.1.1.1", "host", ClientSourceRDNS) clients.SetWhoisInfo("1.1.1.1", whois) - assert.True(t, clients.ipHost["1.1.1.1"].WhoisInfo[0][1] == "orgname-val") + assert.Equal(t, "orgname-val", clients.ipHost["1.1.1.1"].WhoisInfo[0][1]) // Check that we cannot set whois info on a manually-added client c = Client{ @@ -186,7 +189,7 @@ func TestClientsWhois(t *testing.T) { } _, _ = clients.Add(c) clients.SetWhoisInfo("1.1.1.2", whois) - assert.True(t, clients.ipHost["1.1.1.2"] == nil) + assert.Nil(t, clients.ipHost["1.1.1.2"]) _ = clients.Del("client1") } @@ -272,6 +275,6 @@ func TestClientsCustomUpstream(t *testing.T) { config = clients.FindUpstreams("1.1.1.1") assert.NotNil(t, config) - assert.Equal(t, 1, len(config.Upstreams)) - assert.Equal(t, 1, len(config.DomainReservedUpstreams)) + assert.Len(t, config.Upstreams, 1) + assert.Len(t, config.DomainReservedUpstreams, 1) } diff --git a/internal/home/dns.go b/internal/home/dns.go index 1090d9be..a9880629 100644 --- a/internal/home/dns.go +++ b/internal/home/dns.go @@ -98,7 +98,7 @@ func isRunning() bool { } func onDNSRequest(d *proxy.DNSContext) { - ip := dnsforward.GetIPString(d.Addr) + ip := dnsforward.IPStringFromAddr(d.Addr) if ip == "" { // This would be quite weird if we get here return diff --git a/internal/home/filter_test.go b/internal/home/filter_test.go index 2bc23be1..a5b6d20b 100644 --- a/internal/home/filter_test.go +++ b/internal/home/filter_test.go @@ -50,16 +50,17 @@ func TestFilters(t *testing.T) { // download ok, err := Context.filters.update(&f) - assert.Equal(t, nil, err) + assert.Nil(t, err) assert.True(t, ok) assert.Equal(t, 3, f.RulesCount) // refresh ok, err = Context.filters.update(&f) - assert.True(t, !ok && err == nil) + assert.False(t, ok) + assert.Nil(t, err) err = Context.filters.load(&f) - assert.True(t, err == nil) + assert.Nil(t, err) f.unload() _ = os.Remove(f.Path()) diff --git a/internal/home/home_test.go b/internal/home/home_test.go index 1b16e357..b21d7d46 100644 --- a/internal/home/home_test.go +++ b/internal/home/home_test.go @@ -119,7 +119,7 @@ func TestHome(t *testing.T) { fn := filepath.Join(dir, "AdGuardHome.yaml") // Prepare the test config - assert.True(t, ioutil.WriteFile(fn, []byte(yamlConf), 0o644) == nil) + assert.Nil(t, ioutil.WriteFile(fn, []byte(yamlConf), 0o644)) fn, _ = filepath.Abs(fn) config = configuration{} // the global variable is dirty because of the previous tests run @@ -138,11 +138,11 @@ func TestHome(t *testing.T) { } time.Sleep(100 * time.Millisecond) } - assert.Truef(t, err == nil, "%s", err) + assert.Nilf(t, err, "%s", err) assert.Equal(t, http.StatusOK, resp.StatusCode) resp, err = h.Get("http://127.0.0.1:3000/control/status") - assert.Truef(t, err == nil, "%s", err) + assert.Nilf(t, err, "%s", err) assert.Equal(t, http.StatusOK, resp.StatusCode) // test DNS over UDP @@ -159,16 +159,16 @@ func TestHome(t *testing.T) { req.RecursionDesired = true req.Question = []dns.Question{{Name: "static.adguard.com.", Qtype: dns.TypeA, Qclass: dns.ClassINET}} buf, err := req.Pack() - assert.True(t, err == nil, "%s", err) + assert.Nil(t, err) requestURL := "http://127.0.0.1:3000/dns-query?dns=" + base64.RawURLEncoding.EncodeToString(buf) resp, err = http.DefaultClient.Get(requestURL) - assert.True(t, err == nil, "%s", err) + assert.Nil(t, err) body, err := ioutil.ReadAll(resp.Body) - assert.True(t, err == nil, "%s", err) - assert.True(t, resp.StatusCode == http.StatusOK) + assert.Nil(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) response := dns.Msg{} err = response.Unpack(body) - assert.True(t, err == nil, "%s", err) + assert.Nil(t, err) addrs = nil proxyutil.AppendIPAddrs(&addrs, response.Answer) haveIP = len(addrs) != 0 diff --git a/internal/home/mobileconfig_test.go b/internal/home/mobileconfig_test.go index f5bf3f2b..1025fe93 100644 --- a/internal/home/mobileconfig_test.go +++ b/internal/home/mobileconfig_test.go @@ -23,7 +23,7 @@ func TestHandleMobileConfigDOH(t *testing.T) { _, err = plist.Unmarshal(w.Body.Bytes(), &mc) assert.Nil(t, err) - if assert.Equal(t, 1, len(mc.PayloadContent)) { + if assert.Len(t, mc.PayloadContent, 1) { assert.Equal(t, "example.org DoH", mc.PayloadContent[0].Name) assert.Equal(t, "example.org DoH", mc.PayloadContent[0].PayloadDisplayName) assert.Equal(t, "example.org", mc.PayloadContent[0].DNSSettings.ServerName) @@ -51,7 +51,7 @@ func TestHandleMobileConfigDOH(t *testing.T) { _, err = plist.Unmarshal(w.Body.Bytes(), &mc) assert.Nil(t, err) - if assert.Equal(t, 1, len(mc.PayloadContent)) { + if assert.Len(t, mc.PayloadContent, 1) { assert.Equal(t, "example.org DoH", mc.PayloadContent[0].Name) assert.Equal(t, "example.org DoH", mc.PayloadContent[0].PayloadDisplayName) assert.Equal(t, "example.org", mc.PayloadContent[0].DNSSettings.ServerName) @@ -89,7 +89,7 @@ func TestHandleMobileConfigDOT(t *testing.T) { _, err = plist.Unmarshal(w.Body.Bytes(), &mc) assert.Nil(t, err) - if assert.Equal(t, 1, len(mc.PayloadContent)) { + if assert.Len(t, mc.PayloadContent, 1) { assert.Equal(t, "example.org DoT", mc.PayloadContent[0].Name) assert.Equal(t, "example.org DoT", mc.PayloadContent[0].PayloadDisplayName) assert.Equal(t, "example.org", mc.PayloadContent[0].DNSSettings.ServerName) @@ -116,7 +116,7 @@ func TestHandleMobileConfigDOT(t *testing.T) { _, err = plist.Unmarshal(w.Body.Bytes(), &mc) assert.Nil(t, err) - if assert.Equal(t, 1, len(mc.PayloadContent)) { + if assert.Len(t, mc.PayloadContent, 1) { assert.Equal(t, "example.org DoT", mc.PayloadContent[0].Name) assert.Equal(t, "example.org DoT", mc.PayloadContent[0].PayloadDisplayName) assert.Equal(t, "example.org", mc.PayloadContent[0].DNSSettings.ServerName) diff --git a/internal/home/rdns_test.go b/internal/home/rdns_test.go index 399886c4..516b0ed5 100644 --- a/internal/home/rdns_test.go +++ b/internal/home/rdns_test.go @@ -12,10 +12,10 @@ func TestResolveRDNS(t *testing.T) { conf := &dnsforward.ServerConfig{} conf.UpstreamDNS = []string{"8.8.8.8"} err := dns.Prepare(conf) - assert.True(t, err == nil, "%s", err) + assert.Nil(t, err) clients := &clientsContainer{} rdns := InitRDNS(dns, clients) r := rdns.resolve("1.1.1.1") - assert.True(t, r == "one.one.one.one", "%s", r) + assert.Equal(t, "one.one.one.one", r, r) } diff --git a/internal/querylog/decode_test.go b/internal/querylog/decode_test.go index ffcf94dc..a599084d 100644 --- a/internal/querylog/decode_test.go +++ b/internal/querylog/decode_test.go @@ -84,7 +84,7 @@ func TestDecodeLogEntry(t *testing.T) { decodeLogEntry(got, data) s := logOutput.String() - assert.Equal(t, "", s) + assert.Empty(t, s) // Correct for time zones. got.Time = got.Time.UTC() @@ -172,7 +172,7 @@ func TestDecodeLogEntry(t *testing.T) { s := logOutput.String() if tc.want == "" { - assert.Equal(t, "", s) + assert.Empty(t, s) } else { assert.True(t, strings.HasSuffix(s, tc.want), "got %q", s) diff --git a/internal/querylog/qlog_test.go b/internal/querylog/qlog_test.go index dfd4e6ce..0fa072c1 100644 --- a/internal/querylog/qlog_test.go +++ b/internal/querylog/qlog_test.go @@ -56,7 +56,7 @@ func TestQueryLog(t *testing.T) { // get all entries params := newSearchParams() entries, _ := l.search(params) - assert.Equal(t, 4, len(entries)) + assert.Len(t, entries, 4) assertLogEntry(t, entries[0], "example.com", "1.1.1.4", "2.2.2.4") assertLogEntry(t, entries[1], "test.example.org", "1.1.1.3", "2.2.2.3") assertLogEntry(t, entries[2], "example.org", "1.1.1.2", "2.2.2.2") @@ -70,7 +70,7 @@ func TestQueryLog(t *testing.T) { value: "TEST.example.org", }) entries, _ = l.search(params) - assert.Equal(t, 1, len(entries)) + assert.Len(t, entries, 1) assertLogEntry(t, entries[0], "test.example.org", "1.1.1.3", "2.2.2.3") // search by domain (not strict) @@ -81,7 +81,7 @@ func TestQueryLog(t *testing.T) { value: "example.ORG", }) entries, _ = l.search(params) - assert.Equal(t, 3, len(entries)) + assert.Len(t, entries, 3) assertLogEntry(t, entries[0], "test.example.org", "1.1.1.3", "2.2.2.3") assertLogEntry(t, entries[1], "example.org", "1.1.1.2", "2.2.2.2") assertLogEntry(t, entries[2], "example.org", "1.1.1.1", "2.2.2.1") @@ -94,7 +94,7 @@ func TestQueryLog(t *testing.T) { value: "2.2.2.2", }) entries, _ = l.search(params) - assert.Equal(t, 1, len(entries)) + assert.Len(t, entries, 1) assertLogEntry(t, entries[0], "example.org", "1.1.1.2", "2.2.2.2") // search by client IP (part of) @@ -105,7 +105,7 @@ func TestQueryLog(t *testing.T) { value: "2.2.2", }) entries, _ = l.search(params) - assert.Equal(t, 4, len(entries)) + assert.Len(t, entries, 4) assertLogEntry(t, entries[0], "example.com", "1.1.1.4", "2.2.2.4") assertLogEntry(t, entries[1], "test.example.org", "1.1.1.3", "2.2.2.3") assertLogEntry(t, entries[2], "example.org", "1.1.1.2", "2.2.2.2") @@ -138,7 +138,7 @@ func TestQueryLogOffsetLimit(t *testing.T) { params.offset = 0 params.limit = 10 entries, _ := l.search(params) - assert.Equal(t, 10, len(entries)) + assert.Len(t, entries, 10) assert.Equal(t, entries[0].QHost, "first.example.org") assert.Equal(t, entries[9].QHost, "first.example.org") @@ -146,7 +146,7 @@ func TestQueryLogOffsetLimit(t *testing.T) { params.offset = 10 params.limit = 10 entries, _ = l.search(params) - assert.Equal(t, 10, len(entries)) + assert.Len(t, entries, 10) assert.Equal(t, entries[0].QHost, "second.example.org") assert.Equal(t, entries[9].QHost, "second.example.org") @@ -154,7 +154,7 @@ func TestQueryLogOffsetLimit(t *testing.T) { params.offset = 15 params.limit = 10 entries, _ = l.search(params) - assert.Equal(t, 5, len(entries)) + assert.Len(t, entries, 5) assert.Equal(t, entries[0].QHost, "second.example.org") assert.Equal(t, entries[4].QHost, "second.example.org") @@ -162,7 +162,7 @@ func TestQueryLogOffsetLimit(t *testing.T) { params.offset = 20 params.limit = 10 entries, _ = l.search(params) - assert.Equal(t, 0, len(entries)) + assert.Empty(t, entries) } func TestQueryLogMaxFileScanEntries(t *testing.T) { @@ -186,11 +186,11 @@ func TestQueryLogMaxFileScanEntries(t *testing.T) { params := newSearchParams() params.maxFileScanEntries = 5 // do not scan more than 5 records entries, _ := l.search(params) - assert.Equal(t, 5, len(entries)) + assert.Len(t, entries, 5) params.maxFileScanEntries = 0 // disable the limit entries, _ = l.search(params) - assert.Equal(t, 10, len(entries)) + assert.Len(t, entries, 10) } func TestQueryLogFileDisabled(t *testing.T) { @@ -211,7 +211,7 @@ func TestQueryLogFileDisabled(t *testing.T) { params := newSearchParams() ll, _ := l.search(params) - assert.Equal(t, 2, len(ll)) + assert.Len(t, ll, 2) assert.Equal(t, "example3.org", ll[0].QHost) assert.Equal(t, "example2.org", ll[1].QHost) } @@ -262,7 +262,7 @@ func assertLogEntry(t *testing.T, entry *logEntry, host, answer, client string) msg := new(dns.Msg) assert.Nil(t, msg.Unpack(entry.Answer)) - assert.Equal(t, 1, len(msg.Answer)) + assert.Len(t, msg.Answer, 1) ip := proxyutil.GetIPFromDNSRecord(msg.Answer[0]) assert.NotNil(t, ip) assert.Equal(t, answer, ip.String()) diff --git a/internal/querylog/qlogfile_test.go b/internal/querylog/qlogfile_test.go index 950eaaf3..7d603ba5 100644 --- a/internal/querylog/qlogfile_test.go +++ b/internal/querylog/qlogfile_test.go @@ -28,12 +28,12 @@ func TestQLogFileEmpty(t *testing.T) { // seek to the start pos, err := q.SeekStart() assert.Nil(t, err) - assert.Equal(t, int64(0), pos) + assert.EqualValues(t, 0, pos) // try reading anyway line, err := q.ReadNext() assert.Equal(t, io.EOF, err) - assert.Equal(t, "", line) + assert.Empty(t, line) } func TestQLogFileLarge(t *testing.T) { @@ -53,14 +53,14 @@ func TestQLogFileLarge(t *testing.T) { // seek to the start pos, err := q.SeekStart() assert.Nil(t, err) - assert.NotEqual(t, int64(0), pos) + assert.NotEqualValues(t, 0, pos) read := 0 var line string for err == nil { line, err = q.ReadNext() if err == nil { - assert.True(t, len(line) > 0) + assert.NotZero(t, len(line)) read++ } } @@ -109,10 +109,10 @@ func TestQLogFileSeekLargeFile(t *testing.T) { assert.Nil(t, err) // ALMOST the record we need timestamp := readQLogTimestamp(line) - 1 - assert.NotEqual(t, uint64(0), timestamp) + assert.NotEqualValues(t, 0, timestamp) _, depth, err := q.SeekTS(timestamp) assert.NotNil(t, err) - assert.True(t, depth <= int(math.Log2(float64(count))+3)) + assert.LessOrEqual(t, depth, int(math.Log2(float64(count))+3)) } func TestQLogFileSeekSmallFile(t *testing.T) { @@ -155,22 +155,22 @@ func TestQLogFileSeekSmallFile(t *testing.T) { assert.Nil(t, err) // ALMOST the record we need timestamp := readQLogTimestamp(line) - 1 - assert.NotEqual(t, uint64(0), timestamp) + assert.NotEqualValues(t, 0, timestamp) _, depth, err := q.SeekTS(timestamp) assert.NotNil(t, err) - assert.True(t, depth <= int(math.Log2(float64(count))+3)) + assert.LessOrEqual(t, depth, int(math.Log2(float64(count))+3)) } func testSeekLineQLogFile(t *testing.T, q *QLogFile, lineNumber int) { line, err := getQLogFileLine(q, lineNumber) assert.Nil(t, err) ts := readQLogTimestamp(line) - assert.NotEqual(t, uint64(0), ts) + assert.NotEqualValues(t, 0, ts) // try seeking to that line now pos, _, err := q.SeekTS(ts) assert.Nil(t, err) - assert.NotEqual(t, int64(0), pos) + assert.NotEqualValues(t, 0, pos) testLine, err := q.ReadNext() assert.Nil(t, err) @@ -207,27 +207,27 @@ func TestQLogFile(t *testing.T) { // seek to the start pos, err := q.SeekStart() assert.Nil(t, err) - assert.True(t, pos > 0) + assert.Greater(t, pos, int64(0)) // read first line line, err := q.ReadNext() assert.Nil(t, err) - assert.True(t, strings.Contains(line, "0.0.0.2"), line) + assert.Contains(t, line, "0.0.0.2") assert.True(t, strings.HasPrefix(line, "{"), line) assert.True(t, strings.HasSuffix(line, "}"), line) // read second line line, err = q.ReadNext() assert.Nil(t, err) - assert.Equal(t, int64(0), q.position) - assert.True(t, strings.Contains(line, "0.0.0.1"), line) + assert.EqualValues(t, 0, q.position) + assert.Contains(t, line, "0.0.0.1") assert.True(t, strings.HasPrefix(line, "{"), line) assert.True(t, strings.HasSuffix(line, "}"), line) // try reading again (there's nothing to read anymore) line, err = q.ReadNext() assert.Equal(t, io.EOF, err) - assert.Equal(t, "", line) + assert.Empty(t, line) } // prepareTestFile - prepares a test query log file with the specified number of lines diff --git a/internal/querylog/qlogreader_test.go b/internal/querylog/qlogreader_test.go index d9dfb3ea..967e8396 100644 --- a/internal/querylog/qlogreader_test.go +++ b/internal/querylog/qlogreader_test.go @@ -21,7 +21,7 @@ func TestQLogReaderEmpty(t *testing.T) { assert.Nil(t, err) line, err := r.ReadNext() - assert.Equal(t, "", line) + assert.Empty(t, line) assert.Equal(t, io.EOF, err) } @@ -241,7 +241,7 @@ func testSeekLineQLogReader(t *testing.T, r *QLogReader, lineNumber int) { line, err := getQLogReaderLine(r, lineNumber) assert.Nil(t, err) ts := readQLogTimestamp(line) - assert.NotEqual(t, uint64(0), ts) + assert.NotEqualValues(t, 0, ts) // try seeking to that line now err = r.SeekTS(ts) diff --git a/internal/stats/stats_test.go b/internal/stats/stats_test.go index 3a4bed66..47e68799 100644 --- a/internal/stats/stats_test.go +++ b/internal/stats/stats_test.go @@ -39,13 +39,13 @@ func TestStats(t *testing.T) { e := Entry{} e.Domain = "domain" - e.Client = net.ParseIP("127.0.0.1") + e.Client = net.IP{127, 0, 0, 1} e.Result = RFiltered e.Time = 123456 s.Update(e) e.Domain = "domain" - e.Client = net.ParseIP("127.0.0.1") + e.Client = net.IP{127, 0, 0, 1} e.Result = RNotFiltered e.Time = 123456 s.Update(e) @@ -64,23 +64,23 @@ func TestStats(t *testing.T) { assert.True(t, UIntArrayEquals(d["replaced_parental"].([]uint64), a)) m := d["top_queried_domains"].([]map[string]uint64) - assert.True(t, m[0]["domain"] == 1) + assert.EqualValues(t, 1, m[0]["domain"]) m = d["top_blocked_domains"].([]map[string]uint64) - assert.True(t, m[0]["domain"] == 1) + assert.EqualValues(t, 1, m[0]["domain"]) m = d["top_clients"].([]map[string]uint64) - assert.True(t, m[0]["127.0.0.1"] == 2) + assert.EqualValues(t, 2, m[0]["127.0.0.1"]) - assert.True(t, d["num_dns_queries"].(uint64) == 2) - assert.True(t, d["num_blocked_filtering"].(uint64) == 1) - assert.True(t, d["num_replaced_safebrowsing"].(uint64) == 0) - assert.True(t, d["num_replaced_safesearch"].(uint64) == 0) - assert.True(t, d["num_replaced_parental"].(uint64) == 0) - assert.True(t, d["avg_processing_time"].(float64) == 0.123456) + assert.EqualValues(t, 2, d["num_dns_queries"].(uint64)) + assert.EqualValues(t, 1, d["num_blocked_filtering"].(uint64)) + assert.EqualValues(t, 0, d["num_replaced_safebrowsing"].(uint64)) + assert.EqualValues(t, 0, d["num_replaced_safesearch"].(uint64)) + assert.EqualValues(t, 0, d["num_replaced_parental"].(uint64)) + assert.EqualValues(t, 0.123456, d["avg_processing_time"].(float64)) topClients := s.GetTopClientsIP(2) - assert.True(t, topClients[0] == "127.0.0.1") + assert.Equal(t, "127.0.0.1", topClients[0]) s.clear() s.Close() @@ -111,7 +111,7 @@ func TestLargeNumbers(t *testing.T) { } for i := 0; i != n; i++ { e.Domain = fmt.Sprintf("domain%d", i) - e.Client = net.ParseIP("127.0.0.1") + e.Client = net.IP{127, 0, 0, 1} e.Client[2] = byte((i & 0xff00) >> 8) e.Client[3] = byte(i & 0xff) e.Result = RNotFiltered @@ -121,7 +121,7 @@ func TestLargeNumbers(t *testing.T) { } d := s.getData() - assert.True(t, d["num_dns_queries"].(uint64) == uint64(int(hour)*n)) + assert.EqualValues(t, int(hour)*n, d["num_dns_queries"]) s.Close() os.Remove(conf.Filename) @@ -152,6 +152,6 @@ func aggregateDataPerDay(firstID uint32) int { func TestAggregateDataPerTimeUnit(t *testing.T) { for i := 0; i != 25; i++ { alen := aggregateDataPerDay(uint32(i)) - assert.True(t, alen == 30, "i=%d", i) + assert.Equalf(t, 30, alen, "i=%d", i) } } diff --git a/internal/sysutil/net.go b/internal/sysutil/net.go index 557dd8d7..0e3b448e 100644 --- a/internal/sysutil/net.go +++ b/internal/sysutil/net.go @@ -19,12 +19,12 @@ func IfaceSetStaticIP(ifaceName string) (err error) { } // GatewayIP returns IP address of interface's gateway. -func GatewayIP(ifaceName string) string { +func GatewayIP(ifaceName string) net.IP { cmd := exec.Command("ip", "route", "show", "dev", ifaceName) log.Tracef("executing %s %v", cmd.Path, cmd.Args) d, err := cmd.Output() if err != nil || cmd.ProcessState.ExitCode() != 0 { - return "" + return nil } fields := strings.Fields(string(d)) @@ -32,13 +32,8 @@ func GatewayIP(ifaceName string) string { // "default" at first field and default gateway IP address at third // field. if len(fields) < 3 || fields[0] != "default" { - return "" + return nil } - ip := net.ParseIP(fields[2]) - if ip == nil { - return "" - } - - return fields[2] + return net.ParseIP(fields[2]) } diff --git a/internal/sysutil/net_linux.go b/internal/sysutil/net_linux.go index 5206f9fd..06d27eb2 100644 --- a/internal/sysutil/net_linux.go +++ b/internal/sysutil/net_linux.go @@ -129,7 +129,7 @@ func ifaceSetStaticIP(ifaceName string) (err error) { return err } gatewayIP := GatewayIP(ifaceName) - add := updateStaticIPdhcpcdConf(ifaceName, ip, gatewayIP, ip4.String()) + add := updateStaticIPdhcpcdConf(ifaceName, ip, gatewayIP, ip4) body, err := ioutil.ReadFile("/etc/dhcpcd.conf") if err != nil { @@ -147,14 +147,14 @@ func ifaceSetStaticIP(ifaceName string) (err error) { // updateStaticIPdhcpcdConf sets static IP address for the interface by writing // into dhcpd.conf. -func updateStaticIPdhcpcdConf(ifaceName, ip, gatewayIP, dnsIP string) string { +func updateStaticIPdhcpcdConf(ifaceName, ip string, gatewayIP, dnsIP net.IP) string { var body []byte add := fmt.Sprintf("\ninterface %s\nstatic ip_address=%s\n", ifaceName, ip) body = append(body, []byte(add)...) - if len(gatewayIP) != 0 { + if gatewayIP != nil { add = fmt.Sprintf("static routers=%s\n", gatewayIP) body = append(body, []byte(add)...) diff --git a/internal/sysutil/net_linux_test.go b/internal/sysutil/net_linux_test.go index 8cadbbb7..a9851cb2 100644 --- a/internal/sysutil/net_linux_test.go +++ b/internal/sysutil/net_linux_test.go @@ -4,6 +4,7 @@ package sysutil import ( "bytes" + "net" "testing" "github.com/stretchr/testify/assert" @@ -96,7 +97,7 @@ func TestSetStaticIPdhcpcdConf(t *testing.T) { `static routers=192.168.0.1` + nl + `static domain_name_servers=192.168.0.2` + nl + nl - s := updateStaticIPdhcpcdConf("wlan0", "192.168.0.2/24", "192.168.0.1", "192.168.0.2") + s := updateStaticIPdhcpcdConf("wlan0", "192.168.0.2/24", net.IP{192, 168, 0, 1}, net.IP{192, 168, 0, 2}) assert.Equal(t, dhcpcdConf, s) // without gateway @@ -104,6 +105,6 @@ func TestSetStaticIPdhcpcdConf(t *testing.T) { `static ip_address=192.168.0.2/24` + nl + `static domain_name_servers=192.168.0.2` + nl + nl - s = updateStaticIPdhcpcdConf("wlan0", "192.168.0.2/24", "", "192.168.0.2") + s = updateStaticIPdhcpcdConf("wlan0", "192.168.0.2/24", nil, net.IP{192, 168, 0, 2}) assert.Equal(t, dhcpcdConf, s) } diff --git a/internal/util/autohosts_test.go b/internal/util/autohosts_test.go index 04911142..393646c7 100644 --- a/internal/util/autohosts_test.go +++ b/internal/util/autohosts_test.go @@ -42,7 +42,7 @@ func TestAutoHostsResolution(t *testing.T) { // Existing host ips := ah.Process("localhost", dns.TypeA) assert.NotNil(t, ips) - assert.Equal(t, 1, len(ips)) + assert.Len(t, ips, 1) assert.Equal(t, net.ParseIP("127.0.0.1"), ips[0]) // Unknown host @@ -107,7 +107,7 @@ func TestAutoHostsFSNotify(t *testing.T) { // Check if we are notified about changes ips = ah.Process("newhost", dns.TypeA) assert.NotNil(t, ips) - assert.Equal(t, 1, len(ips)) + assert.Len(t, ips, 1) assert.Equal(t, "127.0.0.2", ips[0].String()) } diff --git a/internal/util/helpers_test.go b/internal/util/helpers_test.go index d5e90637..68ebbabd 100644 --- a/internal/util/helpers_test.go +++ b/internal/util/helpers_test.go @@ -8,7 +8,8 @@ import ( func TestSplitNext(t *testing.T) { s := " a,b , c " - assert.True(t, SplitNext(&s, ',') == "a") - assert.True(t, SplitNext(&s, ',') == "b") - assert.True(t, SplitNext(&s, ',') == "c" && len(s) == 0) + assert.Equal(t, "a", SplitNext(&s, ',')) + assert.Equal(t, "b", SplitNext(&s, ',')) + assert.Equal(t, "c", SplitNext(&s, ',')) + assert.Empty(t, s) } From 4474e9fcf9073af593df76b4844f4091c1c7946e Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Wed, 13 Jan 2021 17:26:57 +0300 Subject: [PATCH 045/141] Pull request: all: imp http handlers, imp docs Merge in DNS/adguard-home from fix-openapi to master Squashed commit of the following: commit 0e7530472fb566e5cab73d178c8ec16e5ef11dcb Author: Ainar Garipov Date: Wed Jan 13 17:02:06 2021 +0300 all: imp http handlers, imp docs --- internal/dnsforward/http.go | 10 ++-- internal/home/auth.go | 4 +- internal/home/auth_test.go | 2 +- internal/home/authglinet_test.go | 2 +- internal/home/control.go | 8 +-- openapi/CHANGELOG.md | 2 + openapi/openapi.yaml | 86 ++++++++++++++++++++++++++------ 7 files changed, 87 insertions(+), 27 deletions(-) diff --git a/internal/dnsforward/http.go b/internal/dnsforward/http.go index 1f5780a6..3a6d6578 100644 --- a/internal/dnsforward/http.go +++ b/internal/dnsforward/http.go @@ -522,12 +522,12 @@ func (s *Server) handleDOH(w http.ResponseWriter, r *http.Request) { } func (s *Server) registerHandlers() { - s.conf.HTTPRegister("GET", "/control/dns_info", s.handleGetConfig) - s.conf.HTTPRegister("POST", "/control/dns_config", s.handleSetConfig) - s.conf.HTTPRegister("POST", "/control/test_upstream_dns", s.handleTestUpstreamDNS) + s.conf.HTTPRegister(http.MethodGet, "/control/dns_info", s.handleGetConfig) + s.conf.HTTPRegister(http.MethodPost, "/control/dns_config", s.handleSetConfig) + s.conf.HTTPRegister(http.MethodPost, "/control/test_upstream_dns", s.handleTestUpstreamDNS) - s.conf.HTTPRegister("GET", "/control/access/list", s.handleAccessList) - s.conf.HTTPRegister("POST", "/control/access/set", s.handleAccessSet) + s.conf.HTTPRegister(http.MethodGet, "/control/access/list", s.handleAccessList) + s.conf.HTTPRegister(http.MethodPost, "/control/access/set", s.handleAccessSet) s.conf.HTTPRegister("", "/dns-query", s.handleDOH) } diff --git a/internal/home/auth.go b/internal/home/auth.go index dce17241..26b57787 100644 --- a/internal/home/auth.go +++ b/internal/home/auth.go @@ -369,8 +369,8 @@ func handleLogout(w http.ResponseWriter, r *http.Request) { // RegisterAuthHandlers - register handlers func RegisterAuthHandlers() { - Context.mux.Handle("/control/login", postInstallHandler(ensureHandler("POST", handleLogin))) - httpRegister("GET", "/control/logout", handleLogout) + Context.mux.Handle("/control/login", postInstallHandler(ensureHandler(http.MethodPost, handleLogin))) + httpRegister(http.MethodGet, "/control/logout", handleLogout) } func parseCookie(cookie string) string { diff --git a/internal/home/auth_test.go b/internal/home/auth_test.go index 3811b170..4a4a21c8 100644 --- a/internal/home/auth_test.go +++ b/internal/home/auth_test.go @@ -119,7 +119,7 @@ func TestAuthHTTP(t *testing.T) { w.hdr = make(http.Header) r := http.Request{} r.Header = make(http.Header) - r.Method = "GET" + r.Method = http.MethodGet // get / - we're redirected to login page r.URL = &url.URL{Path: "/"} diff --git a/internal/home/authglinet_test.go b/internal/home/authglinet_test.go index df5e3342..70bb6636 100644 --- a/internal/home/authglinet_test.go +++ b/internal/home/authglinet_test.go @@ -36,7 +36,7 @@ func TestAuthGL(t *testing.T) { binary.BigEndian.PutUint32(data, tval) } assert.Nil(t, ioutil.WriteFile(glFilePrefix+"test", data, 0o644)) - r, _ := http.NewRequest("GET", "http://localhost/", nil) + r, _ := http.NewRequest(http.MethodGet, "http://localhost/", nil) r.AddCookie(&http.Cookie{Name: glCookieName, Value: "test"}) assert.True(t, glProcessCookie(r)) GLMode = false diff --git a/internal/home/control.go b/internal/home/control.go index 01bf00ce..7caac5e6 100644 --- a/internal/home/control.go +++ b/internal/home/control.go @@ -43,7 +43,7 @@ func addDNSAddress(dnsAddresses *[]string, addr string) { *dnsAddresses = append(*dnsAddresses, addr) } -func handleStatus(w http.ResponseWriter, r *http.Request) { +func handleStatus(w http.ResponseWriter, _ *http.Request) { c := dnsforward.FilteringConfig{} if Context.dnsServer != nil { Context.dnsServer.WriteDiskConfig(&c) @@ -140,7 +140,7 @@ func ensure(method string, handler func(http.ResponseWriter, *http.Request)) fun return } - if method == "POST" || method == "PUT" || method == "DELETE" { + if method == http.MethodPost || method == http.MethodPut || method == http.MethodDelete { Context.controlLock.Lock() defer Context.controlLock.Unlock() } @@ -150,11 +150,11 @@ func ensure(method string, handler func(http.ResponseWriter, *http.Request)) fun } func ensurePOST(handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) { - return ensure("POST", handler) + return ensure(http.MethodPost, handler) } func ensureGET(handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) { - return ensure("GET", handler) + return ensure(http.MethodGet, handler) } // Bridge between http.Handler object and Go function diff --git a/openapi/CHANGELOG.md b/openapi/CHANGELOG.md index 133664b1..225d2ead 100644 --- a/openapi/CHANGELOG.md +++ b/openapi/CHANGELOG.md @@ -62,6 +62,8 @@ The old fields will be removed in v0.106.0. +As well as other documentation fixes. + ## v0.103: API changes ### API: replace settings in GET /control/dns_info & POST /control/dns_config diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index 751eb073..fe0c8cb7 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -608,7 +608,7 @@ 'application/json': 'schema': 'type': 'object' - 'parameters': + 'properties': 'enabled': 'type': 'boolean' 'examples': @@ -664,7 +664,7 @@ 'application/json': 'schema': 'type': 'object' - 'parameters': + 'properties': 'enable': 'type': 'boolean' 'sensitivity': @@ -705,7 +705,7 @@ 'application/json': 'schema': 'type': 'object' - 'parameters': + 'properties': 'enabled': 'type': 'boolean' 'examples': @@ -789,6 +789,39 @@ 'application/json': 'schema': '$ref': '#/components/schemas/ClientsFindResponse' + '/access/list': + 'get': + 'operationId': 'accessList' + 'responses': + '200': + 'description': 'OK.' + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/AccessListResponse' + 'summary': 'List (dis)allowed clients, blocked hosts, etc.' + 'tags': + - 'clients' + '/access/set': + 'post': + 'operationId': 'accessSet' + 'requestBody': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/AccessSetRequest' + 'required': true + 'responses': + '200': + 'description': 'OK.' + '400': + 'description': > + Failed to parse JSON or cannot save the list. + '500': + 'description': 'Internal error.' + 'summary': 'Set (dis)allowed clients, blocked hosts, etc.' + 'tags': + - 'clients' '/blocked_services/list': 'get': 'tags': @@ -1124,31 +1157,35 @@ 'type': 'object' 'description': 'AdGuard Home server status and configuration' 'required': - - 'dns_address' + - 'dns_addresses' - 'dns_port' + - 'http_port' - 'protection_enabled' - - 'querylog_enabled' - 'running' - - 'bootstrap_dns' - - 'upstream_dns' - 'version' - 'language' 'properties': - 'dns_address': - 'type': 'string' - 'example': '127.0.0.1' + 'dns_addresses': + 'example': ['127.0.0.1'] + 'items': + 'type': 'string' + 'type': 'array' 'dns_port': 'type': 'integer' 'format': 'uint16' 'example': 53 'minimum': 1 'maximum': 65535 + 'http_port': + 'type': 'integer' + 'format': 'uint16' + 'example': 80 + 'minimum': 1 + 'maximum': 65535 'protection_enabled': 'type': 'boolean' 'dhcp_available': 'type': 'boolean' - 'querylog_enabled': - 'type': 'boolean' 'running': 'type': 'boolean' 'version': @@ -2077,6 +2114,29 @@ 'description': 'Response to clients find operation' 'items': '$ref': '#/components/schemas/ClientsFindEntry' + 'AccessListResponse': + '$ref': '#/components/schemas/AccessList' + 'AccessSetRequest': + '$ref': '#/components/schemas/AccessList' + 'AccessList': + 'description': 'Client and host access list' + 'properties': + 'allowed_clients': + 'description': 'Allowlist of clients.' + 'items': + 'type': 'string' + 'type': 'array' + 'disallowed_clients': + 'description': 'Blocklist of clients.' + 'items': + 'type': 'string' + 'type': 'array' + 'blocked_hosts': + 'description': 'Blocklist of hosts.' + 'items': + 'type': 'string' + 'type': 'array' + 'type': 'object' 'ClientsFindEntry': 'type': 'object' 'additionalProperties': @@ -2249,7 +2309,6 @@ 'status': 'type': 'string' 'default': '' - 'example': '' 'can_autofix': 'type': 'boolean' 'example': false @@ -2274,7 +2333,6 @@ 'error': 'type': 'string' 'default': '' - 'example': '' 'description': 'Error text. Set if static=error' 'InitialConfigurationBeta': 'type': 'object' From f2c6e1c68210135a21f03d4f9f678ff9897bac6d Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Thu, 14 Jan 2021 13:48:52 +0300 Subject: [PATCH 046/141] Pull request: openapi: fix more docs issues Merge in DNS/adguard-home from fix-openapi to master Squashed commit of the following: commit bd95a502666372443b937cbcb690e307cd943342 Author: Ainar Garipov Date: Thu Jan 14 13:23:37 2021 +0300 openapi: fix more docs issues --- Makefile | 2 ++ openapi/openapi.yaml | 31 +++++++++++++++---------------- scripts/hooks/pre-commit | 5 +++++ 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/Makefile b/Makefile index 246a7c55..d116d4e4 100644 --- a/Makefile +++ b/Makefile @@ -80,6 +80,8 @@ go-lint: ; $(ENV) "$(SHELL)" ./scripts/make/go-lint.sh go-test: ; $(ENV) "$(SHELL)" ./scripts/make/go-test.sh go-tools: ; $(ENV) "$(SHELL)" ./scripts/make/go-tools.sh +openapi-lint: ; cd ./openapi/ && $(YARN) test + # TODO(a.garipov): Remove the legacy targets once the build # infrastructure stops using them. dependencies: diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index fe0c8cb7..dbad4e50 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -1283,7 +1283,7 @@ 'UpstreamsConfigResponse': 'type': 'object' 'description': 'Upstreams configuration response' - 'additionalProperties': + 'additionalProperties': 'type': 'string' 'Filter': 'type': 'object' @@ -1514,6 +1514,8 @@ 'properties': 'domain_or_ip': 'type': 'integer' + 'additionalProperties': + 'type': 'integer' 'StatsConfig': 'type': 'object' 'description': 'Statistics configuration' @@ -1967,7 +1969,7 @@ 'AddressInfoBeta': 'type': 'object' 'description': 'Port information' - 'required': + 'required': - 'ip' - 'port' 'properties': @@ -1985,7 +1987,7 @@ 'AddressInfo': 'type': 'object' 'description': 'Port information' - 'required': + 'required': - 'ip' - 'port' 'properties': @@ -1999,7 +2001,7 @@ 'AddressesInfo': 'type': 'object' 'description': 'AdGuard Home addresses configuration' - 'required': + 'required': - 'dns_port' - 'web_port' - 'interfaces' @@ -2021,7 +2023,7 @@ 'AddressesInfoBeta': 'type': 'object' 'description': 'AdGuard Home addresses configuration' - 'required': + 'required': - 'dns_port' - 'web_port' - 'interfaces' @@ -2176,9 +2178,7 @@ 'items': 'type': 'string' 'whois_info': - 'type': 'array' - 'items': - '$ref': '#/components/schemas/WhoisInfo' + '$ref': '#/components/schemas/WhoisInfo' 'disallowed': 'type': 'boolean' 'description': > @@ -2193,9 +2193,8 @@ 'WhoisInfo': 'type': 'object' - 'properties': - 'key': - 'type': 'string' + 'additionalProperties': + 'type': 'string' 'Clients': 'type': 'object' @@ -2289,7 +2288,7 @@ 'example': false 'CheckConfigResponse': 'type': 'object' - 'required': + 'required': - 'dns' - 'web' - 'static_ip' @@ -2302,7 +2301,7 @@ '$ref': '#/components/schemas/CheckConfigStaticIpInfo' 'CheckConfigResponseInfo': 'type': 'object' - 'required': + 'required': - 'status' - 'can_autofix' 'properties': @@ -2315,7 +2314,7 @@ 'CheckConfigStaticIpInfoStatic': 'type': 'string' 'example': 'no' - 'enum': + 'enum': - 'yes' - 'no' - 'error' @@ -2338,7 +2337,7 @@ 'type': 'object' 'description': > AdGuard Home initial configuration for the first-install wizard. - 'required': + 'required': - 'dns' - 'web' - 'username' @@ -2360,7 +2359,7 @@ 'type': 'object' 'description': > AdGuard Home initial configuration for the first-install wizard. - 'required': + 'required': - 'dns' - 'web' - 'username' diff --git a/scripts/hooks/pre-commit b/scripts/hooks/pre-commit index 278d7d3e..3aa9bd27 100755 --- a/scripts/hooks/pre-commit +++ b/scripts/hooks/pre-commit @@ -11,3 +11,8 @@ if [ "$(git diff --cached --name-only -- '*.go' 'go.mod')" ] then make go-lint go-test fi + +if [ "$(git diff --cached --name-only -- './openapi/openapi.yaml')" ] +then + make openapi-lint +fi From 56cb8a4dde9a7690d336d84a5c249debf5625ef4 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Thu, 14 Jan 2021 15:30:39 +0300 Subject: [PATCH 047/141] Pull request: dnsforward: add dnsrewrite tests Merge in DNS/adguard-home from 2102-dnsrewrite-tests to master Updates #2102. Squashed commit of the following: commit 894ff4baf8378d6e3386e09c416d55241a01f79e Author: Ainar Garipov Date: Thu Jan 14 14:49:28 2021 +0300 dnsforward: add dnsrewrite tests --- internal/dnsfilter/dnsrewrite.go | 4 +- internal/dnsfilter/safebrowsing.go | 4 +- internal/dnsforward/dnsrewrite_test.go | 172 +++++++++++++++++++++++++ 3 files changed, 176 insertions(+), 4 deletions(-) create mode 100644 internal/dnsforward/dnsrewrite_test.go diff --git a/internal/dnsfilter/dnsrewrite.go b/internal/dnsfilter/dnsrewrite.go index 15e4e211..757f742c 100644 --- a/internal/dnsfilter/dnsrewrite.go +++ b/internal/dnsfilter/dnsrewrite.go @@ -33,7 +33,7 @@ func (d *DNSFilter) processDNSRewrites(dnsr []*rules.NetworkRule) (res Result) { if dr.NewCNAME != "" { // NewCNAME rules have a higher priority than // the other rules. - rules := []*ResultRule{{ + rules = []*ResultRule{{ FilterListID: int64(nr.GetFilterListID()), Text: nr.RuleText, }} @@ -56,7 +56,7 @@ func (d *DNSFilter) processDNSRewrites(dnsr []*rules.NetworkRule) (res Result) { default: // RcodeRefused and other such codes have higher // priority. Return immediately. - rules := []*ResultRule{{ + rules = []*ResultRule{{ FilterListID: int64(nr.GetFilterListID()), Text: nr.RuleText, }} diff --git a/internal/dnsfilter/safebrowsing.go b/internal/dnsfilter/safebrowsing.go index 8f4c8f30..87cd2607 100644 --- a/internal/dnsfilter/safebrowsing.go +++ b/internal/dnsfilter/safebrowsing.go @@ -46,12 +46,12 @@ func (d *DNSFilter) initSecurityServices() error { d.parentalUpstream, err = upstream.AddressToUpstream(d.parentalServer, opts) if err != nil { - return err + return fmt.Errorf("converting parental server: %w", err) } d.safeBrowsingUpstream, err = upstream.AddressToUpstream(d.safeBrowsingServer, opts) if err != nil { - return err + return fmt.Errorf("converting safe browsing server: %w", err) } return nil diff --git a/internal/dnsforward/dnsrewrite_test.go b/internal/dnsforward/dnsrewrite_test.go new file mode 100644 index 00000000..b3f5ecf3 --- /dev/null +++ b/internal/dnsforward/dnsrewrite_test.go @@ -0,0 +1,172 @@ +package dnsforward + +import ( + "net" + "testing" + + "github.com/AdguardTeam/AdGuardHome/internal/dnsfilter" + "github.com/AdguardTeam/dnsproxy/proxy" + "github.com/AdguardTeam/urlfilter/rules" + "github.com/miekg/dns" + "github.com/stretchr/testify/assert" +) + +func TestServer_FilterDNSRewrite(t *testing.T) { + // Helper data. + ip4 := net.IP{127, 0, 0, 1} + ip6 := net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1} + mx := &rules.DNSMX{ + Exchange: "mail.example.com", + Preference: 32, + } + svcb := &rules.DNSSVCB{ + Params: map[string]string{"alpn": "h3"}, + Target: "example.com", + Priority: 32, + } + const domain = "example.com" + + // Helper functions and entities. + srv := &Server{} + makeQ := func(qtype rules.RRType) (req *dns.Msg) { + return &dns.Msg{ + Question: []dns.Question{{ + Qtype: qtype, + }}, + } + } + makeRes := func(rcode rules.RCode, rr rules.RRType, v rules.RRValue) (res dnsfilter.Result) { + resp := dnsfilter.DNSRewriteResultResponse{ + rr: []rules.RRValue{v}, + } + return dnsfilter.Result{ + DNSRewriteResult: &dnsfilter.DNSRewriteResult{ + RCode: rcode, + Response: resp, + }, + } + } + + // Tests. + t.Run("nxdomain", func(t *testing.T) { + req := makeQ(dns.TypeA) + res := makeRes(dns.RcodeNameError, 0, nil) + d := &proxy.DNSContext{} + + err := srv.filterDNSRewrite(req, res, d) + assert.Nil(t, err) + assert.Equal(t, dns.RcodeNameError, d.Res.Rcode) + }) + + t.Run("noerror_empty", func(t *testing.T) { + req := makeQ(dns.TypeA) + res := makeRes(dns.RcodeSuccess, 0, nil) + d := &proxy.DNSContext{} + + err := srv.filterDNSRewrite(req, res, d) + assert.Nil(t, err) + assert.Equal(t, dns.RcodeSuccess, d.Res.Rcode) + assert.Empty(t, d.Res.Answer) + }) + + t.Run("noerror_a", func(t *testing.T) { + req := makeQ(dns.TypeA) + res := makeRes(dns.RcodeSuccess, dns.TypeA, ip4) + d := &proxy.DNSContext{} + + err := srv.filterDNSRewrite(req, res, d) + assert.Nil(t, err) + assert.Equal(t, dns.RcodeSuccess, d.Res.Rcode) + if assert.Len(t, d.Res.Answer, 1) { + assert.Equal(t, ip4, d.Res.Answer[0].(*dns.A).A) + } + }) + + t.Run("noerror_aaaa", func(t *testing.T) { + req := makeQ(dns.TypeAAAA) + res := makeRes(dns.RcodeSuccess, dns.TypeAAAA, ip6) + d := &proxy.DNSContext{} + + err := srv.filterDNSRewrite(req, res, d) + assert.Nil(t, err) + assert.Equal(t, dns.RcodeSuccess, d.Res.Rcode) + if assert.Len(t, d.Res.Answer, 1) { + assert.Equal(t, ip6, d.Res.Answer[0].(*dns.AAAA).AAAA) + } + }) + + t.Run("noerror_ptr", func(t *testing.T) { + req := makeQ(dns.TypePTR) + res := makeRes(dns.RcodeSuccess, dns.TypePTR, domain) + d := &proxy.DNSContext{} + + err := srv.filterDNSRewrite(req, res, d) + assert.Nil(t, err) + assert.Equal(t, dns.RcodeSuccess, d.Res.Rcode) + if assert.Len(t, d.Res.Answer, 1) { + assert.Equal(t, domain, d.Res.Answer[0].(*dns.PTR).Ptr) + } + }) + + t.Run("noerror_txt", func(t *testing.T) { + req := makeQ(dns.TypeTXT) + res := makeRes(dns.RcodeSuccess, dns.TypeTXT, domain) + d := &proxy.DNSContext{} + + err := srv.filterDNSRewrite(req, res, d) + assert.Nil(t, err) + assert.Equal(t, dns.RcodeSuccess, d.Res.Rcode) + if assert.Len(t, d.Res.Answer, 1) { + assert.Equal(t, []string{domain}, d.Res.Answer[0].(*dns.TXT).Txt) + } + }) + + t.Run("noerror_mx", func(t *testing.T) { + req := makeQ(dns.TypeMX) + res := makeRes(dns.RcodeSuccess, dns.TypeMX, mx) + d := &proxy.DNSContext{} + + err := srv.filterDNSRewrite(req, res, d) + assert.Nil(t, err) + assert.Equal(t, dns.RcodeSuccess, d.Res.Rcode) + if assert.Len(t, d.Res.Answer, 1) { + ans := d.Res.Answer[0].(*dns.MX) + assert.Equal(t, mx.Exchange, ans.Mx) + assert.Equal(t, mx.Preference, ans.Preference) + } + }) + + t.Run("noerror_svcb", func(t *testing.T) { + req := makeQ(dns.TypeSVCB) + res := makeRes(dns.RcodeSuccess, dns.TypeSVCB, svcb) + d := &proxy.DNSContext{} + + err := srv.filterDNSRewrite(req, res, d) + assert.Nil(t, err) + assert.Equal(t, dns.RcodeSuccess, d.Res.Rcode) + if assert.Len(t, d.Res.Answer, 1) { + ans := d.Res.Answer[0].(*dns.SVCB) + assert.Equal(t, dns.SVCB_ALPN, ans.Value[0].Key()) + assert.Equal(t, svcb.Params["alpn"], ans.Value[0].String()) + assert.Equal(t, svcb.Target, ans.Target) + assert.Equal(t, svcb.Priority, ans.Priority) + } + }) + + t.Run("noerror_https", func(t *testing.T) { + req := makeQ(dns.TypeHTTPS) + res := makeRes(dns.RcodeSuccess, dns.TypeHTTPS, svcb) + d := &proxy.DNSContext{} + + err := srv.filterDNSRewrite(req, res, d) + assert.Nil(t, err) + assert.Equal(t, dns.RcodeSuccess, d.Res.Rcode) + if assert.Len(t, d.Res.Answer, 1) { + ans := d.Res.Answer[0].(*dns.HTTPS) + assert.Equal(t, dns.SVCB_ALPN, ans.Value[0].Key()) + assert.Equal(t, svcb.Params["alpn"], ans.Value[0].String()) + assert.Equal(t, svcb.Target, ans.Target) + assert.Equal(t, svcb.Priority, ans.Priority) + } + }) +} From 679bbcdc26775bc3d961cbeef553dce7aa9c091b Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Fri, 15 Jan 2021 20:30:48 +0300 Subject: [PATCH 048/141] Pull request: home: don't miss blocked clients in client search api Merge in DNS/adguard-home from 2428-blocked-runtime-fix to master Updates #2428. Squashed commit of the following: commit 8aaa3e22a894f0335ced93339655771989846c94 Author: Ainar Garipov Date: Fri Jan 15 16:32:53 2021 +0300 home: don't miss blocked clients in client search api --- internal/home/clientshttp.go | 48 ++++++++++++++++++++++++++------- internal/home/control_test.go | 4 +-- internal/home/controlinstall.go | 4 +-- scripts/make/go-deps.sh | 10 ++++++- scripts/make/go-tools.sh | 26 ++++++++++++++++-- 5 files changed, 75 insertions(+), 17 deletions(-) diff --git a/internal/home/clientshttp.go b/internal/home/clientshttp.go index d8cc3ee3..51a193f9 100644 --- a/internal/home/clientshttp.go +++ b/internal/home/clientshttp.go @@ -233,24 +233,22 @@ func (clients *clientsContainer) handleFindClient(w http.ResponseWriter, r *http if len(ip) == 0 { break } + el := map[string]interface{}{} c, ok := clients.Find(ip) + var cj clientJSON if !ok { - ch, ok := clients.FindAutoClient(ip) - if !ok { - continue // a client with this IP isn't found + var found bool + cj, found = clients.findTemporary(ip) + if !found { + continue } - cj := clientHostToJSON(ip, ch) - - cj.Disallowed, cj.DisallowedRule = clients.dnsServer.IsBlockedIP(ip) - el[ip] = cj } else { - cj := clientToJSON(&c) - + cj = clientToJSON(&c) cj.Disallowed, cj.DisallowedRule = clients.dnsServer.IsBlockedIP(ip) - el[ip] = cj } + el[ip] = cj data = append(data, el) } @@ -267,6 +265,36 @@ func (clients *clientsContainer) handleFindClient(w http.ResponseWriter, r *http } } +// findTemporary looks up the IP in temporary storages, like autohosts or +// blocklists. +func (clients *clientsContainer) findTemporary(ip string) (cj clientJSON, found bool) { + ch, ok := clients.FindAutoClient(ip) + if !ok { + // It is still possible that the IP used to be in the runtime + // clients list, but then the server was reloaded. So, check + // the DNS server's blocked IP list. + // + // See https://github.com/AdguardTeam/AdGuardHome/issues/2428. + disallowed, rule := clients.dnsServer.IsBlockedIP(ip) + if rule == "" { + return clientJSON{}, false + } + + cj = clientJSON{ + IDs: []string{ip}, + Disallowed: disallowed, + DisallowedRule: rule, + } + + return cj, true + } + + cj = clientHostToJSON(ip, ch) + cj.Disallowed, cj.DisallowedRule = clients.dnsServer.IsBlockedIP(ip) + + return cj, true +} + // RegisterClientsHandlers registers HTTP handlers func (clients *clientsContainer) registerWebHandlers() { httpRegister("GET", "/control/clients", clients.handleGetClients) diff --git a/internal/home/control_test.go b/internal/home/control_test.go index b047b65a..5b08c2bd 100644 --- a/internal/home/control_test.go +++ b/internal/home/control_test.go @@ -68,8 +68,8 @@ kXS9jgARhhiWXJrk data.KeyType == "RSA" && data.Subject == "CN=AdGuard Home,O=AdGuard Ltd" && data.Issuer == "CN=AdGuard Home,O=AdGuard Ltd" && - data.NotBefore == notBefore && - data.NotAfter == notAfter && + data.NotBefore.Equal(notBefore) && + data.NotAfter.Equal(notAfter) && // data.DNSNames[0] == && data.ValidPair) { t.Fatalf("valid cert & priv key: validateCertificates(): %v", data) diff --git a/internal/home/controlinstall.go b/internal/home/controlinstall.go index 7d67d140..2abfc4ba 100644 --- a/internal/home/controlinstall.go +++ b/internal/home/controlinstall.go @@ -109,7 +109,7 @@ func (web *Web) handleInstallCheckConfig(w http.ResponseWriter, r *http.Request) if reqData.Web.Port != 0 && reqData.Web.Port != config.BindPort && reqData.Web.Port != config.BetaBindPort { err = util.CheckPortAvailable(reqData.Web.IP, reqData.Web.Port) if err != nil { - respData.Web.Status = fmt.Sprintf("%v", err) + respData.Web.Status = err.Error() } } @@ -137,7 +137,7 @@ func (web *Web) handleInstallCheckConfig(w http.ResponseWriter, r *http.Request) } if err != nil { - respData.DNS.Status = fmt.Sprintf("%v", err) + respData.DNS.Status = err.Error() } else if reqData.DNS.IP != "0.0.0.0" { respData.StaticIP = handleStaticIP(reqData.DNS.IP, reqData.SetStaticIP) } diff --git a/scripts/make/go-deps.sh b/scripts/make/go-deps.sh index f9bfb365..f3c8a77b 100644 --- a/scripts/make/go-deps.sh +++ b/scripts/make/go-deps.sh @@ -27,5 +27,13 @@ go="${GO:-go}" # those aren't set. "$go" mod download $x_flags -env GOBIN="${PWD}/bin" "$go" install $v_flags $x_flags\ +# Reset GOARCH and GOOS to make sure we install the tools for the native +# architecture even when we're cross-compiling the main binary, and also +# to prevent the "cannot install cross-compiled binaries when GOBIN is +# set" error. +env\ + GOARCH=""\ + GOOS=""\ + GOBIN="${PWD}/bin"\ + "$go" install $v_flags $x_flags\ github.com/gobuffalo/packr/packr diff --git a/scripts/make/go-tools.sh b/scripts/make/go-tools.sh index 6c96e1cb..32e30c08 100644 --- a/scripts/make/go-tools.sh +++ b/scripts/make/go-tools.sh @@ -2,16 +2,38 @@ verbose="${VERBOSE:-0}" -if [ "$verbose" -gt '0' ] +if [ "$verbose" -gt '1' ] then set -x + readonly v_flags='-v' + readonly x_flags='-x' +elif [ "$verbose" -gt '0' ] +then + set -x + readonly v_flags='-v' + readonly x_flags='' +else + set +x + readonly v_flags='' + readonly x_flags='' fi set -e -f -u +go="${GO:-go}" + # TODO(a.garipov): Add goconst? -env GOBIN="${PWD}/bin" "$GO" install --modfile=./internal/tools/go.mod\ +# Reset GOARCH and GOOS to make sure we install the tools for the native +# architecture even when we're cross-compiling the main binary, and also +# to prevent the "cannot install cross-compiled binaries when GOBIN is +# set" error. +env\ + GOARCH=""\ + GOOS=""\ + GOBIN="${PWD}/bin"\ + "$go" install --modfile=./internal/tools/go.mod\ + $v_flags $x_flags\ github.com/fzipp/gocyclo/cmd/gocyclo\ github.com/golangci/misspell/cmd/misspell\ github.com/gordonklaus/ineffassign\ From 9f75725dfa0a6f6208f8e7e156d49642a4248f95 Mon Sep 17 00:00:00 2001 From: Ildar Kamalov Date: Mon, 18 Jan 2021 15:09:04 +0300 Subject: [PATCH 049/141] Merge: client: fix mobile layout for install page Squashed commit of the following: commit 5e620f2d8576b08ebfee08e9781cd4927c4dcf2a Merge: d82d5a902 679bbcdc2 Author: Ildar Kamalov Date: Mon Jan 18 14:57:00 2021 +0300 Merge branch 'master' into 2554-mobile-install commit d82d5a9028be0be72e612fc4c375d2be81c6c8c3 Author: Ildar Kamalov Date: Mon Jan 18 14:09:25 2021 +0300 client: fix mobile layout for install page --- client2/src/assets/img/background_min.png | Bin 7187 -> 0 bytes client2/src/assets/img/install.png | Bin 0 -> 24402 bytes .../components/Install/Install.module.pcss | 14 -- client2/src/components/Install/Install.tsx | 9 +- .../AdminInterface/AdminInterface.module.pcss | 17 -- .../AdminInterface/AdminInterface.tsx | 61 ++++--- .../Install/components/Auth/Auth.tsx | 8 +- .../ConfigureDevices.module.pcss | 4 - .../ConfigureDevices/ConfigureDevices.tsx | 90 +++++++---- .../DnsServer/DnsServer.module.pcss | 12 -- .../components/DnsServer/DnsServer.tsx | 57 ++++--- .../StepButtons/StepButtons.module.pcss | 8 - .../components/StepButtons/StepButtons.tsx | 9 +- .../components/Stepper/Stepper.module.pcss | 67 +++++++- .../Install/components/Stepper/Stepper.tsx | 31 ++-- .../components/Welcome/Welcome.module.pcss | 15 -- .../Install/components/Welcome/Welcome.tsx | 32 ++-- .../common/controls/Radio/Radio.module.pcss | 6 +- .../common/controls/Radio/Radio.tsx | 4 +- .../components/common/formating/danger.tsx | 2 +- client2/src/lib/ant/Step.pcss | 65 -------- client2/src/lib/ant/ant-overrides.less | 8 +- client2/src/lib/ant/index.ts | 1 - client2/src/lib/theme/Icons/index.tsx | 40 ++--- client2/src/lib/theme/Install.module.pcss | 153 ++++++++++++++++++ client2/src/lib/theme/Link.module.pcss | 14 ++ client2/src/lib/theme/Text.module.pcss | 29 ++++ client2/src/lib/theme/Typography.module.pcss | 36 ----- client2/src/lib/theme/index.ts | 8 +- client2/src/localization/locales/en.json | 8 +- client2/src/main.pcss | 29 ++-- 31 files changed, 462 insertions(+), 375 deletions(-) delete mode 100644 client2/src/assets/img/background_min.png create mode 100644 client2/src/assets/img/install.png delete mode 100644 client2/src/components/Install/Install.module.pcss delete mode 100644 client2/src/components/Install/components/AdminInterface/AdminInterface.module.pcss delete mode 100644 client2/src/components/Install/components/ConfigureDevices/ConfigureDevices.module.pcss delete mode 100644 client2/src/components/Install/components/DnsServer/DnsServer.module.pcss delete mode 100644 client2/src/components/Install/components/StepButtons/StepButtons.module.pcss delete mode 100644 client2/src/components/Install/components/Welcome/Welcome.module.pcss delete mode 100644 client2/src/lib/ant/Step.pcss create mode 100644 client2/src/lib/theme/Install.module.pcss create mode 100644 client2/src/lib/theme/Link.module.pcss create mode 100644 client2/src/lib/theme/Text.module.pcss delete mode 100644 client2/src/lib/theme/Typography.module.pcss diff --git a/client2/src/assets/img/background_min.png b/client2/src/assets/img/background_min.png deleted file mode 100644 index 528fe2d19087348caa6a65a5448308bd81277e49..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7187 zcmb_hXH-*7x1JCJ5>OO~L_yG(n$UeIqEe&@p@)Pj5D~wId)K$_{5UhS_nv1z`^=ej_C6=X$l&Hl_<1-0;G~YW z1{MG&8~_ZZS#+F*N*$3Zii76KkZ3X`u z$3{aXPZL1(R_~0dD1(4359eECVK9+TYsJ?yfdqVXmE}nU=v-W=LT4;OL8rwGCAr1L zLRZKPGLi1^)jc#X>W{+-;Po;Soo6Bq#?ozH>T82BhkLpP5U>*A<`M%)m9d9B6!9KG z56DF9p)F!EQ%qiLW*As3}KQGXqFuT;z%GLDiq8n$!I#EW7FBNm`hF?*uc$N zUdROut(atlm$QN^l7NH!Dd*Rx~%|r*e z@djqFA9+f$Bd%ieDfhw7kb5%HZYk0I)wR6K;BvLk;J!INM#NAZ_7|RFw2MJ7vR*Vk z6Zdf-_dMS)HCR%63Z94_+=ptL{PS}^Pj9t+?ZfQ;&NEemdGAnjh{n0g)A?=~xv(tN z)OuPtYRppgwJ_JYi&kmt;a<-ns9OXc?tgg1Y#S`rT{`8J$Yh)a=&&@ z7YjQbtQY8YWRD?!m56_fCSA0Le{$bNSI#6k>mXF&+=K5JevFKwgdC1dZ+A^y|0K4a zEf-$qla3sLnU-$Z!_)7tx5c%J|3On1HkG@;(5*<+A90v8!NIqyh3iGu^5tpo+=m;C z=9-$Bs>(6zCP`a(a|?KWie0IIyz2Z7Vv^W=AGG6D+y}^;R0SilK{;fdWISI58?hWLL1KxfE40O1Np2b)-Nh3ydKr$ z{q^Nlf~iswlWrz~6RuQ5%zV6Nhq)_m%-_K7U45BGpkUNs<|KFc7?y0> z=%*0M=_;(a)kw-}#0la&OW$*xKoD5eU@OjSh}!Eo z(Ex}_>VA-dRyHW{ms=qA-1(bkv{w0^)?)$z`>jh-J=V=3@66R;pvcQ$dQg961}E;0 z2Bc=rjUURmT_luq1CVBMHJfX(2{<2DVIlg{by)I*`I^f|RU|2dMV=8Fy~(w4FkU(u zjjg%#$TFjkuVIA)#WCBx&leszTghcRro2o<)mGL#Kru}utxYJAcbvu0_meNrF2<20 zI_DwT%Esf%Uu-?O@xS*E3il@!u3{k*l%D8-rRmD@>|46E=mEE#4Xqdf1mG(}=Nwo+ zEfkIMKZ-1Lk9rDuIDXo6w!*2?p#W_H1(hvDmcwye!`E-VYy{aA=u7zfm=ljWbhaw>FD@o$VsJy`l zc?c&1p0^Z*bTr93#P5d+eWrqJ965gIXW6HsLR4M@IS}$No7Hz#$Qd!s`_WiGA`oI! zh>l09v++MxFXh%PVPqp&`DgREDm&>u0273g?c$!^e0u`u4eP2vU6uy9!4z61ZyZ*h znTc9JydDI#9>0FMv-zhTIJM5d(4MmxpBroS@Bb zLD^;A86-%-L%(~ovZxAV#6h7{IuR>-ek9Q6F-JpytTd<)?!4xD334yJx1NcKRQU!x zFiIHz5(xRGokORa5(p{M9MwJfz+wyv_U{qot`imc!VM50PPV9L$H;;K?8iafO&}8i zJcDj-#*kzDn2K&HAY=qWRC$`>>6lDR)U$p5<(&NBBpYcd#GN2Bt9(mTW?BYNeV~h!!(@t$Zc9QI z0AjmhC-XnaQv7Z9=#%zj#UfO5kyE=hATiHUgJpxQYV1$3ky_6WMzuV&`T_x_WZ3|@ zqMXeiNwK4NP<4CQU%rU9$-jF+n#8`j?A+>nI`re}zZilC3AVk8+RncZU59kyi9Pmy z+34o5^?;a7sCco0@3RbxHq#cTpsCnrjNpsAdXGrKnM43$5zjj@selP=2gq!fUt9B@ zWd+h*6E$((&jl}ZwoX2(!}xHOEDLq03Ic-LrXVUYeC2(K zty3;r7ALzG1Y}yR*oK?gw&p$KLl*jG^@MEI((|aKMYGvjqoKt>g__x#zW{Ji>m8@9 zWp!-7<@0LchooR~LK8&{-@C=0Bu8Is21aS`d7P?}uNfcK1hprhoX=( zK@um~sOFGrY-q~|7C3c%ddS#d+CSMR^)f#F*cZVK_Ar3t8{ab&o{ectdcGQ95?-*F zT>HAy^eoh;1q)KHzJIKbnSSZ=#bv;WQ)ce7*C7J^PEG_GI)U-^kyLMWF5_K)OB0F*4zR{h=FbC9%mh&>R%=O#Ag5?J`2^P>re(3 zcZ%fwkyr>Rw`$BwzoSWVZaTmFd~}{IFp7yB{k1|_JYR3QoEGgJN|$i=c%>1mYiS+c zF=gov>Z}0Q0{g0C{bcwr+>~{HopvTrb&i(zld~S!7$(`ag|#``foy|r29xv7sY_P+ zf9&A^CxVcWsjoQmOvj}0LCPi!^jVxiR}5%HvMgy3!)iYPqxW@x;S%II3;xWn#XpLs zaRVGN`zkK|LBg8xN=ap>$qobQs{hgEQtgbKRy?MFrM|oc$>Zbb70I{#VeG=B{3)n0 zxIjdOMv7vBF5C%}a62-g1aj%z7BP6E#x%A#SS)b&@HF)1#_r03oN}?jpEExC8Ej41 z*AF>?G78rqipLTmpwT!dY9yTAN93&Qa@v8_l$<@zEwU{A9EHc#RyktsmooB^TFDhH z7rX~60I4-Y#$|Djq7MP%(yt#i#qCjc&oNLHUU}4zyW{s6CYUR{L&7nia|B)CKt-mk zu7Q8kaxde8tM$q>2e=;SasNAq>!yzf)#7Kz{5oCo4AteVr}(70zp8L0RI&&yyUF2fT8sB#&eN%UPE!flop_yXymn@i#{ z-}-;svVyzcoJ5!%LvfJUHsh`eq~q60IS@-#20!=JtMqq$ zVqcq>1EOT3+qV1^nhR|KIgC%rDwXaOQ!i>ZEBR(I9=$Qt*B-TopfWTy3fjOUW%b zhX#T}p7gX{aL*+hNIT*vedBJB-trYXgo#$O5kyo%0ljnrEDTp_mq|iYt#gbm*bpR| zmi`6=Xdhcpfx=KrZ%d8#7wGCg~QUmZ44sr2N?H7Q?E^VqI&>6c1ptQvpB zgpFf};L%`hOh(o1i(wxNbUPwz{&})`=z9LHc4lxarby<_=LA>X;^|&jR#s|w#D)!j zK(L2hHlV%;ZRlwo;2z~O{#+ie#z19{#P7wUYsJZma4_*c^sqS{DldZr1Xa!QrdeBG z4WY|8Fws41krsJhL}Um|Fk5J4JU`+tqZ&a-5RcvMJi4Bsk-6efu(nz>)lQe5kqGCq zg3HC_cU$A=oFBfh<4+K+K&oA~cwd?=NWYD|sS}ZLad2 z+?g7A7XoP+r>YoyzTmZrQqpO zW$iZU97qYLlpXswseSCQsn7Sm_pf*a-CI~n)eh0q;%rT5Xfh*^{^#e+RexVIjB|-T z>N5W!`5d5T$tesBA*u&%;zD<#uCq4+g7{0y<1^%+G%`1!nwK1PQXYhc>NYAuK$!4e zPN&ZjhTZt{eDL>q2tgx;qG49^B2-tQE|-~fW5}bht-&d}&(EjX3IULb897=KCCOQJ z#GF+u3>bXCRz03>%SfGXA)W!$Hu2z5N+r9nSp9W+J=`XVl3Iii9!9GTW{CCm3s3K8 zir0r&2-Z%u(Szu;JJHQoiF48MCi~a4xpm)qJkkdPO{Iml>bwt}v19i2tU5hYpW%S5 zaE`l{vCQs3^F19wD^g__mC!OWU$Z~)+64S>5-$v&h#>w z47LoVON1?d`3HjmHzUOTkJujRyXnUX2z`EnOGLt;q#HP;Dc7lXp9q?u2OkAcMRS+U zK9d!(Q;uF$3;;@We4+3vN*FQugC1Ynn49Ef;t67(HZfEN&IF(#6#>=Q!rqj)qoMD) z5j2fETx>v#!#P=M7SlH*+2vW|lr9^Qqy)mIW5wJu=kolOK=h8mg zqwX~Ir|xeQHp278P-z4p9s4^@tl;StcJc!^1yn48zMJWZFZG7i9L`KX#om@v25ec| zof?93wJ0g!>b&fhQ+EJTpF^i+bwsebFq@Oz3=pmyu^`CFx1Y;^{kxjn(L6DGd3s1F zl@Kalt=M*UVRq|{j21nIn8SN%WXqzentR^7lW}JQiRj^9@@(YHSZ;5`exh*!Pz_I& z->Z`zGLm7}(KulIdKSzX(PrLoYsw>>;R{)M031{!(btA7WshDoz`((~tP*J_l zuE%TBVU$8QqgZ)Ztt>&xHJw6(MW;76LuPIon%{%~$q>rCPyI#qx1lw)uw4}R7PRZ} zY%KITmIJ8r|LcCF6xZ%KKcXDMUC+zXQ@C&mw8*KYs43wLtbQ$ZKMwLTC<{wOF0Gs8v32i%@*Pygsex^%c6KN zm^Z*XZ>C273iQrceqY@_hk?pm+%G4;|G>M2TLd!rb7fp08V&qz#d8qf8rOe-Q3EW$ zPVSS@fVWuQq4w_a3$77mlH_i|4GgEPGh=f>k-Dv0I;#l+cp|II%52}F!IV&z7=6BJ z^W?f#9t$a?+0yr_UIZF&-WR%g)j%o}4N7bB^%afgSxBgjxXIPuIzaE<)G)0()w~}j z{k~&3D$OH@K|0ZNnaqWS5#$#mdlK`_uAhonMxCWw zKi}TrE#_W@4El_&dEG9QrZToD%)ZdagY6*m7~c6aTJ#tSm!2=arn?^zACRM^g*CHo zgTlN~zt@8{w?V^cZ;NQY>dkedGz@}d)-z_{=&EpX?ifHTK@#IrCX5F*ddk+X*6%gr zf&zYx2D%JOa|IiJ54HBCbnG=Q4Z94fp3j5lv_`tyKv=f7jU|huI(pF0!*vkg@aOtx zb_AU-ewyeJDyL^UieMTf@0aBkr;mNavB78 z%AA|p5XSqb`J0T4Sq&aWm~~2u7Aw$3kVY4azkDW(KLX{ zC}xTPF4#%(tu;nJ;L<_9^3jagaRTtR?bXmep38%StpYItzaZmuIQ7S^{A26oPBO>M z6Id8YqSw5_)-3goHDK=Z8Qwi3dFPQCa-zuTmR%)%vtLE`L@#0_u&{}5PU1PiuLSep z)DH*dW+MnDLX$@ZJyt%HSw0%0sH{rqB8|Fr8HTw;7Mp*jq8{+AX z556O?WPIDz4!J*4peyP@-=O_j79LCTKnC+pn)MGN#4F0JRxS7U^1Aw>C>kW8hVFK6 zH09h*{asRW7&Cge#RCSCT69Y%l%;N`ZFacrHdjp67=SLWv*}_X0gY05G|v1(hE;nT zd9n!JyFz+zM-rjboXtV)ArB)A^M0G7nnitOcFVk&jZK|r3%g>ULcCZ83i&D8c)m8P zosT{y(KY!Cbuy+Z^K76l_;lV3ZSjjj8CtV4CTiND`D)*YQ0b)Afym#PjH?*QN->U; ze3vv7J2KUxB31glA1U5E9Z^mE1$HQvhh=SVqp9I9JnFY~y(4!x&$J_6sJ~V^3Eu?9UR7qzW18P# zHLd+8Xi`p?>Rj!-xOeIdUWRLnA#WopEp+n3?S~SIMH4kpR?(hNFI!t0Sv42?*G6}X z_(fJm_lZO&PuZ7X%<4to%^Eu*9-iwXb-!M4^F@iO>^0BIe_{*NKtJ{SomyI0le8X_ zS*tCD^4+J$_iuxlxQj0Z@8m`ceFFVcZzy^PV}yqxd5+7rMk=rGo`=7}yj#MR3u7Qf z=o@jFD>-P`p)>2uEB&;0yPx$wd`YL~ zIDKymGk{Ug8{?Kgi2TiV_q@9I^7IsmC_qQq6L_6e(C4QO1fqV;OHY!{a-KUJnv42j-Y1f~;!of2h6ysj8Cpyi@!%wd)=5W7~M?#F~ ze{r6OqA6r(i!C7Wuex2hp#&x>QfbGE#?w=^b!7L!vy4Er=(oPm?88lNkn)K$2LL{W zBbt`Mqa;Bv@#JM_?M{;AhLm6a0{Z9E^0kzTM&k4$NK2 z?@~;>-p2)eJgHAh!uUusUoq*=zGZ@M8GY(y9C#f_ipj9mEA4>4aw8d38;|zW%N>Xd zEC4)Y|055fBH6z;(W|i0FG<@Bgf}&Y^pZ@0lM{p)_p^YOhidEqjC3x<@sW&ajPxac z=iT=Kl-vlcPk$Dj*?r5Xm|y;OKr*MgPUs05tmnD!*dMTxTnc!nSio+&n>~b$q^ygw zM1YnTZg*j9B+2T89b*{Dc60e6Oclw*a8cWm?cgXt$!p!zY*qSCh>%YFgB;osgK9Qd z{o7i>77x4(v%zV1uLGEBd#=c0BN(LB*aAPZT)iMTs0|K*1DU3)I>4?Ioy8A1F)ybH z2a2Oi>&K<#|D~DvKD>Brcd>2UCkTT6zbyG5HWMcdvstjE=gUTqRXUmm8Wc6czy1pd Co2}LW diff --git a/client2/src/assets/img/install.png b/client2/src/assets/img/install.png new file mode 100644 index 0000000000000000000000000000000000000000..68dbd78045cbd659be366bf95eb5ecb0c85a2491 GIT binary patch literal 24402 zcmeEtWmHvBx9A2Gkw!r}R8YFR5di_|ZjlB7X=yA(5RmTfmhKV(>CQt)A6mNMt?f6) z`}y7*_x-s)?imi;XRf(suQ}&hd!4V!iZVEuWS9^H;mE#tt_nd|;t+Iu1_KR1{4sC% zAPDtD`K7uP_}V|YI)GRjm|7l0tT*%^h7fB%hZY7AtHU#!4PCR%{WEnv(+%B-f$7zS zs@lQn)tj3eFbW>l(z937b8fC~MrV+nGwWSTTWyGi(LWmllgQCOTZaTWtPCMmPZx>@5Nit+we2HIU}$J|{pRNCKlB$XtNRgaQ&aGi zzRA_o#bWRPD2&c+O#Inx>7VOr92uG2XzHIC1jG@mfacKb#_GU8+wdP4GQIYvvSt9l z5o?1pYeO^O0a@2QH3+!OtPRd>fCn(z(1&OqoNw-*1r&hdgR`r9`^R9YzGu3BdZn>< z20SL5^L1}Fl!uK_N=IRBnuI%!M5K5M{e{fL$N-kGsE z6;p4*)nYwE{Q-_5V=-EdB`t{`Styt!yn;*8K+#EVfu(yHHWx zkeLIfz)S&sKn<}r0-F@5hU}kQ0g?kFFO^pVrJ4t3M`kzS>C>xVO8*R)vewi$vrF$g=9y;_|*pm`>}^0{GiKwy;oDvj`lGKz0nzFI3b32LZ9$hvvaVAbjhUUL0r+BIHG4O8@6SwJ!bKJS#+0CIQz|X_1{PG2T zps)btBWE6#DF+1^vV1vh3G_Kgy$&u0;S{C{-zeQj_3m#JuI7~|pIg6a5O8j(x^B|necl-@ zh6{jyy)`Q$k3hk1S+M!ysFoLWyRymkZEkitD3Fb3|>D#v$&+2K%A4(1}P^xp% zsYHJ4hqy=% zD1{{3%+;^|xHVw_Xoa;8wR4_6!)+kfw1?)+`WABu{M>z@xw0aum}Jg(uvC>Z2Oz@; z=9`Vqmrp$-D$UK$Uhx2}7OJ?ETpzgM=}t`r1k@txn?GIwHToi2i9qX5F}1e&Xf~3yL1NTVNQwF4%Fua!Sl+_ zYZIpm6+rka|2nLeUY$4!rni2*Idsu?l*D#WWd|%RZ_{HDw-%$P($c%{B1FCAQkC>` z3D5JS{GJPmw8LcuM)Kl`v|R-!&m%h85vf~YSS+y*^&->NN#eP&4hFtq$f|wN)vbD~ z5|;LY9jmoZ-w(^>?Sgaz7F(tpp^Hz%(Cvo#0SyW&D%iys!@u-@hC8txQd{`nH~;F^ zCZ8gc?82fLH?=5U1Vs1yJ8Ej)SN5^*yxPBFYIL}-K#(&mj+dMas0iVwUWA_asm>QT z*KMq5P)9QnO@#C6ZT=fd4>2yU8uApB@@`h>M3X3fzSR;%7=I&vZAJvi$t!kNgIZ0@iPxdpuCr_3W} zz?INFiky7TI+cm0RbQQi2*zE(+I2~_`^^dG$PX?VOjcjB6e;VX%20?JeRcvdhe{hc zh-Rtyu2X@^_+|BtOXdf7jC5n>wmMQpMlkulrp)|aG(nidH5aDcq-!#AJXE~v09BM*}; z!19IyfL-o9`Z*Z*gVth{^)3vl5U`1=K;FK@@RZ_z;|^Ot?6^akmdDdK7XxqEtK9G( z$aO-rhx7pT`1RA7h~;_1B4U`ORbG*-KzSw8PgR$3kn*5Mc5`*K?Ll+0E0YdFj{dd^ z04Dy6W%66%lrGW2+^5XdHxC&AjAqe;uiQ#Dn9W-Klav7Yu@sEA+*oNPfYz?Y^s_9{ z*)@V34j{kFXT=KrS0ju+uf8Q(k;C4BM9SSr1%eic7JPL3+x1&vK_HibDKrgXW`Ffh z^$r4!1+E$}Fwv%@P>tx?WVc#d%1VZ4FLcHa7Bb&%#nDC3`Cw=-@a+XnTHx}JB2(CR ze_rI+vLIh9o;eN`OtOTwyU>f+E2+Wj;IF*3h|Q*~hM9-Q;!*xae1-d}#h+eFOEOEF zffRBwee!i=4cFnrn|&{Nr;iZSBK9H}(UHk=#Ps~N`H1M=2Yi0=C%V&LHhvx&`3_Ih z0)lRr0ts^2IlY?um@keStKOtQmp2>sH4C-bt;f+ls6J(GzHADg9Vj}Rh3_nT(s`H^ ztb`V=-z89+&ZLWa%@CJrOMLI~^S9*LIq#j*Xe=|^f*Rib{Dl6V_$tL%?B#M|<+|N* z;>l*{yHnwTn6(QO=u)d9zmtF&dD0*>OB0e1Q9CwD(`Q?Q|JkC1^BOxYRtq zpc%a0$u7@k*in8C{OH$ZQY5{SANK3~&~eRCYm2fV1qJK*VzE`c5HFbBqBQSschHSJ zxoOI7WA}@45fcaU3vCLm`x*MM#Sjf^wWHq-vx)a-ulq7F@3{o*NVQ$c}RJ}2&|4C72%{~Xl_XiT0fit1j`uT}5S z5`2J_T@0E6zi=w-yO2 z(LX44j~k*{=5;BchqkzlQ5#B_gkBDR-1t zImx6;k2PK6!{te-rviX9(xuepMz{R37CSmE2COvOPhb29L~Btx-pQfcpuzrgI>LpI zMxI6-)vL}KCC-(bOUuFanVC>z%b4KqWx09+t^DXiImUHmDonvbf?F<=l1O8TAv*&R z=S`_Y+~Y`9buL74S$)o#x0wCwCShSNwC5_0@7{@nawCh?Hy+E`tK=Al5BV7+wYABj z;ODm9W%;hpY4$azvPHZRNXMb{MYIyrPr1+P{rD#awS0N3Z=)aqg*n>(cD?JTY%qqJ z;hhkDKN>uM((3rqn@Vf)e(4;!9D)!Sa>$yWBZOM5HH|op8N=lYcKrci zQroNJU1L%1cH=g~YiJnJ39m@QYh< zkG%La*xNA3AbgeWfvpAzL%ZH%IGYhSKL;eO0Ke5v-iHvXW(`UbyGB^NI$m=g$Zmgr z$|u}|9@=OQn5oe*3I9TY0+R2K92*pY+1mki9jfMRHbR`FR-n88>FPCYj;4d8A457%cG|HTGL<_$TN6uH?`)1N@IC4#$zq_`;G0Yo0&M(zvw=t8%)RXeZm41U2YTJ=36yI7LnNnl`1OU?i^YIL)=P8m?}tDH8h zd8Af&58TC4-E2$Z(xb=5m_g;*T#Rh=dp*BP{RFvp@gFq4kL- z^LxPO*F?8D9^^g4B>0eCh54YAfINYOx#~x0UDO`5gY4i(Y|;WWGtlVp-59du^fBZ*SU0r) z7bT!YWSjr>^Q_97J4nT@fCnUxZvphbD*IbCsQB;i`%qHUZfhuDG5Uy5Sb%A1q+a2@ ziu4c%$#AjNW1Xto?z$Cko@W%gqd};AyivWl43G+c2_=_w804hX7xFocF zJXhMI)+Ow#{a*W{Tq_ysQOJ?g%m&faq zO(@eA-~XcMeS-YhdCI(vRtFfM(wgl9|F^Rbin2c_lxTfI!-V|sz3j|P4!ST|*+ur~ z@svCWQPBm99m2;*p|UJDy}dbRUfU3s6A~WAo(bx%pYluVPL;lk8Rn|-Z8^_%25!q*j9s*Z49R30Z=$O`J5~6?iKiXp zK?`$fx{Iru9{7|0Tgn`9IzBB?I86mJ$ydu5WdhEid)@7S%mXYRu0QXXG=ISIiSU{J zXa}#uZQ_$+eE}?E^thxNeDehvWQMw+LSrY_M|QjZzn7xzDLqaDSid&HNQiLqT_^^# zmn^Ale#euPz^w~@ffa|G8IF}}!^-Rie~3@K%vNC_$$hv#4;ahUrsi4ZKg8`6m#<&4 zq$N{j{mQWdM*Orzh-}n8ZW!3PR||#Iq1%4t*?_^82KSbd(WCGLagnZ_4v|H;)-A#% zlR&Ff-|{n|%vaeKM89hO&3ZRWY$Mf%>sF74l-7tTF`YUPI4TU{ z*?}p(3;wDxbij&4vlqKLw&L&6tRB<8zgY_R*pGOb#y6c6PdEvV=~Cn5giBJOugqII2BsCh+HR{fOqFgwUnz9ImP& z{8hIEv&-!ZJTed}*x5R-ZXiE;jx*w*f1k;%4Q?$}JZZ*FRq9`$AKx^u%nHwF_PA11 zw|lI?(`rNr1{z#rM%}C{r+tc=v=m17g+iqEW*lKrmUjXz3_N4Ju0|Xhh&S86|C9aX zr7%$=hSTGzzpBYhi~tQO6!WV*o%pp0?NzD%cV9=7e;$~tcqg~%ff8tsNp2;m7Fl)o z4nX~7kIZ_9X|_EpNzlcXx&F*hab->rz*F&Fe%&oQexs&{-JiMs1(h1!{0z$Yo43bI zP}$1%SanLE!2Pz-)~u1d8tB^gO6JjqHR#0#&ziZSa9Xd$B@`Oz>Gv{gCfmO+d^W`z zq5FjWxr^i%GW|C0*Hu&W0V>q1A+_e9ijiu4*{jzhk2q5hyizeCupQIB z4?=tKBRORSDA6k^mhG_xZt%MM)Dn))kaZECQlNwLJ?7?klTs%BAO1u3+8l{R3&jCz zS)8(ZI)(CezU&&MPMFEX?2I){^|(o+L(%(&;PI^b*S#qX01j0pOpU$T`7|ylkkcFMqfwjQ__Qzl;Kjt{f+9Gp#^rn!EXdqLi1V&a$pi>|$W73)giHsK~GleL$` z!&hMN&S`jj1e+4}{8n{k`s4EYhe-|mJgaNT00QwQ1a1V(u*wmC>Fl^v1Vs&cRsK0L zz%z8Q=eaRy&+A z<~|%gRNduo(jWLotfXrt5|{pg_oFKv5uQ~?slPD>LYh`g-`e;u?fv_4s7GJDRK2H0 zoEQB}Sq?8cJLs!CmkTCk-K45j35`?(>QkcUtynz$k9?k*3+xMP?d|WQL8(8T<*`QG!j0$=Niqr`7IF4zN+# z;dLgrH`uj`CwDPtm1_c!&UDEtWhWl&jmUNW4j{$y)C33y@u`rdgMSEt^&uutfqjKD zbmX2&{hK1@0y3@gvptrqDNtnmgoV`L`OAoNkzIa81fXGRHS0ogfihF(cEWh~jxxy? zB?F1K;?@`+0;6;3$S}V&C0gfUeQ$YybteZsCf80}LYcTx?P|W&!8dvHbgv15cm@gTM6eZAc6`g%JJ#s~bfHsH=-eX*NPS=7^;EURFX$-< z=+Fzsn;j2;_|0{rNFn~mZ0@G4yauG3)ES3ESAd)A9tZm;wfJbnnL-Ojnd#;7JV9xR zHxQDxqQOLk1f#NkVAs4t4JVYi^^nH(xcQqV!!Jc;tWw3wq)csl8qEh(osZ)hTPK^{ zLhe93N+V1BSb*U2GV(9~oG>g!-I?{-J1SVb6Q$KJu+mbqV{hM4J6SqofDYC> z*`1}p^{JWlRAr3=Ks5SwU%ik4fHPX?7UvlXAc1v3>khp@ANqAMy%4;ALtqvNL@F(Qy6`wo2%eVDYf@!P4cqVy!cqGjj8u_G)d`ZbU;JlSoE!EOs?Lrj z$WP35+kmH=9<%LlK68|DD{c1IfOL||)3KkDWOwhAOW2g+qmPo(ewSk+_@IC-OZV=% z1beLYEhoz8+C=s^@<}V#$-iJV?d_ow3N}Zbt-|TZjLQ4>n3j{nGv3*G2;w$h=%Sf7 z+?4Weypo-`LI6uyb}wqK?v&)(6#n*FbBM0Io@Nux-O(5W50HCYz~alwo{DbxFjtGSUsBPj4NRSM3qI9!6nl-ir6vYJx>OiHK| z=SndU4f2TO@lJAO9)#KQcYXNmlONCYjP5gA0xM_y*UvdNObNvV1-?rD634&g5U3nh zOc{O+eYsY)XSR!;%T$b6o9cgi%+>HVHN1b#{NQQ?NRfY5i{zgM9Mn~pmsXeF!`!3S z`N!cXU;XoAN_ZF+O{(2;$iW#5{`N8fzlQ6?ez!2VR?6?!l${Ig(c@GWtLI(Pi6uj4{9P0chbLhFE4bch&pXdLl zsrRfNo67(PLm&2E z%{Xik9#-p5qib=8NtU~SK!7A_jSx=rTWS4Y*I1H9J{iXLfw+e-J5x5mfKa1k`I?NT4WgAO3)=rFMo8`KTX$R~ z>>*mg@J}bk>C1DOwPcpxDfQzP*zk$HM4*hO>gqB1#AM@RdpjK!0224(eIskaG}2$$ zv!C_+Hrz(iNb%?m+9zc$#fCkV^!z6_|Lp=P!RRS!Hwt^Uo*)6lKb;db5{AES9md;; zMd1Z`>k$BI__2M-W-i@Bcx!V5zduGAfW0k%<%(9>*!&ov< z=ui>4-$_0_zyIRXLdB+h4J9#}51PfSBX6IBC55BnZjGfw$L0wGtKp6lC>VZqs)aM; zg66^+GY-z1Y?~6INrnU5V@(UO@F~5r7Wx}?4O;a1#Ad#c;>U4b{U_xTmO>fvhtmyU z*spHbBt~81;F&|`p0@AS%4NE~a+$Y0k7ZTxCwSO{)4nnAJM#{|vU}HD2aj4 zmIn^-fZz*Wx|lcW;=8}i+Wd*Ow>m4?;MM~A)fr2-^0qS+6Ir1zU8DdY;#aZNiUM1w zL}jgCr3cGh)&9Ydf^KDgcFWNqOvSeM@}w1C zP{&tjIqostCK{g-2dQGTd52&CF@MrPXh!VuO2n~C`nIz)Ymh4(lL6($X7*-vmhGM_ zqsP?92BU}66jtGq5&srtsVnQ&nyN%-URF`TQe^Y@C);uQoCGLI(LJ{j;Lr0YT~ffaWcClwWs*|Jwm)QgQfJKVnrSaa9A zAL{RdS&6tKVCFc6>S3U`#QOv~?VD>ntRe9?rfAm2sLvTlFH*}BlwxO|R^|?zBq3nF zg=J;>C>;Zf^sWR+T!Z3QIkhhV+;Qq1WkzXOdVSxKzq&v4juU^!=;A-NJ#gK7cipVy zu+r~6PYkt~F`3^+o}EABShK1$e@ayn>D#@US!aEd7WjhzOO5dQ8Ibd>_LlS2Rq5`Q zNMDs@+e%%LU6}n&LSmiG&4W~ggG&sr9_%f*Z82k;^s z%wOqCO$HRyPfA9>rqPcgDc~Ehe>2|f6THd3eKuQ~5Ckn;%gtkzN~J}{N?mHz4ORj3&MS9J1NQb<^w*U- zpa+L)EW2DT)bpw(*1~@Zyq39&ONxG}i006u4=+86NH41(xtDiBzYvZGP@b3vnM@WA z(J=F_WP-sz$&;j>hM(h73#lloP4=J9=}%>##F~u52^*L5xb|MsxYg0}cb1;}RtGPJ z^t3rhK`w0(X*(K9vmI$gIeOxfUS;I-JGT3olwETdJZndGYesLdGd10?D88oZxzF!- zwZQ3+ba+Pq)vVPT6sZ1v*(mfXr3OHdU@NgRpp;VURccJng0t)A(MhzWviI`)%<72X_76 zmf;vU`KCy#%B3cIWZ{0WGGxrwEnX$Uoj{n>$JdMcPXnhdhp3N*=aKa`MM>{=BtIG2 zcAwGTndajK4J5QfT0p$0mK1v)(QMvKdh}3Ym-E1t{t;8}Zx0mcZ&ahFjxA?kI)9V2 z)KjxH?C>g%)#24g+=hd^)>|}{Nb#VUkjxi%W5?wS=^tSbU^52feq^lp5b)uHlaT}x zEtf47?aLzhJ1ktntiJW-p0k_e*X25%Y|s$b;cD@d-LbJOsfp6eIljrNbl#5ZXoLGe zqDQ$~W#@;yc{{XP`kh3#UsV`Cp2irf>Vu_Ksi;wF^P`9GSl|PFNvf&_M@lEmHbWb`n3UFXO?l9B zLSgD|cUG3dH|d$MOVfEU_c`A7M#FEQ#kr5_GrhLl|M+ZZv4_Mf6nN^L7j|5N0L9lS zDfz9`;67KYKzG~pS0R5IYw&ns5?t@nH;!H03zD1jQVGe9+0%+&@)c4%TLW{%2RzE@ zeTi|Im@{l`Rw{hPM$EYg-+^5CeA3VJiLaX`VQkS_Z^+XM#oBn5EZ|f>4Eb7}Gtb3E z%)ZKK9+^emFTEP&oO>2v#IonjZ|G`NgD5dAT*uo&-zGAVLvNb&pXFHAS-Xvz5gE`5 z!u;sILy?4elW|q=5e<-F8K=hQOo&t;{XC}UbJbMFW74bn=jSy#Cdq39K=j5}E18$G z-f80Pj^5_HRo@4je;q`yl&@mX&cdS$*E2%y2|^ifvjSPT3W!F&#$K~?c(Eh6%S+YZ zswU1hdE}y(wQt75DptZuZ+gFW`0+)tyUG4ukG^OW*}!g3@J#pk_Lru<3s^gK-Gf*GhL*~;{m0<~$ z;Io)Y7@;>&>C!>;{>nEh*Cii85NW@E_I72=pPlJNjxwOX7SjOE=Txb$nX$!uKIjmtQ%%^2iJ1O@AfnU2B$lLqeL zO{89t!B%@8+_8U;>z*#UGgPAKK=!zA06yTXb=OWQk@8gx(iAqZ1p8}f*@C0*7y-=| z&*~@&!5pTM1wD2@G@PGigdGEk%G42ZK@J)jv*|~)yd(RsfM&@%#hH8#JR>qvPtomj zH12@*XW7P4$X1_@pxKg0#*?EVF?t!qrK4to!lua{wPaFTy2jTqygXxHuz%}S7_%1% z@y-7>pX-VnbX*XSWbVxf6m@g5u{v_U)L60Q$~s(n$CiH1!-DihhYvC*o8|OSNIDkm z85J3>(DmpUvLvfj$@D)|1ih{&Y;AuiiC-2_A1m7`R+C#pDRIYuFbVq?xx=0#_dT?Y z?_8eA7G5QPR){$&wFx_q-fgc*iEfN;R;Ftw;&#+*om}^U5VM~>v3pQy-cS)Cne^x+ zzLmc^_>NmMke1IrJE6Afh9g;L;AuTq>$}E|775TQq7+m%yN}!1UX!vIIW6lc|8z0= z^91Y;KPmOd=*0;P&FS$MQ`(j|J1+YwKWpZA3o4Piv{O^%umB5Zc`PFKcUrBH#9ggj z>T9Q6NkETBZ6tzV@9`I13DPg+d{+l;U>lbHr20Tb%MVz9bFPxBI=Tt$H>PuBTwca76^a}eE_1`|TMiM1N1J%059VzqCqX+I9IRC$d!WCscSBUj0@CUIns(k}VUT3s7b0N3 z-KwBnkDg#fIMeOSU?JP3NhoUcRBBb>+$q|jb_UDBE~mkA4kXd>9`Slu8oCp>(fJFj=QUg8eX-F*AsJ_m&zAFrIujh%{$!Sxb(u(sk#}6w}ZVo zljZ_va>W~j*V-vyBVOIgF`p{~q!Q*mwcVFey8^lG@2_{9^DL_wC18%6`iKtr?{ihx zF%397v4kN0``{exubhT1Fdt1S3V7G}MIAgUQw&}JL`>UTFiS)xNWu{O`s)gjr2!sM z!9mCQNpv*IQ`a5*>R>1^9r-jgGVZx8cqzU+sdbD8MlhFggOA>YnQiKlP^i~fmkR@+@? z20|f!tPYIUO9O)r8snjOzw7IPr@Gu(+KCqik;Xk4-sUVcvo8$fHkQ9usOHfD5ykkX zD|Fqp#|-$jE!->4TCWVq@2nj@ueKe217d+Ldij7`4;!dY=x&LJl(*Zqe+EIP;#3hO zYHwu6flv>&QLYlT(0~O*Lb|Hgk7D(KWvnS8F4m9255OU(c<_#&C>boDV+uvicuC?e z`k&xNA?dG7e(CI92_r9Ko_vdFCCVgV#>pFQ>i$g_N>hWf@Q~q?RZdmxRb4Qe&pe|K zE-@=~$j^J{P;wtIQqB}ir7n-YA-x0rDG;Fb%^nRk1zY^V^O{~*& zh}n+piL@Q)x3698J3`j^#Dg)gz#Db_11jA^GcYHEO>30KUJ9Twp$1E;=fMmJcJ<0W zYLza|11aDgjds+!>V6Qe;yOW>WS{j-lfgQT)L=^W+|&oCEJqpO#!jmqZYxdN)v(P*e5WsM83$iCA2F=8m#DgRBCeZ?*OVwQuts`&3`PgjB zc>D0tgdte)MP8)N)l(w4f#`=itAPEW^lnA{EYj+4XQr}v$^JBuc(A)?hc#pAm3?>w z{p&ZN1F_7RpWXgwLP3Y-5#a=!ApTfMdKTqrtNjQkib%gc2x438x{qFqlL@O&tS6T* z>A`z}*OQd4Wa?N*zBu4uaq@vW&jzf(+lRDbJ|O)}znh=UpuTjBD#-m~y!sJ@bJyH# zVK#xZ1BqBd`McR#*-PtN@~&A$G|yfln9aBS{$Qg&|1jsrsr~Q+7T-sC;^K0K#(u?u zgj-9$#^TJA=QV^c1sd4is3$J@FNt9x-{by!%?j=bl7*fOf~LRPAzhjUY9&rR|gocvKCY|Zj)jnv(B|Zj}0;L-8v*iX?);B z1UnW|n_wmKTx;dVLTOZQrUd}@kyWA)F4XgCZKn1*c+WMNSI2F@pmFPKlxRR@Z3nz^ zq3FP5c?T9X17hnB*teX!CTan!MYSy}w@dr}68i~`!4EuSQU?pjh<;fiLfeL>b(TRa$WhsFi`=99 zw~geNji9`;68KjXewF@03;Q}PpF_j)r4!npJN_0<)goKD#s{gI2CduRB)_VGU4YwJ zj9z|@`5W5##dokwAe#GjA}4|50_lVyJOpdSX~bkfSsxhZ^TBE{eYQDAslnFmkoB`E z?M_DavP^0i$uw>yyzTn^sUTTsv9k+lC}Q=K`dd}_{rCAKm>SLSDO;KG`0)%@V}54t z%ZUsBruy%~80tZZo9)HFX)nCLM@tzmNv7g0S zg@a(J@7Nq}94&3pXcZQY49xXC$F>Xw~4NN=~)~#L2eD>YV#usqo#WG?UDCT1>f+5 z7V++|gEbB1Mi!>+($3nNKX%eC_~Undx+nvFWHn)L^cfqJ3Mk*)S!kZ`P=SwIizo9J z=KV&z>rNVqab1+bwfm&e=;G_px8$d(M9;!wxWXIvjd5*9{-&U)&!2yymxnP_oUmgbi*-u zQB15Xtsb$y#vt&R%6frK5-(MUPcU7BVL((*e@uROaD@D1xkM|K@J;5OPtYxM*K&^s z7T3Tr5ZhN(L((&8b>C3;*JkJ>buqEy}ohLcjbcx?}p@xykJC~^Kb&j{j1F`27Ic7jl==$Do+w4e{0^O-Wh7?qfQc3Yp z#7#OK7`a^KCa+_EWh9w1>VIl{9C7^DN5h`wKmhatkiKK2JRiBoi^A}u$EiskvlrN% z5uK|RhsH0!pr32nOAjMbPjZRUFL)^1qrOVTl<35Vbb2Aeo~GA1^HYZ~)~nzH|BxWM zGHdbkjo-@$%gZG-y-3$C1`g@*wF<*it zodxfQC%B{s4nI}&?(18N4y&#+yK2(szVw*Z*EcX71FbHUL{^vj?aKMd6D-sP3lC=a?VV(#M#E;6tmyw?x{ zWSn`_MKqIM=jQnz&b(ntDY4!w9{2E=h!2D7!sJu^qFS8BeU(eMEp+FB1NUvg zGp{CV;?gZoE-?H|F#DX`-ON<}E;YG_lE#~!H8sRe{w#liDu;;dwKH1t z^b9boBJR;sS;{-eJ@dz>ZG2D2^!^rwKCNjkZb*C9ySt5HbINT3);v{D$W%ku5A<8a zn!PT)s3G=`rj7HxWOWC4WFGiDfpG(IeD*STuU0M`LCDn8Mm4122^B14KSMwKG(cYd zLhReUbI_gneZ`rb>>@i)rC7}s^c0U-jT)$3qn;N7y$1V~Dw933T!8n`g9V3+)cowy zmSojp3A7k~4CHn3JkCO_t5!_~USK2efqpDsMn`gj?^)|_3(&n=ZKkJr9zEL5_&xxd zB&|H5$)I%0k_na=K-AwV+N4&jw4o zo{X^TkQt2x+2u!H#}2S5q@Y0B%szvT;B zC}gj5H5r-U+IGqXg`V_+1voal7B1Absx};AmcMj-YPXle~gsYB6aCZ}Unx`tS2<*-WKTa{*-O7fA`=tlJS%Nb*fKs9ilVUN*2Zv`of?n-~Zh>-+{?>t9OoT$O40GiFo z0;g7qv11c4uxPaVZg*-RtboI-Bl9i`bo)KQEd8m3Rq|`#=KG&uV!FLYn`Q=ccQODz zE5iN8gKw30V`g8^2Q0GBM{SW!%pp%lW)4fkQmBDxohLiMtGN0I?NiZioF!{xP7GkE z9~acBg8HG^5=kWjMXTllz_M-V_NwRQjrVPd+iVEV9jQ_}lR=;&%jQ-yBB?~#=8L?o zUENbsjzAF<+}i1hOr|_q_ZC`>v5-`6p4T|;VttAcBb$j>ub}9A zck!Rxd2;{zY>fj}VKJ^@ZcOUB@!7d>%-(8WsIT5x__8W+FLd3xzbO-{*G_1kFLM9dI^ZSG7r zzrd71(UgJt4?}pTnewHp$GlJSgyjUx#TgfQVdz_BBicbqMGTNNY>* zNhTddw7M3ot_kXGqe6a91e@IDeTv3}ZJ&_Cb4Tmt?M|%waUCZ>2C*HQC))Km7#N+1uGU z*u!$aJ^2*eHTxCo?cZmViIj-y68Gc+et{l5$nWOosx9YSZmpo4D6mDv{GT&gfz1go zmoK%QvZo;M#zl3ugr0x$HW65bJfP1Mp;GAO7>G@bI+|uqE!d-2MqC?np7G>LC$8Cn zUS*I>cd^c~IDk;N>E1gp_#w$IqHZm>H}XkZO2 z#7c@nIvet$^djsva!`o(_t;5A8?5MDb{*k_8`4<6oGmA~lGpPs$bc6W`WwzN|8!}y zR&)HskP4*h$ib;;CF#nTZ_TETp!rYrJ4?!}He^iNAN@(20McN#-Hu11Fl*I@PtHjl zFks}iA>CD@=KLbR7~u9&<3uvN{KI;rMT1;3GC~A`37 zwyJa-Vh^Zca?80XGsRU?4vB@J2*n0wZJTn^Cn~ykOceHww}7m|Ciqd7Q`fqDMa^*( zbet(dbFv;x?IR2Wll@~`I5>a5G+t=ga$oi-qsOgdhS#?oypY?uDZ5t6>Wt?jEo0sUj9A>Yqh1Y2q*li zC_6{(+Eus@`~aZkjdxT0lM+35mWIRya4DdL*@Z1)L9udpRs|R6W^Qp{6;7NmrW%1A z3eu~2mT%Z$vbs$X9ehFM#;)<^;;LlVe$F+2WADJUVs?XVy8LjuvhNP$J6Z3WoV`f@ zZRyL(S)T!8;{)C6uU5mE3btxI&(a;lXTy*tq1GgF4#c74C2}IulP?5y_ZZ$mf$Q7RPUU$mcdI;1_AqeFxVn_N`y(fYUHjU z1|k;<;Kse({|2Q{K1g{I)MdYmBRWED8y0c&*(PPMN+Cb~&NRQ#sd z>}_-Sceh$!y7fU%Yy&Inu77AoZG~?vEs7q?CgF)pa2(bi8-lyt5I$Yk&u{Oqj+*A9 zoKG9A?O5C@tpHd&ah831*iIuk>BaDx;Z)+Zy4%!0Dvd*<|oqV#Q+k;V-kDS#6>1JIB79xaiV-fxtU%EN8C;G%X?_r+%pSP}==aMP# zq*%$UI^D7x%ZX(9;bToi;`(Mfd}wgmHkVZJi?rRCLR5C|nH+gc?$A&TJHK=+J?kq1 z+nLx~Ojt~c@HL!@BR%-oL|&i%Se$s|{=m7291%Nf6wiUH7t^HSg8?`z_O&tK;De;Bw!koaJEL8-G07Ah)3tn8 zC@ge|m|@dMNt-AK1@zTvlVL^p;k-t2npSulXxbjLmtI~nbsY^m! zW_ZLwuxe4GX%nCk@RHdmnbxXEF-B0KYQG*pkS}j8lb221n))SbvN9hQfCQ^WW$S400tU!i*Xdsf2vGxHNYqFSgayK*kF{M;N(kVy_S^_oF&H>=E_OL?wMR- zNCXsp3ebJ4Ec6TSHFC5yPkBrGyPe`raWSRv;Ir-F$EVF{6E3#~F3ERtxcY2>5ayJJ z{`REjsiQlB8Dqd?3N|UB;on@X2L`tt(1EMs-3BkBX)P{Xi02A=eLzOamAIIY3~$Wm zI+QH~il^fCbXv%A3i}vYABA7PX7&f?TE5!zq;zqKDUpIM5levPk@lRCT=;Hduxduu zOeM%WqnQV0(Wd)$<)drdQ!Jns#*FWb@ONEldZ*m@lRf}U`ll7dx|WV|RAaW6e}Dzz zt&2!WiI}l9hzzXQanb~QZ6+e^>G3oJ&h%uDJ22qqAA5X~_G~d7Gs}A> z;QQ@TcM)06Ivq9~ojB8+7W?C!wXAjikfl?-)4#9h&J7crR}<~*u=)71O~_sZ(_j9t zX0G$0iKdB18dj|m{Jv3?3BT5wkB?vr7?}Xk%k*=WhCcTQ% zr5BO*?)e`)-`@Ljw|jH5GqZEMznR;;5Z|k>Svb{uP02EXv=j^};n6E5FJdn=*dK&A zdyMBc`N^C|a&L(NfE1``>Aa?dtw{9i8nEY=`qvt`8EoF-4*Ak8gxB}yl#dKOo`9N` ztb5oVWt|$MnkFVKR3E4bROG^FztdCdEbi~PeVbvLsk>)`jF~fm8w?9AZ+4%WVb{+3 z1`vpDqA?r6s|y`_)9vFiVDwvw@UG|OotqpK|~;vGi1S2#BKsV)TR9 zSM_YfrujeL5&_O)?Qg3))Ha`eAorl_hSY}{@?RqM0Hw7g^28P^NhJF^NFQk^5`}f} zWi%Fn!PZ;2J_hSiFXJF>5SL57sbHEx%f$oCEUvqt2F&B&So+j3#g z?DiE^hE(`b(t7V{h-|x#2POc(fz@8qH|4|D*n}1vX7UFJA&18)Q_P|O)(O;YpBiKE z<)=1w;mlDE5==A4_|r;E-UU{~Xa1~SCu=+#@kR0E=m+tAODi|pIK%G^n74zwQcMB} z&Xi(FvV)zl~cU``2MihoKbJ;8|I?mDLk6(Uq{MpeskJOuA-@&wY-{XhL=Qo!Ux@ z^+1(%t_i1?u*zo{xl`_*uAhttLww`XRw zO`~5OanVeS^6T9AS0@mHP%zQu4P|AhFv-cz&Y-q3kE)NW0v>UHR}j+xz1y zmFiv=3EU7Sd6Ar~u4fv^rwNMHNg`d`<#UN*e_1+9Sy zWcfJyRT!H)Nn?;A?pl3E4>OCiTOE=lm2>x<;Q!#WWVmvZS>vw&!za@G_KxgjXsetS zs`%WG?mT!2=|bJLg;3M|9pAolpPk+rhtL2B^!)TbTeQTMEYs>s3Fi!lKI5EBmN!LL z_;QM=2JPB$ST_6ri=ak zg$BzB`gps>6vpR(W}EObA9hH! z(LlRDljzP>n$|jw{^-yIPc&8*rC+ym>}FrMSI9&Jen3!{5YN+oH*#@I_t{>JPu@fi z5*^zc8T)UTquvn3dlh2Jf2-d;8-(xaaYZP|X$!4(sNK2}aW&|{K-}f}G(jpkrt1O0 zj<>3v{D(gTaqnyAC9LCp=8p-*#9lbV;X_?a8&oj^h-O+QqyWc>VMXFXkd0a;pE39pqb{X|FgAM;#2Tq;@@HI$Mfoaqz6@snCT zbyvyhE|a2W{l*T5!zX#oE%L&H+c4KhT{Q)4+xnz5dyz9TEBe#fH5^(I>PX3PTcgl+ z4JvAC>OqPwZEN_@+`ABjPq!6;=qS@Y+Ktjc4NG;jDFMT{(n-rh%dcT#QU!7f3JO(w zQcY5?#XqdKF%sy+UpmpT!M5tGEbil+>M^wIykGsE7_qYTTmo#Ve?@ey`BSepZ*~-D zNMnh{4QwhC?@<2vQQ#yg6N<1-aY5WU+G~WQ%`{sSPv#aniSR9ta4p~QFtE+F>kAb4 zMEXrIWgx^V0{SRa8BWuNz_GIC ziTf;A_Knr5x`+oW3x*#EW4@ei*P(novv2$*OYnVDz{#~$M(m2t8T`l8Eoq0Io<2VH zo?VRV{Z(ubGHmW#IZe>KnCP$S(q1hl@`lUvr7F~OE7OjgZjM$YHEvyzNA9k!LWd#` zNYjS6v|iD1Z!yNZ_hO)4(Dr@ylU3bBKJ10K!Q+ZCk{Qbmzvik}n7utk5BjMk1ID=a z@BAN8M)iHRFM&6N4=FvD&kBFfkH3td?vxd_K;2aA(d%5w(ta{2BkGd*Zlko=ayFcuCA^CY4<7VBnD|Cs> zQjv1kCCaZZTisa+haq&wli@k)%lFez3^HqkkTG)_wG|62%VOm5-~U9FkviO9OeA71 zPq)0ru5?05c@QzHIFeCB$XPo#YK;)-=Hc%K(JC@Srj~4XH#~>Ybd2 zXT>L>Ia{7|{W7$G@fo)bOW}5lL2_L|R#pKoX_5#Nsl=l|9FBz_**4Qpu4<@StiuL$ z+fQmdv30tA_43{GCJHy=9SPjEOnoZCi!c)VRvko?4xb%;jE;f_kG=^H#dEYOABnzN z{*8#56?WVe3Nflyx$Me!pXI{*gTLDjm=B{rB<*wBhxzTD*6fi}ge`v*gjsxFND_~R#(&)`^bNk1wK1ZKO0lI`uYAHoMTy*Vb zv_O_1%DbVNPi_pi)D&=oL2X^u9}X;ke@Z;P1GAB6pxX)c14$+Y;_zo7<2p4cu!nEr z$}>0)KHYW!rK*_jdRjOn9pR8MVx*KD$qlaWvwPwa``NP48}*=cYO_V*`aObxhionj zO(ayH&U?k_!>EXex{|QEJUI?-)8^Mq>3vKm|n(QBn0y&*3v~;xx z30QY`DyfEFb*A600Wwu$vWL6o*)=wRQ?xdC)Hn4IU_dg;Zd_R+QX>!NRSSh~sODj7 zYpVy~TZ3k(pK-1j6ztn(m$Y z;wWY)i938@1lah@btJAH3Vf>(1JdlbtoLMR0GfXPej0xe!Vb`~?PktQ>76XT1S!$k z@yTvg0Ri3htYFY#(z{;C%Q9&iy7qq6pj)N=j!K6NV4q!2lLd5EusLI|GKP{ous1T^ z51}Qk_>keTL2qP&litK;u_J4L6C7Qt$Vb+OBmWS{ga@_0;8>z?KHBq%XkQ?nBTAh4 zBGmaUtjJai^r=XfBe#WVnyV~G_7YB6s=J&3pz^Qds$WxC!a>pb6=0Wi1F^J;BAbe6 z7d`+5d-Xha2hBX+4+o5}5yH}2>6@3nlolo>m$*P5q$=sMa=R|}dXsGioPo(iObche znh@pqiUADCTxIB9&|b0!Vjd+lcQ4kZ1cx=AY~36^{=o z_d;SZF0;^{T=Tl7*D;^#j?=91_+h>afooIHQ&YwuL;#1kSK1qJT4RJ3plJ;!40nSP zdh*X-fU17NI`G$eUTQ&iuZ3#w({3AGAtY5|Z&fB9Xi;cWxeXIyxrzvkTFVc4VK?-X znd1oZVOUvlyJIG}vfg?Q?4czBE$4`+9|eJ5{vYT~0E?Hr>OqNT@&ZKlu!jMDpq7U* z4_xb4hC*M$p`)cHwE>*;FNA>G4HiNp!8P(ZOMt>MRiOYCpN65y=Q9Q~R$Q>COVw7tb_$PDEBQ%{9>6@a^O4E!X_D*#N%Q16O@e)9; zYmELtlClSj+fPepW)lgcOQ8`leq7d$h1|X9FYoo%|2kWeV3EQFp9e#ZfY%dSwdw{)?zrS^Bd zLPO?=O~Lg0M3_F7fpKlY$AZDzoBl(lun7pswIx+Z4>-n(br)Cz<$fFpFoOZlv(tE= z3baQ720MirxWgn6?K`gn20Rho)Qj~2Me6P%I}ND3GOG_luJlcz&gx1~KUy!UI0H=y zwyBn>zyrsTUF}VR(8Y@E?z_V~fa{AivAVf6NFS#>!8%P#=mlyyxb5ZJ2pKiquRG&W zGX)GY(IC$RLxD}(!5&u>=q|}I8U>cYP=th`(&Nn9`hy-X8-!-1?mv`B)2cPR3K0eVXLp@u@qgD0>42M(jWUH||9 literal 0 HcmV?d00001 diff --git a/client2/src/components/Install/Install.module.pcss b/client2/src/components/Install/Install.module.pcss deleted file mode 100644 index 8beec8bc..00000000 --- a/client2/src/components/Install/Install.module.pcss +++ /dev/null @@ -1,14 +0,0 @@ -.layout { - background-image: url('../../assets/img/background_min.png'); - background-repeat: no-repeat; - min-height: 100vh; - background-color: #f1f3f7; -} -.container { - display: flex; - justify-content: center; - padding: 40px; -} -.content { - max-width: 404px; -} diff --git a/client2/src/components/Install/Install.tsx b/client2/src/components/Install/Install.tsx index ce5dda59..701ce626 100644 --- a/client2/src/components/Install/Install.tsx +++ b/client2/src/components/Install/Install.tsx @@ -13,6 +13,7 @@ import { } from 'Consts/install'; import { notifyError } from 'Common/ui'; import InstallStore from 'Store/stores/Install'; +import theme from 'Lib/theme'; import AdminInterface from './components/AdminInterface'; import Auth from './components/Auth'; @@ -21,8 +22,6 @@ import Stepper from './components/Stepper'; import Welcome from './components/Welcome'; import ConfigureDevices from './components/ConfigureDevices'; -import s from './Install.module.pcss'; - const { Content } = Layout; export type FormValues = IInitialConfigurationBeta & { step: number }; @@ -85,7 +84,7 @@ const InstallForm: FC = observer(() => { onSubmit={onNext} > {({ values, handleSubmit, setFieldValue }) => ( -
+ {values.step === 0 && ( setFieldValue('step', 1)}/> @@ -110,8 +109,8 @@ const InstallForm: FC = observer(() => { const Install: FC = () => { return ( - - + + diff --git a/client2/src/components/Install/components/AdminInterface/AdminInterface.module.pcss b/client2/src/components/Install/components/AdminInterface/AdminInterface.module.pcss deleted file mode 100644 index 815f2470..00000000 --- a/client2/src/components/Install/components/AdminInterface/AdminInterface.module.pcss +++ /dev/null @@ -1,17 +0,0 @@ -.manualOptions { - margin-bottom: 48px; -} - -.name { - padding-bottom: 5px; - border-bottom: 1px solid var(--gray300); - margin-bottom: 16px; - margin-top: 20px; -} - -.manualOption { - display: flex; - justify-content: space-between; - align-items: baseline; - -} \ No newline at end of file diff --git a/client2/src/components/Install/components/AdminInterface/AdminInterface.tsx b/client2/src/components/Install/components/AdminInterface/AdminInterface.tsx index 711427a9..1a4b3757 100644 --- a/client2/src/components/Install/components/AdminInterface/AdminInterface.tsx +++ b/client2/src/components/Install/components/AdminInterface/AdminInterface.tsx @@ -9,7 +9,6 @@ import { chechNetworkType, NETWORK_TYPE } from 'Helpers/installHelpers'; import theme from 'Lib/theme'; import Store from 'Store/installStore'; -import s from './AdminInterface.module.pcss'; import { FormValues } from '../../Install'; import StepButtons from '../StepButtons'; @@ -39,7 +38,7 @@ const AdminInterface: FC = observer(({ }; const getManualBlock = () => ( -
+
{addresses?.interfaces.map((a) => { let name = ''; const type = chechNetworkType(a.name); @@ -56,29 +55,27 @@ const AdminInterface: FC = observer(({ } return (
-
-
- {name} -
- {a.ipAddresses?.map((addrIp) => ( -
-
- http://{addrIp} -
- { - const temp = new Set(ip); - if (temp.has(addrIp)) { - temp.delete(addrIp); - } else { - temp.add(addrIp); - } - setFieldValue('web.ip', Array.from(temp.values())); - }}/> -
- ))} +
+ {name}
+ {a.ipAddresses?.map((addrIp) => ( +
+
+ http://{addrIp} +
+ { + const temp = new Set(ip); + if (temp.has(addrIp)) { + temp.delete(addrIp); + } else { + temp.add(addrIp); + } + setFieldValue('web.ip', Array.from(temp.values())); + }}/> +
+ ))}
); })} @@ -86,17 +83,17 @@ const AdminInterface: FC = observer(({ ); return ( -
-
+ <> +
{intl.getMessage('install_admin_interface_title')}
-
+
{intl.getMessage('install_admin_interface_title_decs')}
-
+
{intl.getMessage('install_admin_interface_where_interface')}
-
+
{intl.getMessage('install_admin_interface_where_interface_desc')}
= observer(({ ]} /> { radioValue !== NETWORK_OPTIONS.ALL && getManualBlock()} -
+
{intl.getMessage('install_admin_interface_port')}
-
+
{intl.getMessage('install_admin_interface_port_desc')}
= observer(({ currentStep={1} values={values} /> -
+ ); }); diff --git a/client2/src/components/Install/components/Auth/Auth.tsx b/client2/src/components/Install/components/Auth/Auth.tsx index a0f5797f..fe346328 100644 --- a/client2/src/components/Install/components/Auth/Auth.tsx +++ b/client2/src/components/Install/components/Auth/Auth.tsx @@ -22,11 +22,11 @@ const Auth: FC = observer(({ const { ui: { intl } } = useContext(Store); return ( -
-
+ <> +
{intl.getMessage('install_auth_title')}
-
+
{intl.getMessage('install_auth_description')}
= observer(({ currentStep={2} values={values} /> -
+ ); }); diff --git a/client2/src/components/Install/components/ConfigureDevices/ConfigureDevices.module.pcss b/client2/src/components/Install/components/ConfigureDevices/ConfigureDevices.module.pcss deleted file mode 100644 index a3b2e199..00000000 --- a/client2/src/components/Install/components/ConfigureDevices/ConfigureDevices.module.pcss +++ /dev/null @@ -1,4 +0,0 @@ -.tabs { - width: 505px; - margin-left: -131px; -} \ No newline at end of file diff --git a/client2/src/components/Install/components/ConfigureDevices/ConfigureDevices.tsx b/client2/src/components/Install/components/ConfigureDevices/ConfigureDevices.tsx index 57c49cd4..8fbcb933 100644 --- a/client2/src/components/Install/components/ConfigureDevices/ConfigureDevices.tsx +++ b/client2/src/components/Install/components/ConfigureDevices/ConfigureDevices.tsx @@ -1,5 +1,5 @@ import React, { FC, useContext } from 'react'; -import { Tabs } from 'antd'; +import { Tabs, Grid } from 'antd'; import cn from 'classnames'; import { FormikHelpers } from 'formik'; @@ -10,8 +10,8 @@ import { DEFAULT_DNS_PORT, DEFAULT_IP_ADDRESS, DEFAULT_IP_PORT } from 'Consts/in import { FormValues } from '../../Install'; import StepButtons from '../StepButtons'; -import s from './ConfigureDevices.module.pcss'; +const { useBreakpoint } = Grid; const { TabPane } = Tabs; interface ConfigureDevicesProps { @@ -23,10 +23,18 @@ const ConfigureDevices: FC = ({ values, setFieldValue, }) => { const { ui: { intl }, install: { addresses } } = useContext(Store); + const screens = useBreakpoint(); + const tabsPosition = screens.md ? 'left' : 'top'; const dhcp = (e: string) => ( - // TODO: link to dhcp - {e} + + {e} + ); const allIps = addresses?.interfaces.reduce((all, data) => { @@ -44,76 +52,92 @@ const ConfigureDevices: FC = ({ ? allIps : dnsIp; return ( -
-
+ <> +
{intl.getMessage('install_configure_title')}
-
+
{intl.getMessage('install_configure_danger_notice', { danger })}
-
- {intl.getMessage('install_configure_how_to_title')} -
- - -
+ + + +
+ {intl.getMessage('install_configure_how_to_title', { value: intl.getMessage('router') })} +
+
{intl.getMessage('install_configure_router', { p })}
-
+
+ {intl.getMessage('install_configure_how_to_title', { value: 'Windows' })} +
+
{intl.getMessage('install_configure_windows', { p })}
- -
+ +
+ {intl.getMessage('install_configure_how_to_title', { value: 'macOS' })} +
+
{intl.getMessage('install_configure_macos', { p })}
-
+
+ {intl.getMessage('install_configure_how_to_title', { value: 'Linux' })} +
+
{/* TODO: add linux setup */} {intl.getMessage('install_configure_router', { p })}
-
+
+ {intl.getMessage('install_configure_how_to_title', { value: 'Android' })} +
+
{intl.getMessage('install_configure_android', { p })}
- -
+ +
+ {intl.getMessage('install_configure_how_to_title', { value: 'iOS' })} +
+
{intl.getMessage('install_configure_ios', { p })}
-
+
{intl.getMessage('install_configure_adresses')}
-
-

+

+
{intl.getMessage('install_admin_interface_title')} -

-

+

+
{selectedWebIps?.map((ip) => ( -
+
{ip}{values.web.port !== DEFAULT_IP_PORT && `:${values.web.port}`}
))} -

-

+

+
{intl.getMessage('install_dns_server_title')} -

-
+
+
{selectedDnsIps?.map((ip) => ( -
+
{ip}{values.dns.port !== DEFAULT_DNS_PORT && `:${values.dns.port}`}
))}
-
+
{intl.getMessage('install_configure_dhcp', { dhcp })}
= ({ currentStep={4} values={values} /> -
+ ); }; diff --git a/client2/src/components/Install/components/DnsServer/DnsServer.module.pcss b/client2/src/components/Install/components/DnsServer/DnsServer.module.pcss deleted file mode 100644 index 5a90e4d5..00000000 --- a/client2/src/components/Install/components/DnsServer/DnsServer.module.pcss +++ /dev/null @@ -1,12 +0,0 @@ -.manualOptions { - margin-bottom: 48px; -} - -.manualOption { - display: flex; - justify-content: space-between; - align-items: baseline; - padding-bottom: 16px; - border-bottom: 1px solid var(--gray300); - margin-bottom: 16px; -} \ No newline at end of file diff --git a/client2/src/components/Install/components/DnsServer/DnsServer.tsx b/client2/src/components/Install/components/DnsServer/DnsServer.tsx index d3e73494..60db1260 100644 --- a/client2/src/components/Install/components/DnsServer/DnsServer.tsx +++ b/client2/src/components/Install/components/DnsServer/DnsServer.tsx @@ -9,7 +9,6 @@ import { chechNetworkType, NETWORK_TYPE } from 'Helpers/installHelpers'; import theme from 'Lib/theme'; import Store from 'Store/installStore'; -import s from './DnsServer.module.pcss'; import { FormValues } from '../../Install'; import StepButtons from '../StepButtons'; @@ -39,7 +38,7 @@ const DnsServer: FC = observer(({ }; const getManualBlock = () => ( -
+
{addresses?.interfaces.map((a) => { let name = ''; const type = chechNetworkType(a.name); @@ -56,29 +55,27 @@ const DnsServer: FC = observer(({ } return (
-
-
- {name} -
- {a.ipAddresses?.map((addrIp) => ( -
-
- {addrIp} -
- { - const temp = new Set(ip); - if (temp.has(addrIp)) { - temp.delete(addrIp); - } else { - temp.add(addrIp); - } - setFieldValue('dns.ip', Array.from(temp.values())); - }}/> -
- ))} +
+ {name}
+ {a.ipAddresses?.map((addrIp) => ( +
+
+ {addrIp} +
+ { + const temp = new Set(ip); + if (temp.has(addrIp)) { + temp.delete(addrIp); + } else { + temp.add(addrIp); + } + setFieldValue('dns.ip', Array.from(temp.values())); + }}/> +
+ ))}
); })} @@ -87,16 +84,16 @@ const DnsServer: FC = observer(({ return (
-
+
{intl.getMessage('install_dns_server_title')}
-
+
{intl.getMessage('install_dns_server_desc')}
-
+
{intl.getMessage('install_dns_server_network_interfaces')}
-
+
{intl.getMessage('install_dns_server_network_interfaces_desc')}
= observer(({ ]} /> { radioValue !== NETWORK_OPTIONS.ALL && getManualBlock()} -
+
{intl.getMessage('install_dns_server_port')}
-
+
{intl.getMessage('install_dns_server_port_desc')}
['setFieldValue']; @@ -21,11 +20,11 @@ const StepButtons: FC = observer(({ }) => { const { ui: { intl } } = useContext(Store); return ( -
+
diff --git a/client2/src/components/Install/components/Stepper/Stepper.module.pcss b/client2/src/components/Install/components/Stepper/Stepper.module.pcss index c50ba055..3f6f3a0d 100644 --- a/client2/src/components/Install/components/Stepper/Stepper.module.pcss +++ b/client2/src/components/Install/components/Stepper/Stepper.module.pcss @@ -1,3 +1,66 @@ .stepper { - margin-bottom: 48px; -} \ No newline at end of file + position: relative; + display: flex; + align-items: center; + justify-content: space-between; + height: 16px; + margin-bottom: 32px; + + @media (--m-viewport) { + margin-bottom: 48px; + } +} + +.wrap { + flex: 1; + position: relative; + display: inline-flex; + align-items: center; + justify-content: flex-end; + height: 16px; + + &:before { + content: ""; + position: absolute; + left: 0; + bottom: 7px; + width: 100%; + height: 1px; + background-color: var(--gray400); + } + + &:first-child { + flex: 0; + + &:before { + display: none; + } + } + + &.current .circle { + transform: scale(2); + background-color: var(--green400); + border-color: var(--green400); + } + + &.active .circle { + background-color: var(--green400); + border-color: var(--green400); + } + + &.current:before, + &.active:before { + background-color: var(--green400); + } +} + +.circle { + position: relative; + z-index: 1; + width: 8px; + height: 8px; + background-color: var(--white); + border-radius: 50%; + border: 1px solid var(--gray400); + transition: var(--transition) transform, var(--transition) background, var(--transition) border; +} diff --git a/client2/src/components/Install/components/Stepper/Stepper.tsx b/client2/src/components/Install/components/Stepper/Stepper.tsx index 060aa4e3..29950ca8 100644 --- a/client2/src/components/Install/components/Stepper/Stepper.tsx +++ b/client2/src/components/Install/components/Stepper/Stepper.tsx @@ -1,23 +1,34 @@ import React, { FC } from 'react'; -import { Steps } from 'antd'; +import cn from 'classnames'; import s from './Stepper.module.pcss'; +interface StepProps { + active: boolean; + current: boolean; +} + +const Step: FC = ({ active, current }) => { + return ( +
+
+
+ ); +}; + interface StepperProps { currentStep: number; } -const { Step } = Steps; - const Stepper: FC = ({ currentStep }) => { return ( - - - - - - - +
+ = 0} /> + = 1} /> + = 2} /> + = 3} /> + = 4} /> +
); }; diff --git a/client2/src/components/Install/components/Welcome/Welcome.module.pcss b/client2/src/components/Install/components/Welcome/Welcome.module.pcss deleted file mode 100644 index 37a5824f..00000000 --- a/client2/src/components/Install/components/Welcome/Welcome.module.pcss +++ /dev/null @@ -1,15 +0,0 @@ -.iconContainer{ - margin-bottom: 48px; -} -.icon { - width: 185px; - height: 57px; -} -.button { - margin-top: 48px; - width: 190px; - - &.inGroup { - margin-right: 24px; - } -} \ No newline at end of file diff --git a/client2/src/components/Install/components/Welcome/Welcome.tsx b/client2/src/components/Install/components/Welcome/Welcome.tsx index cd876465..baa81184 100644 --- a/client2/src/components/Install/components/Welcome/Welcome.tsx +++ b/client2/src/components/Install/components/Welcome/Welcome.tsx @@ -6,8 +6,6 @@ import Store from 'Store/installStore'; import Icon from 'Common/ui/Icon'; import theme from 'Lib/theme'; -import s from './Welcome.module.pcss'; - interface WelcomeProps { onNext: () => void; } @@ -15,25 +13,25 @@ interface WelcomeProps { const Welcome: FC = observer(({ onNext }) => { const { ui: { intl } } = useContext(Store); return ( -
-
- -
-
+ <> + +
{intl.getMessage('install_wellcome_title')}
-
+
{intl.getMessage('install_wellcome_desc')}
- -
+
+ +
+ ); }); diff --git a/client2/src/components/common/controls/Radio/Radio.module.pcss b/client2/src/components/common/controls/Radio/Radio.module.pcss index 50c4a8a9..ebbce092 100644 --- a/client2/src/components/common/controls/Radio/Radio.module.pcss +++ b/client2/src/components/common/controls/Radio/Radio.module.pcss @@ -13,4 +13,8 @@ &:last-child { border-bottom: 0; } -} \ No newline at end of file +} + +.desc { + color: var(--gray400); +} diff --git a/client2/src/components/common/controls/Radio/Radio.tsx b/client2/src/components/common/controls/Radio/Radio.tsx index dfb1586b..dc67100a 100644 --- a/client2/src/components/common/controls/Radio/Radio.tsx +++ b/client2/src/components/common/controls/Radio/Radio.tsx @@ -2,8 +2,6 @@ import React, { FC } from 'react'; import { Radio } from 'antd'; import { observer } from 'mobx-react-lite'; -import theme from 'Lib/theme'; - import s from './Radio.module.pcss'; const { Group } = Radio; @@ -43,7 +41,7 @@ const RadioComponent: FC = observer(({ {o.label}
{o.desc && ( -
+
{o.desc}
)} diff --git a/client2/src/components/common/formating/danger.tsx b/client2/src/components/common/formating/danger.tsx index 60e9cc7e..a04cce3c 100644 --- a/client2/src/components/common/formating/danger.tsx +++ b/client2/src/components/common/formating/danger.tsx @@ -3,7 +3,7 @@ import theme from 'Lib/theme'; const danger = (e: string) => { return ( - + {e} ); diff --git a/client2/src/lib/ant/Step.pcss b/client2/src/lib/ant/Step.pcss deleted file mode 100644 index 9f6e2044..00000000 --- a/client2/src/lib/ant/Step.pcss +++ /dev/null @@ -1,65 +0,0 @@ -.ant-steps { - display: flex; - margin-left: -67px; - .ant-steps-item-process { - .ant-steps-item-icon { - top: -4px; - box-sizing: content-box; - width: 16px; - height: 16px; - .ant-steps-icon { - background: var(--green400); - .ant-steps-icon-dot { - background: var(--green400); - border: 0; - } - } - } - } - .ant-steps-item-content { - width: 99px; - } - .ant-steps-item-finish > .ant-steps-item-container > .ant-steps-item-tail, - .ant-steps-item-wait > .ant-steps-item-container > .ant-steps-item-tail, - .ant-steps-item-process > .ant-steps-item-container > .ant-steps-item-tail { - top: 2px; - width: 100%; - margin: 0px 0px 0px 70px; - padding: 0; - &::after { - width: calc(100% - 8px); - height: 2px; - margin-left: 5px; - } - } - .ant-steps-item-wait > .ant-steps-item-container > .ant-steps-item-tail::after, - .ant-steps-item-process > .ant-steps-item-container > .ant-steps-item-tail::after { - background-color: var(--gray400); - } - .ant-steps-item-finish > .ant-steps-item-container > .ant-steps-item-tail::after { - background-color: var(--green400); - } - - .ant-steps-item-finish { - .ant-steps-item-icon { - .ant-steps-icon { - background: var(--green400); - .ant-steps-icon-dot { - background: var(--green400); - border: 0; - } - } - } - } - .ant-steps-item-icon { - width: 8px; - height: 8px; - .ant-steps-icon { - background: transparent; - .ant-steps-icon-dot { - background: transparent; - border: 2px solid var(--gray400); - } - } - } -} \ No newline at end of file diff --git a/client2/src/lib/ant/ant-overrides.less b/client2/src/lib/ant/ant-overrides.less index a07601d9..1f683335 100644 --- a/client2/src/lib/ant/ant-overrides.less +++ b/client2/src/lib/ant/ant-overrides.less @@ -1,8 +1,8 @@ @primary-color: #67b279; -@success-color: #53d4b1; +@success-color: #4d995f; @text-color: #000; -@link-hover-color: #1332BB; -@link-active-color: #246FFF; +@link-hover-color: #4d995f; +@link-active-color: #4d995f; @font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Arial, sans-serif; -@font-size-base: 14px; \ No newline at end of file +@font-size-base: 14px; diff --git a/client2/src/lib/ant/index.ts b/client2/src/lib/ant/index.ts index 9c8a364d..7eba91c1 100644 --- a/client2/src/lib/ant/index.ts +++ b/client2/src/lib/ant/index.ts @@ -1,4 +1,3 @@ -import './Step.pcss'; import './Radio.pcss'; const insertStyles = true; diff --git a/client2/src/lib/theme/Icons/index.tsx b/client2/src/lib/theme/Icons/index.tsx index 23f0a95d..a94c2c5c 100644 --- a/client2/src/lib/theme/Icons/index.tsx +++ b/client2/src/lib/theme/Icons/index.tsx @@ -2,43 +2,22 @@ import React, { FC } from 'react'; import './Icon.pcss'; export type IconType = - 'mainLogo' | + 'logo' | 'visibility_disable' | 'visibility_enable'; const Icons: FC = () => ( - - ); diff --git a/client2/src/lib/theme/Install.module.pcss b/client2/src/lib/theme/Install.module.pcss new file mode 100644 index 00000000..5e000bbb --- /dev/null +++ b/client2/src/lib/theme/Install.module.pcss @@ -0,0 +1,153 @@ +.layout { + min-height: 100vh; + background-color: var(--gray100); + + @media (--m-viewport) { + background-image: url('../../assets/img/install.png'); + background-position: center 20px; + background-repeat: no-repeat; + background-size: 100%; + } +} + +.container { + width: 100%; + max-width: 432px; + margin: 0 auto; + padding: 24px 16px 40px; + + @media (--m-viewport) { + padding: 40px 16px; + } +} + +.title, +.subtitle, +.text { + color: var(--gray900); +} + +.title { + margin-bottom: 20px; + font-size: 28px; + line-height: 1.1; +} + +.subtitle { + margin-bottom: 12px; + font-size: 20px; + line-height: 1.4; +} + +.text { + font-size: 16px; + line-height: 1.5; + + &_block { + margin-bottom: 35px; + } + + &_base { + margin-bottom: 12px; + } +} + +.danger { + text-transform: capitalize; + color: var(--red400); + font-weight: bold; +} + +.actions { + display: flex; + align-items: center; + justify-content: space-between; + flex-direction: column-reverse; + margin-top: 48px; + + @media (--m-viewport) { + flex-direction: row; + } +} + +.button { + width: 100%; + min-width: 188px; + + &:last-child { + margin-bottom: 16px; + } + + &:only-child { + margin: 0; + } + + @media (--m-viewport) { + width: auto; + + &:first-child { + margin-right: 12px; + } + + &:last-child { + margin-bottom: 0; + margin-left: 12px; + } + + &:only-child { + margin: 0; + } + } +} + +.logo { + width: 130px; + height: 40px; + margin-bottom: 35px; + + @media (--m-viewport) { + width: 185px; + height: 57px; + } +} + +.ip { + font-family: var(--font-family-monospace); + font-size: 16px; + font-weight: 600; + word-break: break-all; + color: var(--green400); +} + +.options { + margin-bottom: 48px; +} + +.name { + padding-bottom: 5px; + border-bottom: 1px solid var(--gray300); + margin-bottom: 16px; + margin-top: 20px; +} + +.option { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 8px; +} + +.address { + margin-right: 16px; + word-break: break-all; + color: var(--gray400); +} + +.tabs { + width: 100%; + + @media (--m-viewport) { + width: 505px; + margin-left: -131px; + } +} diff --git a/client2/src/lib/theme/Link.module.pcss b/client2/src/lib/theme/Link.module.pcss new file mode 100644 index 00000000..d3731b42 --- /dev/null +++ b/client2/src/lib/theme/Link.module.pcss @@ -0,0 +1,14 @@ +.link { + color: var(--green400); + text-decoration: underline; + + &:hover, + &:focus { + color: var(--green700); + text-decoration: none; + } + + &:active { + color: var(--green400); + } +} diff --git a/client2/src/lib/theme/Text.module.pcss b/client2/src/lib/theme/Text.module.pcss new file mode 100644 index 00000000..5ff8bf11 --- /dev/null +++ b/client2/src/lib/theme/Text.module.pcss @@ -0,0 +1,29 @@ +.f14 { + font-size: 14px; +} + +.f16 { + font-size: 16px; +} + +.f20 { + font-size: 20px; +} + +.bold { + font-weight: 700; +} + +.medium { + font-weight: 600; +} + +.regular { + font-weight: 400; +} + +.danger { + text-transform: uppercase; + color: var(--red400); + font-weight: bold; +} diff --git a/client2/src/lib/theme/Typography.module.pcss b/client2/src/lib/theme/Typography.module.pcss deleted file mode 100644 index b51e0e56..00000000 --- a/client2/src/lib/theme/Typography.module.pcss +++ /dev/null @@ -1,36 +0,0 @@ -.title, .subTitle, .text { - color: var(--gray900); - letter-spacing: 0px; -} - -.title { - font-size: 30px; - line-height: 36px; - margin-bottom: 16px; -} -.subTitle { - font-size: 20px; - line-height: 24px; - margin-bottom: 16px; -} -.text { - font-size: 16px; - line-height: 22px; - - &_block { - margin-bottom: 48px; - } - - &_base { - margin-bottom: 32px; - } -} -.subtext { - color: var(--gray400); -} - -.danger { - text-transform: capitalize; - color: var(--red400); - font-weight: bold; -} \ No newline at end of file diff --git a/client2/src/lib/theme/index.ts b/client2/src/lib/theme/index.ts index 43df8b68..d60feaca 100644 --- a/client2/src/lib/theme/index.ts +++ b/client2/src/lib/theme/index.ts @@ -1,9 +1,13 @@ -import typography from './Typography.module.pcss'; import form from './Form.module.pcss'; +import text from './Text.module.pcss'; +import install from './Install.module.pcss'; +import link from './Link.module.pcss'; const theme = { - typography, form, + text, + install, + link, }; export default theme; diff --git a/client2/src/localization/locales/en.json b/client2/src/localization/locales/en.json index 99ae88d4..f6801463 100644 --- a/client2/src/localization/locales/en.json +++ b/client2/src/localization/locales/en.json @@ -6,13 +6,13 @@ "password": "Password", "next": "Next", "port": "Port", - + "router": "Router", "install_admin_interface_port_desc": "Now it is working at 3000 port, just in case, but we recomended to use 80 port. Using this ports allow to access to Web interface like to common site", "install_admin_interface_port": "Which port will be used", "install_admin_interface_title_decs": "Admin web interface is used to control AdGuard Home. You can open it in your browser and it does not require using a client-side program", "install_admin_interface_title": "Admin interface settings", - "install_admin_interface_where_interface_desc": "Set what kind of networks will be able to access to Admin interface. For example: if you choose a local interface only, then Admin inteface will be accessed by this local device only", + "install_admin_interface_where_interface_desc": "Set what kind of networks will be able to access to Admin interface. For example: if you choose a local interface only, then Admin inteface will be accessed by this local device only", "install_admin_interface_where_interface": "Where can I open Admin interface", "install_all_networks_description": "All available web interfaces", @@ -37,7 +37,7 @@ "install_configure_title": "Configure your devices", "install_configure_danger_notice": "IMPORTANT! To start using AdGuard Home, you need to configure your devices manually", - "install_configure_how_to_title": "How to configure Router", + "install_configure_how_to_title": "How to configure %value%", "install_configure_router": "

This setup will automatically cover all the devices connected to your home router and you will not need to configure each of them manually.

Open the preferences for your router. Usually, you can access it from your browser via a URL (like http://192.168.0.1/ or http://192.168.1.1/). You may be asked to enter the password. If you don't remember it, you can often reset the password by pressing a button on the router itself. Some routers require a specific application, which in that case should be already installed on your computer/phone.

Find the DHCP/DNS settings. Look for the DNS letters next to a field which allows two or three sets of numbers, each broken into four groups of one to three digits.

Enter your AdGuard Home server addresses there.

", "install_configure_windows": "

Open Control Panel through Start menu or Windows search.

Go to Network and Internet category and then to Network and Sharing Center.

On the left side of the screen find Change adapter settings and click on it.

Select your active connection, right-click on it and choose Properties.

Find Internet Protocol Version 4 (TCP/IP) in the list, select it and then click on Properties again.

Choose Use the following DNS server addresses and enter your AdGuard Home server addresses.

", "install_configure_macos": "

Click on Apple icon and go to System Preferences.

Click on Network.

Select the first connection in your list and click Advanced.

Select the DNS tab and enter your AdGuard Home server addresses.

", @@ -45,4 +45,4 @@ "install_configure_ios": "

From the home screen, tap Settings.

Choose Wi-Fi in the left menu (it is impossible to configure DNS for mobile networks).

Tap on the name of the currently active network.

In the DNS field enter your AdGuard Home server addresses.

", "install_configure_adresses": "AdGuard Home addresses:", "install_configure_dhcp": "You can't set a custom DNS server on some types of routers. In this case it may help if you set up AdGuard Home as a DHCP server. Otherwise, you should search for the manual on how to customize DNS servers for your particular router model." -} \ No newline at end of file +} diff --git a/client2/src/main.pcss b/client2/src/main.pcss index 5efd7a6b..852b3e20 100644 --- a/client2/src/main.pcss +++ b/client2/src/main.pcss @@ -1,33 +1,24 @@ :root { - /*GRAYSCALE*/ --white: #ffffff; --gray100: #f3f3f3; --gray300: #d8d8d8; --gray400: #a4a4a4; --gray700: #888888; --gray900: #4d4d4d; - --black: #131313; - /*FEEDBACK*/ - --red400: #c23814; - --orange400: #eb9300; - /*BRAND*/ + --black: #000000; --green400: #67b279; --green700: #4d995f; - /*ADDITIONAL*/ - --purple400: #b267a0; - --purple700: #994d87; - --slateblue400: #677bb2; - --slateblue700: #4d6199; - /*VPN*/ - --vpngreen400: #74a352; - --vpngreen700: #568040; - --vpnslateblue700: #575770; - --vpnslateblue800: #464558; - --vpnseagreen: #5eb0b7; - + --red400: #c23814; + --transition: 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); + --font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Arial, sans-serif; + --font-family-monospace: Monaco, Menlo, "Ubuntu Mono", Consolas, source-code-pro, monospace; } body { font-size: 14px; color: var(--gray900); -} \ No newline at end of file +} + +@custom-media --m-viewport (min-width: 768px); +@custom-media --l-viewport (min-width: 992px); +@custom-media --xl-viewport (min-width: 1200px); From 715df4cd92cf360c3bb9c415825f0422aad0e0f8 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Wed, 20 Jan 2021 13:35:09 +0300 Subject: [PATCH 050/141] Pull request: all: don't close recurrent issues as stale Merge in DNS/adguard-home from fix-stale to master Updates #2275. Squashed commit of the following: commit 01cc5b85c2cb4905bb964d6b3e949e33f56d4763 Author: Ainar Garipov Date: Wed Jan 20 13:21:14 2021 +0300 all: don't close recurrent issues as stale --- .github/stale.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/stale.yml b/.github/stale.yml index 90e24289..5c7c4caa 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -8,6 +8,7 @@ - 'enhancement' - 'feature request' - 'localization' +- 'recurrent' # Label to use when marking an issue as stale. 'staleLabel': 'wontfix' # Comment to post when marking an issue as stale. Set to `false` to disable. From 5a50efadb2be6493d4c6f5fd2104b6d5861471f5 Mon Sep 17 00:00:00 2001 From: Eugene Burkov Date: Wed, 20 Jan 2021 15:59:24 +0300 Subject: [PATCH 051/141] Pull request: 2509 type-safety vol.1 Merge in DNS/adguard-home from 2509-type-safety to master Updates #2509. Squashed commit of the following: commit 535968eb7de3a9e0817ddb57bc2320e5c5a55086 Author: Eugene Burkov Date: Wed Jan 20 15:06:16 2021 +0300 dhcpd: fix comments commit dc79b80381fe7a8ecec6f9659fd23710c9229f59 Author: Eugene Burkov Date: Wed Jan 20 14:08:10 2021 +0300 all: improve docs commit 156ebf6c9bad95f82cd121f019f3b59b77b18ba6 Author: Eugene Burkov Date: Tue Jan 19 17:08:15 2021 +0300 all: improve JSON encoding and decoding --- internal/dhcpd/dhcpd.go | 35 +++++ internal/dhcpd/dhcphttp.go | 216 ++++++++++++----------------- internal/dhcpd/server.go | 30 ++-- internal/dhcpd/v4.go | 5 +- internal/dhcpd/v6.go | 5 +- internal/dnsfilter/safebrowsing.go | 29 ++-- internal/dnsfilter/safesearch.go | 15 +- openapi/openapi.yaml | 47 +++++-- 8 files changed, 202 insertions(+), 180 deletions(-) diff --git a/internal/dhcpd/dhcpd.go b/internal/dhcpd/dhcpd.go index ecb7d48d..17d817cf 100644 --- a/internal/dhcpd/dhcpd.go +++ b/internal/dhcpd/dhcpd.go @@ -3,6 +3,8 @@ package dhcpd import ( "encoding/hex" + "encoding/json" + "fmt" "net" "net/http" "path/filepath" @@ -33,6 +35,39 @@ type Lease struct { Expiry time.Time `json:"expires"` } +// MarshalJSON implements the json.Marshaler interface for *Lease. +func (l *Lease) MarshalJSON() ([]byte, error) { + type lease Lease + return json.Marshal(&struct { + HWAddr string `json:"mac"` + *lease + }{ + HWAddr: l.HWAddr.String(), + lease: (*lease)(l), + }) +} + +// UnmarshalJSON implements the json.Unmarshaler interface for *Lease. +func (l *Lease) UnmarshalJSON(data []byte) (err error) { + type lease Lease + aux := struct { + HWAddr string `json:"mac"` + *lease + }{ + lease: (*lease)(l), + } + if err = json.Unmarshal(data, &aux); err != nil { + return err + } + + l.HWAddr, err = net.ParseMAC(aux.HWAddr) + if err != nil { + return fmt.Errorf("couldn't parse MAC address: %w", err) + } + + return nil +} + // ServerConfig - DHCP server configuration // field ordering is important -- yaml fields will mirror ordering from here type ServerConfig struct { diff --git a/internal/dhcpd/dhcphttp.go b/internal/dhcpd/dhcphttp.go index e35322f8..36bc64ef 100644 --- a/internal/dhcpd/dhcphttp.go +++ b/internal/dhcpd/dhcphttp.go @@ -8,7 +8,6 @@ import ( "net/http" "os" "strings" - "time" "github.com/AdguardTeam/AdGuardHome/internal/sysutil" "github.com/AdguardTeam/AdGuardHome/internal/util" @@ -22,25 +21,6 @@ func httpError(r *http.Request, w http.ResponseWriter, code int, format string, http.Error(w, text, code) } -// []Lease -> JSON -func convertLeases(inputLeases []Lease, includeExpires bool) []map[string]string { - leases := []map[string]string{} - for _, l := range inputLeases { - lease := map[string]string{ - "mac": l.HWAddr.String(), - "ip": l.IP.String(), - "hostname": l.Hostname, - } - - if includeExpires { - lease["expires"] = l.Expiry.Format(time.RFC3339) - } - - leases = append(leases, lease) - } - return leases -} - type v4ServerConfJSON struct { GatewayIP net.IP `json:"gateway_ip"` SubnetMask net.IP `json:"subnet_mask"` @@ -49,22 +29,12 @@ type v4ServerConfJSON struct { LeaseDuration uint32 `json:"lease_duration"` } -func v4ServerConfToJSON(c V4ServerConf) v4ServerConfJSON { - return v4ServerConfJSON{ - GatewayIP: c.GatewayIP, - SubnetMask: c.SubnetMask, - RangeStart: c.RangeStart, - RangeEnd: c.RangeEnd, - LeaseDuration: c.LeaseDuration, - } -} - func v4JSONToServerConf(j v4ServerConfJSON) V4ServerConf { return V4ServerConf{ - GatewayIP: j.GatewayIP.To4(), - SubnetMask: j.SubnetMask.To4(), - RangeStart: j.RangeStart.To4(), - RangeEnd: j.RangeEnd.To4(), + GatewayIP: j.GatewayIP, + SubnetMask: j.SubnetMask, + RangeStart: j.RangeStart, + RangeEnd: j.RangeEnd, LeaseDuration: j.LeaseDuration, } } @@ -74,13 +44,6 @@ type v6ServerConfJSON struct { LeaseDuration uint32 `json:"lease_duration"` } -func v6ServerConfToJSON(c V6ServerConf) v6ServerConfJSON { - return v6ServerConfJSON{ - RangeStart: c.RangeStart, - LeaseDuration: c.LeaseDuration, - } -} - func v6JSONToServerConf(j v6ServerConfJSON) V6ServerConf { return V6ServerConf{ RangeStart: j.RangeStart, @@ -88,25 +51,30 @@ func v6JSONToServerConf(j v6ServerConfJSON) V6ServerConf { } } +// dhcpStatusResponse is the response for /control/dhcp/status endpoint. +type dhcpStatusResponse struct { + Enabled bool `json:"enabled"` + IfaceName string `json:"interface_name"` + V4 V4ServerConf `json:"v4"` + V6 V6ServerConf `json:"v6"` + Leases []Lease `json:"leases"` + StaticLeases []Lease `json:"static_leases"` +} + func (s *Server) handleDHCPStatus(w http.ResponseWriter, r *http.Request) { - leases := convertLeases(s.Leases(LeasesDynamic), true) - staticLeases := convertLeases(s.Leases(LeasesStatic), false) - - v4conf := V4ServerConf{} - s.srv4.WriteDiskConfig4(&v4conf) - - v6conf := V6ServerConf{} - s.srv6.WriteDiskConfig6(&v6conf) - - status := map[string]interface{}{ - "enabled": s.conf.Enabled, - "interface_name": s.conf.InterfaceName, - "v4": v4ServerConfToJSON(v4conf), - "v6": v6ServerConfToJSON(v6conf), - "leases": leases, - "static_leases": staticLeases, + status := &dhcpStatusResponse{ + Enabled: s.conf.Enabled, + IfaceName: s.conf.InterfaceName, + V4: V4ServerConf{}, + V6: V6ServerConf{}, } + s.srv4.WriteDiskConfig4(&status.V4) + s.srv6.WriteDiskConfig6(&status.V6) + + status.Leases = s.Leases(LeasesDynamic) + status.StaticLeases = s.Leases(LeasesStatic) + w.Header().Set("Content-Type", "application/json") err := json.NewEncoder(w).Encode(status) if err != nil { @@ -115,12 +83,6 @@ func (s *Server) handleDHCPStatus(w http.ResponseWriter, r *http.Request) { } } -type staticLeaseJSON struct { - HWAddr string `json:"mac"` - IP net.IP `json:"ip"` - Hostname string `json:"hostname"` -} - type dhcpServerConfigJSON struct { Enabled bool `json:"enabled"` InterfaceName string `json:"interface_name"` @@ -233,7 +195,7 @@ type netInterfaceJSON struct { } func (s *Server) handleDHCPInterfaces(w http.ResponseWriter, r *http.Request) { - response := map[string]interface{}{} + response := map[string]netInterfaceJSON{} ifaces, err := util.GetValidNetInterfaces() if err != nil { @@ -295,6 +257,40 @@ func (s *Server) handleDHCPInterfaces(w http.ResponseWriter, r *http.Request) { } } +// dhcpSearchOtherResult contains information about other DHCP server for +// specific network interface. +type dhcpSearchOtherResult struct { + Found string `json:"found,omitempty"` + Error string `json:"error,omitempty"` +} + +// dhcpStaticIPStatus contains information about static IP address for DHCP +// server. +type dhcpStaticIPStatus struct { + Static string `json:"static"` + IP string `json:"ip,omitempty"` + Error string `json:"error,omitempty"` +} + +// dhcpSearchV4Result contains information about DHCPv4 server for specific +// network interface. +type dhcpSearchV4Result struct { + OtherServer dhcpSearchOtherResult `json:"other_server"` + StaticIP dhcpStaticIPStatus `json:"static_ip"` +} + +// dhcpSearchV6Result contains information about DHCPv6 server for specific +// network interface. +type dhcpSearchV6Result struct { + OtherServer dhcpSearchOtherResult `json:"other_server"` +} + +// dhcpSearchResult is a response for /control/dhcp/find_active_dhcp endpoint. +type dhcpSearchResult struct { + V4 dhcpSearchV4Result `json:"v4"` + V6 dhcpSearchV6Result `json:"v6"` +} + // Perform the following tasks: // . Search for another DHCP server running // . Check if a static IP is configured for the network interface @@ -317,50 +313,42 @@ func (s *Server) handleDHCPFindActiveServer(w http.ResponseWriter, r *http.Reque return } + result := dhcpSearchResult{ + V4: dhcpSearchV4Result{ + OtherServer: dhcpSearchOtherResult{}, + StaticIP: dhcpStaticIPStatus{}, + }, + V6: dhcpSearchV6Result{ + OtherServer: dhcpSearchOtherResult{}, + }, + } + found4, err4 := CheckIfOtherDHCPServersPresentV4(interfaceName) - staticIP := map[string]interface{}{} isStaticIP, err := sysutil.IfaceHasStaticIP(interfaceName) - staticIPStatus := "yes" if err != nil { - staticIPStatus = "error" - staticIP["error"] = err.Error() + result.V4.StaticIP.Static = "error" + result.V4.StaticIP.Error = err.Error() } else if !isStaticIP { - staticIPStatus = "no" - staticIP["ip"] = util.GetSubnet(interfaceName) + result.V4.StaticIP.Static = "no" + result.V4.StaticIP.IP = util.GetSubnet(interfaceName) } - staticIP["static"] = staticIPStatus - v4 := map[string]interface{}{} - othSrv := map[string]interface{}{} - foundVal := "no" if found4 { - foundVal = "yes" + result.V4.OtherServer.Found = "yes" } else if err4 != nil { - foundVal = "error" - othSrv["error"] = err4.Error() + result.V4.OtherServer.Found = "error" + result.V4.OtherServer.Error = err4.Error() } - othSrv["found"] = foundVal - v4["other_server"] = othSrv - v4["static_ip"] = staticIP found6, err6 := CheckIfOtherDHCPServersPresentV6(interfaceName) - v6 := map[string]interface{}{} - othSrv = map[string]interface{}{} - foundVal = "no" if found6 { - foundVal = "yes" + result.V6.OtherServer.Found = "yes" } else if err6 != nil { - foundVal = "error" - othSrv["error"] = err6.Error() + result.V6.OtherServer.Found = "error" + result.V6.OtherServer.Error = err6.Error() } - othSrv["found"] = foundVal - v6["other_server"] = othSrv - - result := map[string]interface{}{} - result["v4"] = v4 - result["v6"] = v6 w.Header().Set("Content-Type", "application/json") err = json.NewEncoder(w).Encode(result) @@ -371,7 +359,7 @@ func (s *Server) handleDHCPFindActiveServer(w http.ResponseWriter, r *http.Reque } func (s *Server) handleDHCPAddStaticLease(w http.ResponseWriter, r *http.Request) { - lj := staticLeaseJSON{} + lj := Lease{} err := json.NewDecoder(r.Body).Decode(&lj) if err != nil { httpError(r, w, http.StatusBadRequest, "json.Decode: %s", err) @@ -387,21 +375,10 @@ func (s *Server) handleDHCPAddStaticLease(w http.ResponseWriter, r *http.Request ip4 := lj.IP.To4() - mac, err := net.ParseMAC(lj.HWAddr) - lease := Lease{ - HWAddr: mac, - } - if ip4 == nil { - lease.IP = lj.IP.To16() + lj.IP = lj.IP.To16() - if err != nil { - httpError(r, w, http.StatusBadRequest, "invalid MAC") - - return - } - - err = s.srv6.AddStaticLease(lease) + err = s.srv6.AddStaticLease(lj) if err != nil { httpError(r, w, http.StatusBadRequest, "%s", err) } @@ -409,9 +386,8 @@ func (s *Server) handleDHCPAddStaticLease(w http.ResponseWriter, r *http.Request return } - lease.IP = ip4 - lease.Hostname = lj.Hostname - err = s.srv4.AddStaticLease(lease) + lj.IP = ip4 + err = s.srv4.AddStaticLease(lj) if err != nil { httpError(r, w, http.StatusBadRequest, "%s", err) @@ -420,7 +396,7 @@ func (s *Server) handleDHCPAddStaticLease(w http.ResponseWriter, r *http.Request } func (s *Server) handleDHCPRemoveStaticLease(w http.ResponseWriter, r *http.Request) { - lj := staticLeaseJSON{} + lj := Lease{} err := json.NewDecoder(r.Body).Decode(&lj) if err != nil { httpError(r, w, http.StatusBadRequest, "json.Decode: %s", err) @@ -436,21 +412,10 @@ func (s *Server) handleDHCPRemoveStaticLease(w http.ResponseWriter, r *http.Requ ip4 := lj.IP.To4() - mac, err := net.ParseMAC(lj.HWAddr) - lease := Lease{ - HWAddr: mac, - } - if ip4 == nil { - lease.IP = lj.IP.To16() + lj.IP = lj.IP.To16() - if err != nil { - httpError(r, w, http.StatusBadRequest, "invalid MAC") - - return - } - - err = s.srv6.RemoveStaticLease(lease) + err = s.srv6.RemoveStaticLease(lj) if err != nil { httpError(r, w, http.StatusBadRequest, "%s", err) } @@ -458,9 +423,8 @@ func (s *Server) handleDHCPRemoveStaticLease(w http.ResponseWriter, r *http.Requ return } - lease.IP = ip4 - lease.Hostname = lj.Hostname - err = s.srv4.RemoveStaticLease(lease) + lj.IP = ip4 + err = s.srv4.RemoveStaticLease(lj) if err != nil { httpError(r, w, http.StatusBadRequest, "%s", err) diff --git a/internal/dhcpd/server.go b/internal/dhcpd/server.go index 20f6cad3..261ad4db 100644 --- a/internal/dhcpd/server.go +++ b/internal/dhcpd/server.go @@ -33,22 +33,22 @@ type DHCPServer interface { // V4ServerConf - server configuration type V4ServerConf struct { - Enabled bool `yaml:"-"` - InterfaceName string `yaml:"-"` + Enabled bool `yaml:"-" json:"-"` + InterfaceName string `yaml:"-" json:"-"` - GatewayIP net.IP `yaml:"gateway_ip"` - SubnetMask net.IP `yaml:"subnet_mask"` + GatewayIP net.IP `yaml:"gateway_ip" json:"gateway_ip"` + SubnetMask net.IP `yaml:"subnet_mask" json:"subnet_mask"` // The first & the last IP address for dynamic leases // Bytes [0..2] of the last allowed IP address must match the first IP - RangeStart net.IP `yaml:"range_start"` - RangeEnd net.IP `yaml:"range_end"` + RangeStart net.IP `yaml:"range_start" json:"range_start"` + RangeEnd net.IP `yaml:"range_end" json:"range_end"` - LeaseDuration uint32 `yaml:"lease_duration"` // in seconds + LeaseDuration uint32 `yaml:"lease_duration" json:"lease_duration"` // in seconds // IP conflict detector: time (ms) to wait for ICMP reply // 0: disable - ICMPTimeout uint32 `yaml:"icmp_timeout_msec"` + ICMPTimeout uint32 `yaml:"icmp_timeout_msec" json:"-"` // Custom Options. // @@ -58,7 +58,7 @@ type V4ServerConf struct { // // Option with IP data (only 1 IP is supported): // DEC_CODE ip IP_ADDR - Options []string `yaml:"options"` + Options []string `yaml:"options" json:"-"` ipStart net.IP // starting IP address for dynamic leases ipEnd net.IP // ending IP address for dynamic leases @@ -74,17 +74,17 @@ type V4ServerConf struct { // V6ServerConf - server configuration type V6ServerConf struct { - Enabled bool `yaml:"-"` - InterfaceName string `yaml:"-"` + Enabled bool `yaml:"-" json:"-"` + InterfaceName string `yaml:"-" json:"-"` // The first IP address for dynamic leases // The last allowed IP address ends with 0xff byte - RangeStart string `yaml:"range_start"` + RangeStart string `yaml:"range_start" json:"range_start"` - LeaseDuration uint32 `yaml:"lease_duration"` // in seconds + LeaseDuration uint32 `yaml:"lease_duration" json:"lease_duration"` // in seconds - RaSlaacOnly bool `yaml:"ra_slaac_only"` // send ICMPv6.RA packets without MO flags - RaAllowSlaac bool `yaml:"ra_allow_slaac"` // send ICMPv6.RA packets with MO flags + RaSlaacOnly bool `yaml:"ra_slaac_only" json:"-"` // send ICMPv6.RA packets without MO flags + RaAllowSlaac bool `yaml:"ra_allow_slaac" json:"-"` // send ICMPv6.RA packets with MO flags ipStart net.IP // starting IP address for dynamic leases leaseTime time.Duration // the time during which a dynamic lease is considered valid diff --git a/internal/dhcpd/v4.go b/internal/dhcpd/v4.go index 038d8f9c..2f5484a2 100644 --- a/internal/dhcpd/v4.go +++ b/internal/dhcpd/v4.go @@ -77,7 +77,10 @@ func (s *v4Server) blacklisted(l *Lease) bool { // GetLeases returns the list of current DHCP leases (thread-safe) func (s *v4Server) GetLeases(flags int) []Lease { - var result []Lease + // The function shouldn't return nil value because zero-length slice + // behaves differently in cases like marshalling. Our front-end also + // requires non-nil value in the response. + result := []Lease{} now := time.Now().Unix() s.leasesLock.Lock() diff --git a/internal/dhcpd/v6.go b/internal/dhcpd/v6.go index 2dd41b5c..0f8cddfd 100644 --- a/internal/dhcpd/v6.go +++ b/internal/dhcpd/v6.go @@ -72,7 +72,10 @@ func (s *v6Server) ResetLeases(ll []*Lease) { // GetLeases - get current leases func (s *v6Server) GetLeases(flags int) []Lease { - var result []Lease + // The function shouldn't return nil value because zero-length slice + // behaves differently in cases like marshalling. Our front-end also + // requires non-nil value in the response. + result := []Lease{} s.leasesLock.Lock() for _, lease := range s.leases { if lease.Expiry.Unix() == leaseExpireStatic { diff --git a/internal/dnsfilter/safebrowsing.go b/internal/dnsfilter/safebrowsing.go index 87cd2607..cec3081b 100644 --- a/internal/dnsfilter/safebrowsing.go +++ b/internal/dnsfilter/safebrowsing.go @@ -346,16 +346,12 @@ func (d *DNSFilter) handleSafeBrowsingDisable(w http.ResponseWriter, r *http.Req } func (d *DNSFilter) handleSafeBrowsingStatus(w http.ResponseWriter, r *http.Request) { - data := map[string]interface{}{ - "enabled": d.Config.SafeBrowsingEnabled, - } - jsonVal, err := json.Marshal(data) - if err != nil { - httpError(r, w, http.StatusInternalServerError, "Unable to marshal status json: %s", err) - } - w.Header().Set("Content-Type", "application/json") - _, err = w.Write(jsonVal) + err := json.NewEncoder(w).Encode(&struct { + Enabled bool `json:"enabled"` + }{ + Enabled: d.Config.SafeBrowsingEnabled, + }) if err != nil { httpError(r, w, http.StatusInternalServerError, "Unable to write response json: %s", err) return @@ -373,17 +369,12 @@ func (d *DNSFilter) handleParentalDisable(w http.ResponseWriter, r *http.Request } func (d *DNSFilter) handleParentalStatus(w http.ResponseWriter, r *http.Request) { - data := map[string]interface{}{ - "enabled": d.Config.ParentalEnabled, - } - jsonVal, err := json.Marshal(data) - if err != nil { - httpError(r, w, http.StatusInternalServerError, "Unable to marshal status json: %s", err) - return - } - w.Header().Set("Content-Type", "application/json") - _, err = w.Write(jsonVal) + err := json.NewEncoder(w).Encode(&struct { + Enabled bool `json:"enabled"` + }{ + Enabled: d.Config.ParentalEnabled, + }) if err != nil { httpError(r, w, http.StatusInternalServerError, "Unable to write response json: %s", err) return diff --git a/internal/dnsfilter/safesearch.go b/internal/dnsfilter/safesearch.go index 4aefa5e1..958962db 100644 --- a/internal/dnsfilter/safesearch.go +++ b/internal/dnsfilter/safesearch.go @@ -133,17 +133,12 @@ func (d *DNSFilter) handleSafeSearchDisable(w http.ResponseWriter, r *http.Reque } func (d *DNSFilter) handleSafeSearchStatus(w http.ResponseWriter, r *http.Request) { - data := map[string]interface{}{ - "enabled": d.Config.SafeSearchEnabled, - } - jsonVal, err := json.Marshal(data) - if err != nil { - httpError(r, w, http.StatusInternalServerError, "Unable to marshal status json: %s", err) - return - } - w.Header().Set("Content-Type", "application/json") - _, err = w.Write(jsonVal) + err := json.NewEncoder(w).Encode(&struct { + Enabled bool `json:"enabled"` + }{ + Enabled: d.Config.SafeSearchEnabled, + }) if err != nil { httpError(r, w, http.StatusInternalServerError, "Unable to write response json: %s", err) return diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index dbad4e50..e41eac24 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -350,7 +350,26 @@ 'application/json': 'schema': '$ref': '#/components/schemas/DhcpStatus' - '501': + '500': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/Error' + 'description': 'Not implemented (for example, on Windows).' + '/dhcp/interfaces': + 'get': + 'tags': + - 'dhcp' + 'operationId': 'dhcpInterfaces' + 'summary': 'Gets the available interfaces' + 'responses': + '200': + 'description': 'OK.' + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/NetInterfaces' + '500': 'content': 'application/json': 'schema': @@ -1620,6 +1639,12 @@ 'type': 'array' 'items': '$ref': '#/components/schemas/DhcpStaticLease' + 'NetInterfaces': + 'type': 'object' + 'description': > + Network interfaces dictionary, keys are interface names. + 'additionalProperties': + '$ref': '#/components/schemas/NetInterface' 'DhcpSearchResult': 'type': 'object' @@ -1650,7 +1675,12 @@ 'properties': 'found': 'type': 'string' - 'description': 'yes|no|error' + 'enum': + - 'yes' + - 'no' + - 'error' + 'description': > + The result of searching the other DHCP server. 'example': 'no' 'error': 'type': 'string' @@ -1662,7 +1692,12 @@ 'properties': 'static': 'type': 'string' - 'description': 'yes|no|error' + 'enum': + - 'yes' + - 'no' + - 'error' + 'description': > + The result of determining static IP address. 'example': 'yes' 'ip': 'type': 'string' @@ -2015,11 +2050,7 @@ 'format': 'uint16' 'example': 80 'interfaces': - 'type': 'object' - 'description': > - Network interfaces dictionary, keys are interface names. - 'additionalProperties': - '$ref': '#/components/schemas/NetInterface' + '$ref': '#/components/schemas/NetInterfaces' 'AddressesInfoBeta': 'type': 'object' 'description': 'AdGuard Home addresses configuration' From 7fab31beaeb8c7d1c9892746bbf37e99d4f9dc01 Mon Sep 17 00:00:00 2001 From: Eugene Burkov Date: Wed, 20 Jan 2021 17:27:53 +0300 Subject: [PATCH 052/141] Pull request: 2508 ip conversion vol.2 Merge in DNS/adguard-home from 2508-ip-conversion-vol2 to master Closes #2508. Squashed commit of the following: commit 5b9d33f9cd352756831f63e34c4aea48674628c1 Author: Eugene Burkov Date: Wed Jan 20 17:15:17 2021 +0300 util: replace net.IPNet with pointer commit 680126de7d59464077f9edf1bbaa925dd3fcee19 Merge: d3ba6a6c 5a50efad Author: Eugene Burkov Date: Wed Jan 20 17:02:41 2021 +0300 Merge branch 'master' into 2508-ip-conversion-vol2 commit d3ba6a6cdd01c0aa736418fdb86ed40120169fe9 Author: Eugene Burkov Date: Tue Jan 19 18:29:54 2021 +0300 all: remove last conversion commit 88b63f11a6c3f8705d7fa0c448c50dd646cc9214 Author: Eugene Burkov Date: Tue Jan 19 14:12:45 2021 +0300 all: improve code quality commit 71af60c70a0dbaf55e2221023d6d2e4993c9e9a7 Merge: 98af3784 9f75725d Author: Eugene Burkov Date: Mon Jan 18 17:13:27 2021 +0300 Merge branch 'master' into 2508-ip-conversion-vol2 commit 98af3784ce44d0993d171653c13d6e83bb8d1e6a Author: Eugene Burkov Date: Mon Jan 18 16:32:53 2021 +0300 all: log changes commit e99595a172bae1e844019d344544be84ddd65e4e Author: Eugene Burkov Date: Mon Jan 18 16:06:49 2021 +0300 all: fix or remove remaining net.IP <-> string conversions commit 7fd0634ce945f7e4c9b856684c5199f8a84a543e Author: Eugene Burkov Date: Fri Jan 15 15:36:17 2021 +0300 all: remove redundant net.IP <-> string converions commit 5df8af030421237d41b67ed659f83526cc258199 Author: Eugene Burkov Date: Thu Jan 14 16:35:25 2021 +0300 stats: remove redundant net.IP <-> string conversion commit fbe4e3fc015e6898063543a90c04401d76dbb18f Author: Eugene Burkov Date: Thu Jan 14 16:20:35 2021 +0300 querylog: remove redundant net.IP <-> string conversion --- CHANGELOG.md | 5 +++ internal/dhcpd/dhcpd.go | 3 -- internal/dhcpd/dhcpd_test.go | 6 +-- internal/dhcpd/dhcphttp.go | 4 +- internal/dhcpd/server.go | 2 +- internal/dhcpd/v4_test.go | 48 ++++++++++----------- internal/dhcpd/v6.go | 2 +- internal/dhcpd/v6_test.go | 8 ++-- internal/dnsfilter/dnsfilter.go | 9 ++-- internal/dnsfilter/rewrites_test.go | 12 +++--- internal/dnsforward/access.go | 16 +++---- internal/dnsforward/access_test.go | 17 ++++---- internal/dnsforward/config.go | 4 +- internal/dnsforward/dnsforward.go | 2 +- internal/dnsforward/dnsforward_test.go | 10 ++--- internal/dnsforward/filter.go | 5 +-- internal/dnsforward/stats.go | 2 +- internal/dnsforward/util.go | 8 ++-- internal/home/clients.go | 43 +++++++++---------- internal/home/clients_test.go | 28 ++++++------- internal/home/clientshttp.go | 15 ++++--- internal/home/config.go | 9 ++-- internal/home/control.go | 7 ++-- internal/home/controlinstall.go | 38 ++++++++--------- internal/home/dns.go | 38 ++++++++--------- internal/home/home.go | 20 +++++---- internal/home/home_test.go | 2 +- internal/home/options.go | 18 ++++++-- internal/home/options_test.go | 9 ++-- internal/home/rdns.go | 17 ++++---- internal/home/rdns_test.go | 3 +- internal/home/web.go | 9 ++-- internal/home/whois.go | 10 ++--- internal/querylog/decode.go | 6 ++- internal/querylog/decode_test.go | 2 +- internal/querylog/json.go | 27 ++++++------ internal/querylog/qlog.go | 5 ++- internal/querylog/qlog_test.go | 58 +++++++++++++------------- internal/querylog/searchcriteria.go | 8 +++- internal/stats/stats.go | 2 +- internal/stats/stats_test.go | 2 +- internal/stats/unit.go | 33 +++++++-------- internal/sysutil/net_linux.go | 10 ++--- internal/util/autohosts_test.go | 4 +- internal/util/network.go | 40 +++++++++--------- 45 files changed, 324 insertions(+), 302 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e2fa248b..3a7951ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -66,6 +66,7 @@ and this project adheres to ### Fixed +- Unnecessary conversions from `string` to `net.IP`, and vice versa ([#2508]). - Inability to set DNS cache TTL limits ([#2459]). - Possible freezes on slower machines ([#2225]). - A mitigation against records being shown in the wrong order on the query log @@ -79,9 +80,13 @@ and this project adheres to [#2345]: https://github.com/AdguardTeam/AdGuardHome/issues/2345 [#2355]: https://github.com/AdguardTeam/AdGuardHome/issues/2355 [#2459]: https://github.com/AdguardTeam/AdGuardHome/issues/2459 +[#2508]: https://github.com/AdguardTeam/AdGuardHome/issues/2508 ### Removed +- The undocumented ability to use hostnames as any of `bind_host` values in + configuration. Documentation requires them to be valid IP addresses, and now + the implementation makes sure that that is the case ([#2508]). - `Dockerfile` ([#2276]). Replaced with the script `scripts/make/build-docker.sh` which uses `scripts/make/Dockerfile`. - Support for pre-v0.99.3 format of query logs ([#2102]). diff --git a/internal/dhcpd/dhcpd.go b/internal/dhcpd/dhcpd.go index 17d817cf..18509991 100644 --- a/internal/dhcpd/dhcpd.go +++ b/internal/dhcpd/dhcpd.go @@ -297,9 +297,6 @@ func parseOptionString(s string) (uint8, []byte) { return 0, nil } val = ip - if ip.To4() != nil { - val = ip.To4() - } default: return 0, nil diff --git a/internal/dhcpd/dhcpd_test.go b/internal/dhcpd/dhcpd_test.go index 1d3aab21..f65e6823 100644 --- a/internal/dhcpd/dhcpd_test.go +++ b/internal/dhcpd/dhcpd_test.go @@ -61,11 +61,11 @@ func TestDB(t *testing.T) { ll := s.srv4.GetLeases(LeasesAll) assert.Equal(t, "aa:aa:aa:aa:aa:bb", ll[0].HWAddr.String()) - assert.Equal(t, "192.168.10.101", ll[0].IP.String()) + assert.True(t, net.IP{192, 168, 10, 101}.Equal(ll[0].IP)) assert.EqualValues(t, leaseExpireStatic, ll[0].Expiry.Unix()) assert.Equal(t, "aa:aa:aa:aa:aa:aa", ll[1].HWAddr.String()) - assert.Equal(t, "192.168.10.100", ll[1].IP.String()) + assert.True(t, net.IP{192, 168, 10, 100}.Equal(ll[1].IP)) assert.Equal(t, exp1.Unix(), ll[1].Expiry.Unix()) _ = os.Remove("leases.db") @@ -117,7 +117,7 @@ func TestOptions(t *testing.T) { code, val = parseOptionString("123 ip 1.2.3.4") assert.EqualValues(t, 123, code) - assert.Equal(t, "1.2.3.4", net.IP(string(val)).String()) + assert.True(t, net.IP{1, 2, 3, 4}.Equal(net.IP(val))) code, _ = parseOptionString("256 ip 1.1.1.1") assert.EqualValues(t, 0, code) diff --git a/internal/dhcpd/dhcphttp.go b/internal/dhcpd/dhcphttp.go index 36bc64ef..e38afbca 100644 --- a/internal/dhcpd/dhcphttp.go +++ b/internal/dhcpd/dhcphttp.go @@ -40,7 +40,7 @@ func v4JSONToServerConf(j v4ServerConfJSON) V4ServerConf { } type v6ServerConfJSON struct { - RangeStart string `json:"range_start"` + RangeStart net.IP `json:"range_start"` LeaseDuration uint32 `json:"lease_duration"` } @@ -331,7 +331,7 @@ func (s *Server) handleDHCPFindActiveServer(w http.ResponseWriter, r *http.Reque result.V4.StaticIP.Error = err.Error() } else if !isStaticIP { result.V4.StaticIP.Static = "no" - result.V4.StaticIP.IP = util.GetSubnet(interfaceName) + result.V4.StaticIP.IP = util.GetSubnet(interfaceName).String() } if found4 { diff --git a/internal/dhcpd/server.go b/internal/dhcpd/server.go index 261ad4db..4adbca5a 100644 --- a/internal/dhcpd/server.go +++ b/internal/dhcpd/server.go @@ -79,7 +79,7 @@ type V6ServerConf struct { // The first IP address for dynamic leases // The last allowed IP address ends with 0xff byte - RangeStart string `yaml:"range_start" json:"range_start"` + RangeStart net.IP `yaml:"range_start"` LeaseDuration uint32 `yaml:"lease_duration" json:"lease_duration"` // in seconds diff --git a/internal/dhcpd/v4_test.go b/internal/dhcpd/v4_test.go index e3086026..74eab484 100644 --- a/internal/dhcpd/v4_test.go +++ b/internal/dhcpd/v4_test.go @@ -40,7 +40,7 @@ func TestV4StaticLeaseAddRemove(t *testing.T) { // check ls = s.GetLeases(LeasesStatic) assert.Len(t, ls, 1) - assert.Equal(t, "192.168.10.150", ls[0].IP.String()) + assert.True(t, net.IP{192, 168, 10, 150}.Equal(ls[0].IP)) assert.Equal(t, "aa:aa:aa:aa:aa:aa", ls[0].HWAddr.String()) assert.EqualValues(t, leaseExpireStatic, ls[0].Expiry.Unix()) @@ -102,11 +102,11 @@ func TestV4StaticLeaseAddReplaceDynamic(t *testing.T) { ls := s.GetLeases(LeasesStatic) assert.Len(t, ls, 2) - assert.Equal(t, "192.168.10.150", ls[0].IP.String()) + assert.True(t, net.IP{192, 168, 10, 150}.Equal(ls[0].IP)) assert.Equal(t, "33:aa:aa:aa:aa:aa", ls[0].HWAddr.String()) assert.EqualValues(t, leaseExpireStatic, ls[0].Expiry.Unix()) - assert.Equal(t, "192.168.10.152", ls[1].IP.String()) + assert.True(t, net.IP{192, 168, 10, 152}.Equal(ls[1].IP)) assert.Equal(t, "22:aa:aa:aa:aa:aa", ls[1].HWAddr.String()) assert.EqualValues(t, leaseExpireStatic, ls[1].Expiry.Unix()) } @@ -139,10 +139,10 @@ func TestV4StaticLeaseGet(t *testing.T) { // check "Offer" assert.Equal(t, dhcpv4.MessageTypeOffer, resp.MessageType()) assert.Equal(t, "aa:aa:aa:aa:aa:aa", resp.ClientHWAddr.String()) - assert.Equal(t, "192.168.10.150", resp.YourIPAddr.String()) - assert.Equal(t, "192.168.10.1", resp.Router()[0].String()) - assert.Equal(t, "192.168.10.1", resp.ServerIdentifier().String()) - assert.Equal(t, "255.255.255.0", net.IP(resp.SubnetMask()).String()) + assert.True(t, net.IP{192, 168, 10, 150}.Equal(resp.YourIPAddr)) + assert.True(t, net.IP{192, 168, 10, 1}.Equal(resp.Router()[0])) + assert.True(t, net.IP{192, 168, 10, 1}.Equal(resp.ServerIdentifier())) + assert.True(t, net.IP{255, 255, 255, 0}.Equal(net.IP(resp.SubnetMask()))) assert.Equal(t, s.conf.leaseTime.Seconds(), resp.IPAddressLeaseTime(-1).Seconds()) // "Request" @@ -153,20 +153,20 @@ func TestV4StaticLeaseGet(t *testing.T) { // check "Ack" assert.Equal(t, dhcpv4.MessageTypeAck, resp.MessageType()) assert.Equal(t, "aa:aa:aa:aa:aa:aa", resp.ClientHWAddr.String()) - assert.Equal(t, "192.168.10.150", resp.YourIPAddr.String()) - assert.Equal(t, "192.168.10.1", resp.Router()[0].String()) - assert.Equal(t, "192.168.10.1", resp.ServerIdentifier().String()) - assert.Equal(t, "255.255.255.0", net.IP(resp.SubnetMask()).String()) + assert.True(t, net.IP{192, 168, 10, 150}.Equal(resp.YourIPAddr)) + assert.True(t, net.IP{192, 168, 10, 1}.Equal(resp.Router()[0])) + assert.True(t, net.IP{192, 168, 10, 1}.Equal(resp.ServerIdentifier())) + assert.True(t, net.IP{255, 255, 255, 0}.Equal(net.IP(resp.SubnetMask()))) assert.Equal(t, s.conf.leaseTime.Seconds(), resp.IPAddressLeaseTime(-1).Seconds()) dnsAddrs := resp.DNS() assert.Len(t, dnsAddrs, 1) - assert.Equal(t, "192.168.10.1", dnsAddrs[0].String()) + assert.True(t, net.IP{192, 168, 10, 1}.Equal(dnsAddrs[0])) // check lease ls := s.GetLeases(LeasesStatic) assert.Len(t, ls, 1) - assert.Equal(t, "192.168.10.150", ls[0].IP.String()) + assert.True(t, net.IP{192, 168, 10, 150}.Equal(ls[0].IP)) assert.Equal(t, "aa:aa:aa:aa:aa:aa", ls[0].HWAddr.String()) } @@ -197,13 +197,13 @@ func TestV4DynamicLeaseGet(t *testing.T) { // check "Offer" assert.Equal(t, dhcpv4.MessageTypeOffer, resp.MessageType()) assert.Equal(t, "aa:aa:aa:aa:aa:aa", resp.ClientHWAddr.String()) - assert.Equal(t, "192.168.10.100", resp.YourIPAddr.String()) - assert.Equal(t, "192.168.10.1", resp.Router()[0].String()) - assert.Equal(t, "192.168.10.1", resp.ServerIdentifier().String()) - assert.Equal(t, "255.255.255.0", net.IP(resp.SubnetMask()).String()) + assert.True(t, net.IP{192, 168, 10, 100}.Equal(resp.YourIPAddr)) + assert.True(t, net.IP{192, 168, 10, 1}.Equal(resp.Router()[0])) + assert.True(t, net.IP{192, 168, 10, 1}.Equal(resp.ServerIdentifier())) + assert.True(t, net.IP{255, 255, 255, 0}.Equal(net.IP(resp.SubnetMask()))) assert.Equal(t, s.conf.leaseTime.Seconds(), resp.IPAddressLeaseTime(-1).Seconds()) assert.Equal(t, []byte("012"), resp.Options[uint8(dhcpv4.OptionFQDN)]) - assert.Equal(t, "1.2.3.4", net.IP(resp.Options[uint8(dhcpv4.OptionRelayAgentInformation)]).String()) + assert.True(t, net.IP{1, 2, 3, 4}.Equal(net.IP(resp.Options[uint8(dhcpv4.OptionRelayAgentInformation)]))) // "Request" req, _ = dhcpv4.NewRequestFromOffer(resp) @@ -213,20 +213,20 @@ func TestV4DynamicLeaseGet(t *testing.T) { // check "Ack" assert.Equal(t, dhcpv4.MessageTypeAck, resp.MessageType()) assert.Equal(t, "aa:aa:aa:aa:aa:aa", resp.ClientHWAddr.String()) - assert.Equal(t, "192.168.10.100", resp.YourIPAddr.String()) - assert.Equal(t, "192.168.10.1", resp.Router()[0].String()) - assert.Equal(t, "192.168.10.1", resp.ServerIdentifier().String()) - assert.Equal(t, "255.255.255.0", net.IP(resp.SubnetMask()).String()) + assert.True(t, net.IP{192, 168, 10, 100}.Equal(resp.YourIPAddr)) + assert.True(t, net.IP{192, 168, 10, 1}.Equal(resp.Router()[0])) + assert.True(t, net.IP{192, 168, 10, 1}.Equal(resp.ServerIdentifier())) + assert.True(t, net.IP{255, 255, 255, 0}.Equal(net.IP(resp.SubnetMask()))) assert.Equal(t, s.conf.leaseTime.Seconds(), resp.IPAddressLeaseTime(-1).Seconds()) dnsAddrs := resp.DNS() assert.Len(t, dnsAddrs, 1) - assert.Equal(t, "192.168.10.1", dnsAddrs[0].String()) + assert.True(t, net.IP{192, 168, 10, 1}.Equal(dnsAddrs[0])) // check lease ls := s.GetLeases(LeasesDynamic) assert.Len(t, ls, 1) - assert.Equal(t, "192.168.10.100", ls[0].IP.String()) + assert.True(t, net.IP{192, 168, 10, 100}.Equal(ls[0].IP)) assert.Equal(t, "aa:aa:aa:aa:aa:aa", ls[0].HWAddr.String()) start := net.IP{192, 168, 10, 100} diff --git a/internal/dhcpd/v6.go b/internal/dhcpd/v6.go index 0f8cddfd..f9dab113 100644 --- a/internal/dhcpd/v6.go +++ b/internal/dhcpd/v6.go @@ -660,7 +660,7 @@ func v6Create(conf V6ServerConf) (DHCPServer, error) { return s, nil } - s.conf.ipStart = net.ParseIP(conf.RangeStart) + s.conf.ipStart = conf.RangeStart if s.conf.ipStart == nil || s.conf.ipStart.To16() == nil { return s, fmt.Errorf("dhcpv6: invalid range-start IP: %s", conf.RangeStart) } diff --git a/internal/dhcpd/v6_test.go b/internal/dhcpd/v6_test.go index fd3dd89b..1c54163a 100644 --- a/internal/dhcpd/v6_test.go +++ b/internal/dhcpd/v6_test.go @@ -17,7 +17,7 @@ func notify6(flags uint32) { func TestV6StaticLeaseAddRemove(t *testing.T) { conf := V6ServerConf{ Enabled: true, - RangeStart: "2001::1", + RangeStart: net.ParseIP("2001::1"), notify: notify6, } s, err := v6Create(conf) @@ -60,7 +60,7 @@ func TestV6StaticLeaseAddRemove(t *testing.T) { func TestV6StaticLeaseAddReplaceDynamic(t *testing.T) { conf := V6ServerConf{ Enabled: true, - RangeStart: "2001::1", + RangeStart: net.ParseIP("2001::1"), notify: notify6, } sIface, err := v6Create(conf) @@ -109,7 +109,7 @@ func TestV6StaticLeaseAddReplaceDynamic(t *testing.T) { func TestV6GetLease(t *testing.T) { conf := V6ServerConf{ Enabled: true, - RangeStart: "2001::1", + RangeStart: net.ParseIP("2001::1"), notify: notify6, } sIface, err := v6Create(conf) @@ -169,7 +169,7 @@ func TestV6GetLease(t *testing.T) { func TestV6GetDynamicLease(t *testing.T) { conf := V6ServerConf{ Enabled: true, - RangeStart: "2001::2", + RangeStart: net.ParseIP("2001::2"), notify: notify6, } sIface, err := v6Create(conf) diff --git a/internal/dnsfilter/dnsfilter.go b/internal/dnsfilter/dnsfilter.go index c5c28aff..45ce5b8a 100644 --- a/internal/dnsfilter/dnsfilter.go +++ b/internal/dnsfilter/dnsfilter.go @@ -36,7 +36,7 @@ type RequestFilteringSettings struct { ParentalEnabled bool ClientName string - ClientIP string + ClientIP net.IP ClientTags []string ServicesRules []ServiceEntry @@ -676,9 +676,10 @@ func (d *DNSFilter) matchHost(host string, qtype uint16, setts RequestFilteringS ureq := urlfilter.DNSRequest{ Hostname: host, SortedClientTags: setts.ClientTags, - ClientIP: setts.ClientIP, - ClientName: setts.ClientName, - DNSType: qtype, + // TODO(e.burkov): Wait for urlfilter update to pass net.IP. + ClientIP: setts.ClientIP.String(), + ClientName: setts.ClientName, + DNSType: qtype, } if d.filteringEngineAllow != nil { diff --git a/internal/dnsfilter/rewrites_test.go b/internal/dnsfilter/rewrites_test.go index a38f3f9d..12a59409 100644 --- a/internal/dnsfilter/rewrites_test.go +++ b/internal/dnsfilter/rewrites_test.go @@ -117,19 +117,19 @@ func TestRewritesLevels(t *testing.T) { r := d.processRewrites("host.com", dns.TypeA) assert.Equal(t, Rewritten, r.Reason) assert.Len(t, r.IPList, 1) - assert.Equal(t, "1.1.1.1", r.IPList[0].String()) + assert.True(t, net.IP{1, 1, 1, 1}.Equal(r.IPList[0])) // match L2 r = d.processRewrites("sub.host.com", dns.TypeA) assert.Equal(t, Rewritten, r.Reason) assert.Len(t, r.IPList, 1) - assert.Equal(t, "2.2.2.2", r.IPList[0].String()) + assert.True(t, net.IP{2, 2, 2, 2}.Equal(r.IPList[0])) // match L3 r = d.processRewrites("my.sub.host.com", dns.TypeA) assert.Equal(t, Rewritten, r.Reason) assert.Len(t, r.IPList, 1) - assert.Equal(t, "3.3.3.3", r.IPList[0].String()) + assert.True(t, net.IP{3, 3, 3, 3}.Equal(r.IPList[0])) } func TestRewritesExceptionCNAME(t *testing.T) { @@ -145,7 +145,7 @@ func TestRewritesExceptionCNAME(t *testing.T) { r := d.processRewrites("my.host.com", dns.TypeA) assert.Equal(t, Rewritten, r.Reason) assert.Len(t, r.IPList, 1) - assert.Equal(t, "2.2.2.2", r.IPList[0].String()) + assert.True(t, net.IP{2, 2, 2, 2}.Equal(r.IPList[0])) // match sub-domain, but handle exception r = d.processRewrites("sub.host.com", dns.TypeA) @@ -165,7 +165,7 @@ func TestRewritesExceptionWC(t *testing.T) { r := d.processRewrites("my.host.com", dns.TypeA) assert.Equal(t, Rewritten, r.Reason) assert.Len(t, r.IPList, 1) - assert.Equal(t, "2.2.2.2", r.IPList[0].String()) + assert.True(t, net.IP{2, 2, 2, 2}.Equal(r.IPList[0])) // match sub-domain, but handle exception r = d.processRewrites("my.sub.host.com", dns.TypeA) @@ -188,7 +188,7 @@ func TestRewritesExceptionIP(t *testing.T) { r := d.processRewrites("host.com", dns.TypeA) assert.Equal(t, Rewritten, r.Reason) assert.Len(t, r.IPList, 1) - assert.Equal(t, "1.2.3.4", r.IPList[0].String()) + assert.True(t, net.IP{1, 2, 3, 4}.Equal(r.IPList[0])) // match exception r = d.processRewrites("host.com", dns.TypeAAAA) diff --git a/internal/dnsforward/access.go b/internal/dnsforward/access.go index 5038a89a..8afae955 100644 --- a/internal/dnsforward/access.go +++ b/internal/dnsforward/access.go @@ -83,20 +83,21 @@ func processIPCIDRArray(dst *map[string]bool, dstIPNet *[]net.IPNet, src []strin // Returns the item from the "disallowedClients" list that lead to blocking IP. // If it returns TRUE and an empty string, it means that the "allowedClients" is not empty, // but the ip does not belong to it. -func (a *accessCtx) IsBlockedIP(ip string) (bool, string) { +func (a *accessCtx) IsBlockedIP(ip net.IP) (bool, string) { + ipStr := ip.String() + a.lock.Lock() defer a.lock.Unlock() if len(a.allowedClients) != 0 || len(a.allowedClientsIPNet) != 0 { - _, ok := a.allowedClients[ip] + _, ok := a.allowedClients[ipStr] if ok { return false, "" } if len(a.allowedClientsIPNet) != 0 { - ipAddr := net.ParseIP(ip) for _, ipnet := range a.allowedClientsIPNet { - if ipnet.Contains(ipAddr) { + if ipnet.Contains(ip) { return false, "" } } @@ -105,15 +106,14 @@ func (a *accessCtx) IsBlockedIP(ip string) (bool, string) { return true, "" } - _, ok := a.disallowedClients[ip] + _, ok := a.disallowedClients[ipStr] if ok { - return true, ip + return true, ipStr } if len(a.disallowedClientsIPNet) != 0 { - ipAddr := net.ParseIP(ip) for _, ipnet := range a.disallowedClientsIPNet { - if ipnet.Contains(ipAddr) { + if ipnet.Contains(ip) { return true, ipnet.String() } } diff --git a/internal/dnsforward/access_test.go b/internal/dnsforward/access_test.go index 5c225b21..af13b02e 100644 --- a/internal/dnsforward/access_test.go +++ b/internal/dnsforward/access_test.go @@ -1,6 +1,7 @@ package dnsforward import ( + "net" "testing" "github.com/stretchr/testify/assert" @@ -10,19 +11,19 @@ func TestIsBlockedIPAllowed(t *testing.T) { a := &accessCtx{} assert.Nil(t, a.Init([]string{"1.1.1.1", "2.2.0.0/16"}, nil, nil)) - disallowed, disallowedRule := a.IsBlockedIP("1.1.1.1") + disallowed, disallowedRule := a.IsBlockedIP(net.IPv4(1, 1, 1, 1)) assert.False(t, disallowed) assert.Empty(t, disallowedRule) - disallowed, disallowedRule = a.IsBlockedIP("1.1.1.2") + disallowed, disallowedRule = a.IsBlockedIP(net.IPv4(1, 1, 1, 2)) assert.True(t, disallowed) assert.Empty(t, disallowedRule) - disallowed, disallowedRule = a.IsBlockedIP("2.2.1.1") + disallowed, disallowedRule = a.IsBlockedIP(net.IPv4(2, 2, 1, 1)) assert.False(t, disallowed) assert.Empty(t, disallowedRule) - disallowed, disallowedRule = a.IsBlockedIP("2.3.1.1") + disallowed, disallowedRule = a.IsBlockedIP(net.IPv4(2, 3, 1, 1)) assert.True(t, disallowed) assert.Empty(t, disallowedRule) } @@ -31,19 +32,19 @@ func TestIsBlockedIPDisallowed(t *testing.T) { a := &accessCtx{} assert.Nil(t, a.Init(nil, []string{"1.1.1.1", "2.2.0.0/16"}, nil)) - disallowed, disallowedRule := a.IsBlockedIP("1.1.1.1") + disallowed, disallowedRule := a.IsBlockedIP(net.IPv4(1, 1, 1, 1)) assert.True(t, disallowed) assert.Equal(t, "1.1.1.1", disallowedRule) - disallowed, disallowedRule = a.IsBlockedIP("1.1.1.2") + disallowed, disallowedRule = a.IsBlockedIP(net.IPv4(1, 1, 1, 2)) assert.False(t, disallowed) assert.Empty(t, disallowedRule) - disallowed, disallowedRule = a.IsBlockedIP("2.2.1.1") + disallowed, disallowedRule = a.IsBlockedIP(net.IPv4(2, 2, 1, 1)) assert.True(t, disallowed) assert.Equal(t, "2.2.0.0/16", disallowedRule) - disallowed, disallowedRule = a.IsBlockedIP("2.3.1.1") + disallowed, disallowedRule = a.IsBlockedIP(net.IPv4(2, 3, 1, 1)) assert.False(t, disallowed) assert.Empty(t, disallowedRule) } diff --git a/internal/dnsforward/config.go b/internal/dnsforward/config.go index 0ff078f7..ee3f6aa0 100644 --- a/internal/dnsforward/config.go +++ b/internal/dnsforward/config.go @@ -25,11 +25,11 @@ type FilteringConfig struct { // -- // Filtering callback function - FilterHandler func(clientAddr string, settings *dnsfilter.RequestFilteringSettings) `yaml:"-"` + FilterHandler func(clientAddr net.IP, settings *dnsfilter.RequestFilteringSettings) `yaml:"-"` // GetCustomUpstreamByClient - a callback function that returns upstreams configuration // based on the client IP address. Returns nil if there are no custom upstreams for the client - // TODO(e.burkov): replace argument type with net.IP. + // TODO(e.burkov): Replace argument type with net.IP. GetCustomUpstreamByClient func(clientAddr string) *proxy.UpstreamConfig `yaml:"-"` // Protection configuration diff --git a/internal/dnsforward/dnsforward.go b/internal/dnsforward/dnsforward.go index ab6bea27..0ada0640 100644 --- a/internal/dnsforward/dnsforward.go +++ b/internal/dnsforward/dnsforward.go @@ -298,6 +298,6 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { } // IsBlockedIP - return TRUE if this client should be blocked -func (s *Server) IsBlockedIP(ip string) (bool, string) { +func (s *Server) IsBlockedIP(ip net.IP) (bool, string) { return s.access.IsBlockedIP(ip) } diff --git a/internal/dnsforward/dnsforward_test.go b/internal/dnsforward/dnsforward_test.go index ab9bb03c..ec56c9e8 100644 --- a/internal/dnsforward/dnsforward_test.go +++ b/internal/dnsforward/dnsforward_test.go @@ -322,7 +322,7 @@ func TestServerCustomClientUpstream(t *testing.T) { assert.Nil(t, err) assert.Equal(t, dns.RcodeSuccess, reply.Rcode) assert.NotNil(t, reply.Answer) - assert.Equal(t, "192.168.0.1", reply.Answer[0].(*dns.A).A.String()) + assert.True(t, net.IP{192, 168, 0, 1}.Equal(reply.Answer[0].(*dns.A).A)) assert.Nil(t, s.Stop()) } @@ -473,7 +473,7 @@ func TestBlockCNAME(t *testing.T) { func TestClientRulesForCNAMEMatching(t *testing.T) { s := createTestServer(t) testUpstm := &testUpstream{testCNAMEs, testIPv4, nil} - s.conf.FilterHandler = func(_ string, settings *dnsfilter.RequestFilteringSettings) { + s.conf.FilterHandler = func(_ net.IP, settings *dnsfilter.RequestFilteringSettings) { settings.FilteringEnabled = false } err := s.startWithUpstream(testUpstm) @@ -568,7 +568,7 @@ func TestBlockedCustomIP(t *testing.T) { assert.Len(t, reply.Answer, 1) a, ok := reply.Answer[0].(*dns.A) assert.True(t, ok) - assert.Equal(t, "0.0.0.1", a.A.String()) + assert.True(t, net.IP{0, 0, 0, 1}.Equal(a.A)) req = createTestMessageWithType("null.example.org.", dns.TypeAAAA) reply, err = dns.Exchange(req, addr.String()) @@ -713,7 +713,7 @@ func TestRewrite(t *testing.T) { assert.Len(t, reply.Answer, 1) a, ok := reply.Answer[0].(*dns.A) assert.True(t, ok) - assert.Equal(t, "1.2.3.4", a.A.String()) + assert.True(t, net.IP{1, 2, 3, 4}.Equal(a.A)) req = createTestMessageWithType("test.com.", dns.TypeAAAA) reply, err = dns.Exchange(req, addr.String()) @@ -725,7 +725,7 @@ func TestRewrite(t *testing.T) { assert.Nil(t, err) assert.Len(t, reply.Answer, 2) assert.Equal(t, "test.com.", reply.Answer[0].(*dns.CNAME).Target) - assert.Equal(t, "1.2.3.4", reply.Answer[1].(*dns.A).A.String()) + assert.True(t, net.IP{1, 2, 3, 4}.Equal(reply.Answer[1].(*dns.A).A)) req = createTestMessageWithType("my.alias.example.org.", dns.TypeA) reply, err = dns.Exchange(req, addr.String()) diff --git a/internal/dnsforward/filter.go b/internal/dnsforward/filter.go index 77ae30a9..4d319288 100644 --- a/internal/dnsforward/filter.go +++ b/internal/dnsforward/filter.go @@ -12,7 +12,7 @@ import ( ) func (s *Server) beforeRequestHandler(_ *proxy.Proxy, d *proxy.DNSContext) (bool, error) { - ip := IPStringFromAddr(d.Addr) + ip := IPFromAddr(d.Addr) disallowed, _ := s.access.IsBlockedIP(ip) if disallowed { log.Tracef("Client IP %s is blocked by settings", ip) @@ -36,8 +36,7 @@ func (s *Server) getClientRequestFilteringSettings(d *proxy.DNSContext) *dnsfilt setts := s.dnsFilter.GetConfig() setts.FilteringEnabled = true if s.conf.FilterHandler != nil { - clientAddr := IPStringFromAddr(d.Addr) - s.conf.FilterHandler(clientAddr, &setts) + s.conf.FilterHandler(IPFromAddr(d.Addr), &setts) } return &setts } diff --git a/internal/dnsforward/stats.go b/internal/dnsforward/stats.go index 822df6a0..be45b0f9 100644 --- a/internal/dnsforward/stats.go +++ b/internal/dnsforward/stats.go @@ -36,7 +36,7 @@ func processQueryLogsAndStats(ctx *dnsContext) int { OrigAnswer: ctx.origResp, Result: ctx.result, Elapsed: elapsed, - ClientIP: ipFromAddr(d.Addr), + ClientIP: IPFromAddr(d.Addr), } switch d.Proto { diff --git a/internal/dnsforward/util.go b/internal/dnsforward/util.go index 3a8c1cb3..4b57768b 100644 --- a/internal/dnsforward/util.go +++ b/internal/dnsforward/util.go @@ -8,8 +8,8 @@ import ( "github.com/AdguardTeam/golibs/utils" ) -// ipFromAddr gets IP address from addr. -func ipFromAddr(addr net.Addr) (ip net.IP) { +// IPFromAddr gets IP address from addr. +func IPFromAddr(addr net.Addr) (ip net.IP) { switch addr := addr.(type) { case *net.UDPAddr: return addr.IP @@ -22,8 +22,8 @@ func ipFromAddr(addr net.Addr) (ip net.IP) { // IPStringFromAddr extracts IP address from net.Addr. // Note: we can't use net.SplitHostPort(a.String()) because of IPv6 zone: // https://github.com/AdguardTeam/AdGuardHome/internal/issues/1261 -func IPStringFromAddr(addr net.Addr) (ipstr string) { - if ip := ipFromAddr(addr); ip != nil { +func IPStringFromAddr(addr net.Addr) (ipStr string) { + if ip := IPFromAddr(addr); ip != nil { return ip.String() } diff --git a/internal/home/clients.go b/internal/home/clients.go index 3c6bfa48..b631f63e 100644 --- a/internal/home/clients.go +++ b/internal/home/clients.go @@ -70,10 +70,12 @@ type ClientHost struct { } type clientsContainer struct { - list map[string]*Client // name -> client - idIndex map[string]*Client // IP -> client - ipHost map[string]*ClientHost // IP -> Hostname - lock sync.Mutex + list map[string]*Client // name -> client + idIndex map[string]*Client // IP -> client + // TODO(e.burkov): Think of a way to not require string conversion for + // IP addresses. + ipHost map[string]*ClientHost // IP -> Hostname + lock sync.Mutex allTags map[string]bool @@ -239,7 +241,7 @@ func (clients *clientsContainer) onHostsChanged() { } // Exists checks if client with this IP already exists -func (clients *clientsContainer) Exists(ip string, source clientSource) bool { +func (clients *clientsContainer) Exists(ip net.IP, source clientSource) bool { clients.lock.Lock() defer clients.lock.Unlock() @@ -248,7 +250,7 @@ func (clients *clientsContainer) Exists(ip string, source clientSource) bool { return true } - ch, ok := clients.ipHost[ip] + ch, ok := clients.ipHost[ip.String()] if !ok { return false } @@ -265,7 +267,7 @@ func stringArrayDup(a []string) []string { } // Find searches for a client by IP -func (clients *clientsContainer) Find(ip string) (Client, bool) { +func (clients *clientsContainer) Find(ip net.IP) (Client, bool) { clients.lock.Lock() defer clients.lock.Unlock() @@ -287,7 +289,7 @@ func (clients *clientsContainer) FindUpstreams(ip string) *proxy.UpstreamConfig clients.lock.Lock() defer clients.lock.Unlock() - c, ok := clients.findByIP(ip) + c, ok := clients.findByIP(net.ParseIP(ip)) if !ok { return nil } @@ -307,13 +309,12 @@ func (clients *clientsContainer) FindUpstreams(ip string) *proxy.UpstreamConfig } // Find searches for a client by IP (and does not lock anything) -func (clients *clientsContainer) findByIP(ip string) (Client, bool) { - ipAddr := net.ParseIP(ip) - if ipAddr == nil { +func (clients *clientsContainer) findByIP(ip net.IP) (Client, bool) { + if ip == nil { return Client{}, false } - c, ok := clients.idIndex[ip] + c, ok := clients.idIndex[ip.String()] if ok { return *c, true } @@ -324,7 +325,7 @@ func (clients *clientsContainer) findByIP(ip string) (Client, bool) { if err != nil { continue } - if ipnet.Contains(ipAddr) { + if ipnet.Contains(ip) { return *c, true } } @@ -333,7 +334,7 @@ func (clients *clientsContainer) findByIP(ip string) (Client, bool) { if clients.dhcpServer == nil { return Client{}, false } - macFound := clients.dhcpServer.FindMACbyIP(ipAddr) + macFound := clients.dhcpServer.FindMACbyIP(ip) if macFound == nil { return Client{}, false } @@ -353,16 +354,15 @@ func (clients *clientsContainer) findByIP(ip string) (Client, bool) { } // FindAutoClient - search for an auto-client by IP -func (clients *clientsContainer) FindAutoClient(ip string) (ClientHost, bool) { - ipAddr := net.ParseIP(ip) - if ipAddr == nil { +func (clients *clientsContainer) FindAutoClient(ip net.IP) (ClientHost, bool) { + if ip == nil { return ClientHost{}, false } clients.lock.Lock() defer clients.lock.Unlock() - ch, ok := clients.ipHost[ip] + ch, ok := clients.ipHost[ip.String()] if ok { return *ch, true } @@ -539,7 +539,7 @@ func (clients *clientsContainer) Update(name string, c Client) error { } // SetWhoisInfo - associate WHOIS information with a client -func (clients *clientsContainer) SetWhoisInfo(ip string, info [][]string) { +func (clients *clientsContainer) SetWhoisInfo(ip net.IP, info [][]string) { clients.lock.Lock() defer clients.lock.Unlock() @@ -549,7 +549,8 @@ func (clients *clientsContainer) SetWhoisInfo(ip string, info [][]string) { return } - ch, ok := clients.ipHost[ip] + ipStr := ip.String() + ch, ok := clients.ipHost[ipStr] if ok { ch.WhoisInfo = info log.Debug("Clients: set WHOIS info for auto-client %s: %v", ch.Host, ch.WhoisInfo) @@ -561,7 +562,7 @@ func (clients *clientsContainer) SetWhoisInfo(ip string, info [][]string) { Source: ClientSourceWHOIS, } ch.WhoisInfo = info - clients.ipHost[ip] = ch + clients.ipHost[ipStr] = ch log.Debug("Clients: set WHOIS info for auto-client with IP %s: %v", ip, ch.WhoisInfo) } diff --git a/internal/home/clients_test.go b/internal/home/clients_test.go index 69f2bada..94ff8009 100644 --- a/internal/home/clients_test.go +++ b/internal/home/clients_test.go @@ -36,21 +36,21 @@ func TestClients(t *testing.T) { assert.True(t, b) assert.Nil(t, err) - c, b = clients.Find("1.1.1.1") + c, b = clients.Find(net.IPv4(1, 1, 1, 1)) assert.True(t, b) assert.Equal(t, c.Name, "client1") - c, b = clients.Find("1:2:3::4") + c, b = clients.Find(net.ParseIP("1:2:3::4")) assert.True(t, b) assert.Equal(t, c.Name, "client1") - c, b = clients.Find("2.2.2.2") + c, b = clients.Find(net.IPv4(2, 2, 2, 2)) assert.True(t, b) assert.Equal(t, c.Name, "client2") - assert.False(t, clients.Exists("1.2.3.4", ClientSourceHostsFile)) - assert.True(t, clients.Exists("1.1.1.1", ClientSourceHostsFile)) - assert.True(t, clients.Exists("2.2.2.2", ClientSourceHostsFile)) + assert.False(t, clients.Exists(net.IPv4(1, 2, 3, 4), ClientSourceHostsFile)) + assert.True(t, clients.Exists(net.IPv4(1, 1, 1, 1), ClientSourceHostsFile)) + assert.True(t, clients.Exists(net.IPv4(2, 2, 2, 2), ClientSourceHostsFile)) }) t.Run("add_fail_name", func(t *testing.T) { @@ -112,8 +112,8 @@ func TestClients(t *testing.T) { err := clients.Update("client1", c) assert.Nil(t, err) - assert.False(t, clients.Exists("1.1.1.1", ClientSourceHostsFile)) - assert.True(t, clients.Exists("1.1.1.2", ClientSourceHostsFile)) + assert.False(t, clients.Exists(net.IPv4(1, 1, 1, 1), ClientSourceHostsFile)) + assert.True(t, clients.Exists(net.IPv4(1, 1, 1, 2), ClientSourceHostsFile)) c = Client{ IDs: []string{"1.1.1.2"}, @@ -124,7 +124,7 @@ func TestClients(t *testing.T) { err = clients.Update("client1", c) assert.Nil(t, err) - c, b := clients.Find("1.1.1.2") + c, b := clients.Find(net.IPv4(1, 1, 1, 2)) assert.True(t, b) assert.Equal(t, "client1-renamed", c.Name) assert.Equal(t, "1.1.1.2", c.IDs[0]) @@ -135,7 +135,7 @@ func TestClients(t *testing.T) { t.Run("del_success", func(t *testing.T) { b := clients.Del("client1-renamed") assert.True(t, b) - assert.False(t, clients.Exists("1.1.1.2", ClientSourceHostsFile)) + assert.False(t, clients.Exists(net.IPv4(1, 1, 1, 2), ClientSourceHostsFile)) }) t.Run("del_fail", func(t *testing.T) { @@ -156,7 +156,7 @@ func TestClients(t *testing.T) { assert.True(t, b) assert.Nil(t, err) - assert.True(t, clients.Exists("1.1.1.1", ClientSourceHostsFile)) + assert.True(t, clients.Exists(net.IPv4(1, 1, 1, 1), ClientSourceHostsFile)) }) t.Run("addhost_fail", func(t *testing.T) { @@ -174,12 +174,12 @@ func TestClientsWhois(t *testing.T) { whois := [][]string{{"orgname", "orgname-val"}, {"country", "country-val"}} // set whois info on new client - clients.SetWhoisInfo("1.1.1.255", whois) + clients.SetWhoisInfo(net.IPv4(1, 1, 1, 255), whois) assert.Equal(t, "orgname-val", clients.ipHost["1.1.1.255"].WhoisInfo[0][1]) // set whois info on existing auto-client _, _ = clients.AddHost("1.1.1.1", "host", ClientSourceRDNS) - clients.SetWhoisInfo("1.1.1.1", whois) + clients.SetWhoisInfo(net.IPv4(1, 1, 1, 1), whois) assert.Equal(t, "orgname-val", clients.ipHost["1.1.1.1"].WhoisInfo[0][1]) // Check that we cannot set whois info on a manually-added client @@ -188,7 +188,7 @@ func TestClientsWhois(t *testing.T) { Name: "client1", } _, _ = clients.Add(c) - clients.SetWhoisInfo("1.1.1.2", whois) + clients.SetWhoisInfo(net.IPv4(1, 1, 1, 2), whois) assert.Nil(t, clients.ipHost["1.1.1.2"]) _ = clients.Del("client1") } diff --git a/internal/home/clientshttp.go b/internal/home/clientshttp.go index 51a193f9..aecc68e5 100644 --- a/internal/home/clientshttp.go +++ b/internal/home/clientshttp.go @@ -3,6 +3,7 @@ package home import ( "encoding/json" "fmt" + "net" "net/http" ) @@ -229,8 +230,9 @@ func (clients *clientsContainer) handleFindClient(w http.ResponseWriter, r *http q := r.URL.Query() data := []map[string]interface{}{} for i := 0; ; i++ { - ip := q.Get(fmt.Sprintf("ip%d", i)) - if len(ip) == 0 { + ipStr := q.Get(fmt.Sprintf("ip%d", i)) + ip := net.ParseIP(ipStr) + if ip == nil { break } @@ -248,7 +250,7 @@ func (clients *clientsContainer) handleFindClient(w http.ResponseWriter, r *http cj.Disallowed, cj.DisallowedRule = clients.dnsServer.IsBlockedIP(ip) } - el[ip] = cj + el[ipStr] = cj data = append(data, el) } @@ -267,7 +269,8 @@ func (clients *clientsContainer) handleFindClient(w http.ResponseWriter, r *http // findTemporary looks up the IP in temporary storages, like autohosts or // blocklists. -func (clients *clientsContainer) findTemporary(ip string) (cj clientJSON, found bool) { +func (clients *clientsContainer) findTemporary(ip net.IP) (cj clientJSON, found bool) { + ipStr := ip.String() ch, ok := clients.FindAutoClient(ip) if !ok { // It is still possible that the IP used to be in the runtime @@ -281,7 +284,7 @@ func (clients *clientsContainer) findTemporary(ip string) (cj clientJSON, found } cj = clientJSON{ - IDs: []string{ip}, + IDs: []string{ipStr}, Disallowed: disallowed, DisallowedRule: rule, } @@ -289,7 +292,7 @@ func (clients *clientsContainer) findTemporary(ip string) (cj clientJSON, found return cj, true } - cj = clientHostToJSON(ip, ch) + cj = clientHostToJSON(ipStr, ch) cj.Disallowed, cj.DisallowedRule = clients.dnsServer.IsBlockedIP(ip) return cj, true diff --git a/internal/home/config.go b/internal/home/config.go index f7b799dc..6c8381ab 100644 --- a/internal/home/config.go +++ b/internal/home/config.go @@ -2,6 +2,7 @@ package home import ( "io/ioutil" + "net" "os" "path/filepath" "sync" @@ -40,7 +41,7 @@ type configuration struct { // It's reset after config is parsed fileData []byte - BindHost string `yaml:"bind_host"` // BindHost is the IP address of the HTTP server to bind to + BindHost net.IP `yaml:"bind_host"` // BindHost is the IP address of the HTTP server to bind to BindPort int `yaml:"bind_port"` // BindPort is the port the HTTP server BetaBindPort int `yaml:"beta_bind_port"` // BetaBindPort is the port for new client Users []User `yaml:"users"` // Users that can access HTTP server @@ -74,7 +75,7 @@ type configuration struct { // field ordering is important -- yaml fields will mirror ordering from here type dnsConfig struct { - BindHost string `yaml:"bind_host"` + BindHost net.IP `yaml:"bind_host"` Port int `yaml:"port"` // time interval for statistics (in days) @@ -121,9 +122,9 @@ type tlsConfigSettings struct { var config = configuration{ BindPort: 3000, BetaBindPort: 0, - BindHost: "0.0.0.0", + BindHost: net.IP{0, 0, 0, 0}, DNS: dnsConfig{ - BindHost: "0.0.0.0", + BindHost: net.IP{0, 0, 0, 0}, Port: 53, StatsInterval: 1, FilteringConfig: dnsforward.FilteringConfig{ diff --git a/internal/home/control.go b/internal/home/control.go index 7caac5e6..f309663f 100644 --- a/internal/home/control.go +++ b/internal/home/control.go @@ -36,11 +36,12 @@ func httpError(w http.ResponseWriter, code int, format string, args ...interface // --------------- // dns run control // --------------- -func addDNSAddress(dnsAddresses *[]string, addr string) { +func addDNSAddress(dnsAddresses *[]string, addr net.IP) { + hostport := addr.String() if config.DNS.Port != 53 { - addr = fmt.Sprintf("%s:%d", addr, config.DNS.Port) + hostport = net.JoinHostPort(hostport, strconv.Itoa(config.DNS.Port)) } - *dnsAddresses = append(*dnsAddresses, addr) + *dnsAddresses = append(*dnsAddresses, hostport) } func handleStatus(w http.ResponseWriter, _ *http.Request) { diff --git a/internal/home/controlinstall.go b/internal/home/controlinstall.go index 2abfc4ba..ae93184c 100644 --- a/internal/home/controlinstall.go +++ b/internal/home/controlinstall.go @@ -31,7 +31,7 @@ type netInterfaceJSON struct { Name string `json:"name"` MTU int `json:"mtu"` HardwareAddr string `json:"hardware_address"` - Addresses []string `json:"ip_addresses"` + Addresses []net.IP `json:"ip_addresses"` Flags string `json:"flags"` } @@ -69,7 +69,7 @@ func (web *Web) handleInstallGetAddresses(w http.ResponseWriter, r *http.Request type checkConfigReqEnt struct { Port int `json:"port"` - IP string `json:"ip"` + IP net.IP `json:"ip"` Autofix bool `json:"autofix"` } @@ -138,7 +138,7 @@ func (web *Web) handleInstallCheckConfig(w http.ResponseWriter, r *http.Request) if err != nil { respData.DNS.Status = err.Error() - } else if reqData.DNS.IP != "0.0.0.0" { + } else if !reqData.DNS.IP.IsUnspecified() { respData.StaticIP = handleStaticIP(reqData.DNS.IP, reqData.SetStaticIP) } } @@ -154,7 +154,7 @@ func (web *Web) handleInstallCheckConfig(w http.ResponseWriter, r *http.Request) // handleStaticIP - handles static IP request // It either checks if we have a static IP // Or if set=true, it tries to set it -func handleStaticIP(ip string, set bool) staticIPJSON { +func handleStaticIP(ip net.IP, set bool) staticIPJSON { resp := staticIPJSON{} interfaceName := util.GetInterfaceByIP(ip) @@ -186,7 +186,7 @@ func handleStaticIP(ip string, set bool) staticIPJSON { if isStaticIP { resp.Static = "yes" } - resp.IP = util.GetSubnet(interfaceName) + resp.IP = util.GetSubnet(interfaceName).String() } return resp } @@ -262,7 +262,7 @@ func disableDNSStubListener() error { } type applyConfigReqEnt struct { - IP string `json:"ip"` + IP net.IP `json:"ip"` Port int `json:"port"` } @@ -297,7 +297,7 @@ func (web *Web) handleInstallConfigure(w http.ResponseWriter, r *http.Request) { } restartHTTP := true - if config.BindHost == newSettings.Web.IP && config.BindPort == newSettings.Web.Port { + if config.BindHost.Equal(newSettings.Web.IP) && config.BindPort == newSettings.Web.Port { // no need to rebind restartHTTP = false } @@ -307,7 +307,7 @@ func (web *Web) handleInstallConfigure(w http.ResponseWriter, r *http.Request) { err = util.CheckPortAvailable(newSettings.Web.IP, newSettings.Web.Port) if err != nil { httpError(w, http.StatusBadRequest, "Impossible to listen on IP:port %s due to %s", - net.JoinHostPort(newSettings.Web.IP, strconv.Itoa(newSettings.Web.Port)), err) + net.JoinHostPort(newSettings.Web.IP.String(), strconv.Itoa(newSettings.Web.Port)), err) return } @@ -388,18 +388,18 @@ func (web *Web) registerInstallHandlers() { // checkConfigReqEntBeta is a struct representing new client's config check // request entry. It supports multiple IP values unlike the checkConfigReqEnt. // -// TODO(e.burkov): this should removed with the API v1 when the appropriate +// TODO(e.burkov): This should removed with the API v1 when the appropriate // functionality will appear in default checkConfigReqEnt. type checkConfigReqEntBeta struct { Port int `json:"port"` - IP []string `json:"ip"` + IP []net.IP `json:"ip"` Autofix bool `json:"autofix"` } // checkConfigReqBeta is a struct representing new client's config check request // body. It uses checkConfigReqEntBeta instead of checkConfigReqEnt. // -// TODO(e.burkov): this should removed with the API v1 when the appropriate +// TODO(e.burkov): This should removed with the API v1 when the appropriate // functionality will appear in default checkConfigReq. type checkConfigReqBeta struct { Web checkConfigReqEntBeta `json:"web"` @@ -410,7 +410,7 @@ type checkConfigReqBeta struct { // handleInstallCheckConfigBeta is a substitution of /install/check_config // handler for new client. // -// TODO(e.burkov): this should removed with the API v1 when the appropriate +// TODO(e.burkov): This should removed with the API v1 when the appropriate // functionality will appear in default handleInstallCheckConfig. func (web *Web) handleInstallCheckConfigBeta(w http.ResponseWriter, r *http.Request) { reqData := checkConfigReqBeta{} @@ -456,17 +456,17 @@ func (web *Web) handleInstallCheckConfigBeta(w http.ResponseWriter, r *http.Requ // applyConfigReqEntBeta is a struct representing new client's config setting // request entry. It supports multiple IP values unlike the applyConfigReqEnt. // -// TODO(e.burkov): this should removed with the API v1 when the appropriate +// TODO(e.burkov): This should removed with the API v1 when the appropriate // functionality will appear in default applyConfigReqEnt. type applyConfigReqEntBeta struct { - IP []string `json:"ip"` + IP []net.IP `json:"ip"` Port int `json:"port"` } // applyConfigReqBeta is a struct representing new client's config setting // request body. It uses applyConfigReqEntBeta instead of applyConfigReqEnt. // -// TODO(e.burkov): this should removed with the API v1 when the appropriate +// TODO(e.burkov): This should removed with the API v1 when the appropriate // functionality will appear in default applyConfigReq. type applyConfigReqBeta struct { Web applyConfigReqEntBeta `json:"web"` @@ -478,7 +478,7 @@ type applyConfigReqBeta struct { // handleInstallConfigureBeta is a substitution of /install/configure handler // for new client. // -// TODO(e.burkov): this should removed with the API v1 when the appropriate +// TODO(e.burkov): This should removed with the API v1 when the appropriate // functionality will appear in default handleInstallConfigure. func (web *Web) handleInstallConfigureBeta(w http.ResponseWriter, r *http.Request) { reqData := applyConfigReqBeta{} @@ -523,7 +523,7 @@ func (web *Web) handleInstallConfigureBeta(w http.ResponseWriter, r *http.Reques // firstRunDataBeta is a struct representing new client's getting addresses // request body. It uses array of structs instead of map. // -// TODO(e.burkov): this should removed with the API v1 when the appropriate +// TODO(e.burkov): This should removed with the API v1 when the appropriate // functionality will appear in default firstRunData. type firstRunDataBeta struct { WebPort int `json:"web_port"` @@ -534,7 +534,7 @@ type firstRunDataBeta struct { // handleInstallConfigureBeta is a substitution of /install/get_addresses // handler for new client. // -// TODO(e.burkov): this should removed with the API v1 when the appropriate +// TODO(e.burkov): This should removed with the API v1 when the appropriate // functionality will appear in default handleInstallGetAddresses. func (web *Web) handleInstallGetAddressesBeta(w http.ResponseWriter, r *http.Request) { data := firstRunDataBeta{} @@ -570,7 +570,7 @@ func (web *Web) handleInstallGetAddressesBeta(w http.ResponseWriter, r *http.Req // registerBetaInstallHandlers registers the install handlers for new client // with the structures it supports. // -// TODO(e.burkov): this should removed with the API v1 when the appropriate +// TODO(e.burkov): This should removed with the API v1 when the appropriate // functionality will appear in default handlers. func (web *Web) registerBetaInstallHandlers() { Context.mux.HandleFunc("/control/install/get_addresses_beta", preInstall(ensureGET(web.handleInstallGetAddressesBeta))) diff --git a/internal/home/dns.go b/internal/home/dns.go index a9880629..82b844a4 100644 --- a/internal/home/dns.go +++ b/internal/home/dns.go @@ -55,8 +55,8 @@ func initDNSServer() error { filterConf := config.DNS.DnsfilterConf bindhost := config.DNS.BindHost - if config.DNS.BindHost == "0.0.0.0" { - bindhost = "127.0.0.1" + if config.DNS.BindHost.IsUnspecified() { + bindhost = net.IPv4(127, 0, 0, 1) } filterConf.ResolverAddress = fmt.Sprintf("%s:%d", bindhost, config.DNS.Port) filterConf.AutoHosts = &Context.autoHosts @@ -98,26 +98,24 @@ func isRunning() bool { } func onDNSRequest(d *proxy.DNSContext) { - ip := dnsforward.IPStringFromAddr(d.Addr) - if ip == "" { + ip := dnsforward.IPFromAddr(d.Addr) + if ip == nil { // This would be quite weird if we get here return } - ipAddr := net.ParseIP(ip) - if !ipAddr.IsLoopback() { + if !ip.IsLoopback() { Context.rdns.Begin(ip) } - if !Context.ipDetector.detectSpecialNetwork(ipAddr) { + if !Context.ipDetector.detectSpecialNetwork(ip) { Context.whois.Begin(ip) } } func generateServerConfig() (newconfig dnsforward.ServerConfig, err error) { - bindHost := net.ParseIP(config.DNS.BindHost) newconfig = dnsforward.ServerConfig{ - UDPListenAddr: &net.UDPAddr{IP: bindHost, Port: config.DNS.Port}, - TCPListenAddr: &net.TCPAddr{IP: bindHost, Port: config.DNS.Port}, + UDPListenAddr: &net.UDPAddr{IP: config.DNS.BindHost, Port: config.DNS.Port}, + TCPListenAddr: &net.TCPAddr{IP: config.DNS.BindHost, Port: config.DNS.Port}, FilteringConfig: config.DNS.FilteringConfig, ConfigModified: onConfigModified, HTTPRegister: httpRegister, @@ -131,20 +129,20 @@ func generateServerConfig() (newconfig dnsforward.ServerConfig, err error) { if tlsConf.PortDNSOverTLS != 0 { newconfig.TLSListenAddr = &net.TCPAddr{ - IP: bindHost, + IP: config.DNS.BindHost, Port: tlsConf.PortDNSOverTLS, } } if tlsConf.PortDNSOverQUIC != 0 { newconfig.QUICListenAddr = &net.UDPAddr{ - IP: bindHost, + IP: config.DNS.BindHost, Port: int(tlsConf.PortDNSOverQUIC), } } if tlsConf.PortDNSCrypt != 0 { - newconfig.DNSCryptConfig, err = newDNSCrypt(bindHost, tlsConf) + newconfig.DNSCryptConfig, err = newDNSCrypt(config.DNS.BindHost, tlsConf) if err != nil { // Don't wrap the error, because it's already // wrapped by newDNSCrypt. @@ -245,7 +243,7 @@ func getDNSEncryption() dnsEncryption { func getDNSAddresses() []string { dnsAddresses := []string{} - if config.DNS.BindHost == "0.0.0.0" { + if config.DNS.BindHost.IsUnspecified() { ifaces, e := util.GetValidNetInterfacesForWeb() if e != nil { log.Error("Couldn't get network interfaces: %v", e) @@ -276,10 +274,10 @@ func getDNSAddresses() []string { } // If a client has his own settings, apply them -func applyAdditionalFiltering(clientAddr string, setts *dnsfilter.RequestFilteringSettings) { +func applyAdditionalFiltering(clientAddr net.IP, setts *dnsfilter.RequestFilteringSettings) { Context.dnsFilter.ApplyBlockedServices(setts, nil, true) - if len(clientAddr) == 0 { + if clientAddr == nil { return } setts.ClientIP = clientAddr @@ -328,13 +326,11 @@ func startDNSServer() error { Context.queryLog.Start() const topClientsNumber = 100 // the number of clients to get - topClients := Context.stats.GetTopClientsIP(topClientsNumber) - for _, ip := range topClients { - ipAddr := net.ParseIP(ip) - if !ipAddr.IsLoopback() { + for _, ip := range Context.stats.GetTopClientsIP(topClientsNumber) { + if !ip.IsLoopback() { Context.rdns.Begin(ip) } - if !Context.ipDetector.detectSpecialNetwork(ipAddr) { + if !Context.ipDetector.detectSpecialNetwork(ip) { Context.whois.Begin(ip) } } diff --git a/internal/home/home.go b/internal/home/home.go index fbead57a..55d6c20d 100644 --- a/internal/home/home.go +++ b/internal/home/home.go @@ -206,7 +206,7 @@ func setupConfig(args options) { } // override bind host/port from the console - if args.bindHost != "" { + if args.bindHost != nil { config.BindHost = args.bindHost } if args.bindPort != 0 { @@ -575,36 +575,40 @@ func printHTTPAddresses(proto string) { port = strconv.Itoa(tlsConf.PortHTTPS) } + var hostStr string if proto == "https" && tlsConf.ServerName != "" { if tlsConf.PortHTTPS == 443 { log.Printf("Go to https://%s", tlsConf.ServerName) } else { log.Printf("Go to https://%s:%s", tlsConf.ServerName, port) } - } else if config.BindHost == "0.0.0.0" { + } else if config.BindHost.IsUnspecified() { log.Println("AdGuard Home is available on the following addresses:") ifaces, err := util.GetValidNetInterfacesForWeb() if err != nil { // That's weird, but we'll ignore it - log.Printf("Go to %s://%s", proto, net.JoinHostPort(config.BindHost, port)) + hostStr = config.BindHost.String() + log.Printf("Go to %s://%s", proto, net.JoinHostPort(hostStr, port)) if config.BetaBindPort != 0 { - log.Printf("Go to %s://%s (BETA)", proto, net.JoinHostPort(config.BindHost, strconv.Itoa(config.BetaBindPort))) + log.Printf("Go to %s://%s (BETA)", proto, net.JoinHostPort(hostStr, strconv.Itoa(config.BetaBindPort))) } return } for _, iface := range ifaces { for _, addr := range iface.Addresses { - log.Printf("Go to %s://%s", proto, net.JoinHostPort(addr, strconv.Itoa(config.BindPort))) + hostStr = addr.String() + log.Printf("Go to %s://%s", proto, net.JoinHostPort(hostStr, strconv.Itoa(config.BindPort))) if config.BetaBindPort != 0 { - log.Printf("Go to %s://%s (BETA)", proto, net.JoinHostPort(addr, strconv.Itoa(config.BetaBindPort))) + log.Printf("Go to %s://%s (BETA)", proto, net.JoinHostPort(hostStr, strconv.Itoa(config.BetaBindPort))) } } } } else { - log.Printf("Go to %s://%s", proto, net.JoinHostPort(config.BindHost, port)) + hostStr = config.BindHost.String() + log.Printf("Go to %s://%s", proto, net.JoinHostPort(hostStr, port)) if config.BetaBindPort != 0 { - log.Printf("Go to %s://%s (BETA)", proto, net.JoinHostPort(config.BindHost, strconv.Itoa(config.BetaBindPort))) + log.Printf("Go to %s://%s (BETA)", proto, net.JoinHostPort(hostStr, strconv.Itoa(config.BetaBindPort))) } } } diff --git a/internal/home/home_test.go b/internal/home/home_test.go index b21d7d46..344657e9 100644 --- a/internal/home/home_test.go +++ b/internal/home/home_test.go @@ -1,6 +1,6 @@ // +build !race -// TODO(e.burkov): remove this weird buildtag. +// TODO(e.burkov): Remove this weird buildtag. package home diff --git a/internal/home/options.go b/internal/home/options.go index 0493e856..897cbd08 100644 --- a/internal/home/options.go +++ b/internal/home/options.go @@ -2,6 +2,7 @@ package home import ( "fmt" + "net" "os" "strconv" @@ -13,7 +14,7 @@ type options struct { verbose bool // is verbose logging enabled configFilename string // path to the config file workDir string // path to the working directory where we will store the filters data and the querylog - bindHost string // host address to bind HTTP server on + bindHost net.IP // host address to bind HTTP server on bindPort int // port to serve HTTP pages on logFile string // Path to the log file. If empty, write to stdout. If "syslog", writes to syslog pidFile string // File name to save PID to @@ -54,10 +55,19 @@ type arg struct { // against its zero value and return nil if the parameter value is // zero otherwise they return a string slice of the parameter +func ipSliceOrNil(ip net.IP) []string { + if ip == nil { + return nil + } + + return []string{ip.String()} +} + func stringSliceOrNil(s string) []string { if s == "" { return nil } + return []string{s} } @@ -65,6 +75,7 @@ func intSliceOrNil(i int) []string { if i == 0 { return nil } + return []string{strconv.Itoa(i)} } @@ -72,6 +83,7 @@ func boolSliceOrNil(b bool) []string { if b { return []string{} } + return nil } @@ -96,8 +108,8 @@ var workDirArg = arg{ var hostArg = arg{ "Host address to bind HTTP server on", "host", "h", - func(o options, v string) (options, error) { o.bindHost = v; return o, nil }, nil, nil, - func(o options) []string { return stringSliceOrNil(o.bindHost) }, + func(o options, v string) (options, error) { o.bindHost = net.ParseIP(v); return o, nil }, nil, nil, + func(o options) []string { return ipSliceOrNil(o.bindHost) }, } var portArg = arg{ diff --git a/internal/home/options_test.go b/internal/home/options_test.go index afaa873f..f24dc816 100644 --- a/internal/home/options_test.go +++ b/internal/home/options_test.go @@ -2,6 +2,7 @@ package home import ( "fmt" + "net" "testing" ) @@ -65,14 +66,14 @@ func TestParseWorkDir(t *testing.T) { } func TestParseBindHost(t *testing.T) { - if testParseOk(t).bindHost != "" { + if testParseOk(t).bindHost != nil { t.Fatal("empty is no host") } - if testParseOk(t, "-h", "addr").bindHost != "addr" { + if !testParseOk(t, "-h", "1.2.3.4").bindHost.Equal(net.IP{1, 2, 3, 4}) { t.Fatal("-h is host") } testParseParamMissing(t, "-h") - if testParseOk(t, "--host", "addr").bindHost != "addr" { + if !testParseOk(t, "--host", "1.2.3.4").bindHost.Equal(net.IP{1, 2, 3, 4}) { t.Fatal("--host is host") } testParseParamMissing(t, "--host") @@ -204,7 +205,7 @@ func TestSerializeWorkDir(t *testing.T) { } func TestSerializeBindHost(t *testing.T) { - testSerialize(t, options{bindHost: "addr"}, "-h", "addr") + testSerialize(t, options{bindHost: net.IP{1, 2, 3, 4}}, "-h", "1.2.3.4") } func TestSerializeBindPort(t *testing.T) { diff --git a/internal/home/rdns.go b/internal/home/rdns.go index 05df66ef..c71f3822 100644 --- a/internal/home/rdns.go +++ b/internal/home/rdns.go @@ -2,6 +2,7 @@ package home import ( "encoding/binary" + "net" "strings" "time" @@ -15,7 +16,7 @@ import ( type RDNS struct { dnsServer *dnsforward.Server clients *clientsContainer - ipChannel chan string // pass data from DNS request handling thread to rDNS thread + ipChannel chan net.IP // pass data from DNS request handling thread to rDNS thread // Contains IP addresses of clients to be resolved by rDNS // If IP address is resolved, it stays here while it's inside Clients. @@ -35,15 +36,15 @@ func InitRDNS(dnsServer *dnsforward.Server, clients *clientsContainer) *RDNS { cconf.MaxCount = 10000 r.ipAddrs = cache.New(cconf) - r.ipChannel = make(chan string, 256) + r.ipChannel = make(chan net.IP, 256) go r.workerLoop() return &r } // Begin - add IP address to rDNS queue -func (r *RDNS) Begin(ip string) { +func (r *RDNS) Begin(ip net.IP) { now := uint64(time.Now().Unix()) - expire := r.ipAddrs.Get([]byte(ip)) + expire := r.ipAddrs.Get(ip) if len(expire) != 0 { exp := binary.BigEndian.Uint64(expire) if exp > now { @@ -54,7 +55,7 @@ func (r *RDNS) Begin(ip string) { expire = make([]byte, 8) const ttl = 1 * 60 * 60 binary.BigEndian.PutUint64(expire, now+ttl) - _ = r.ipAddrs.Set([]byte(ip), expire) + _ = r.ipAddrs.Set(ip, expire) if r.clients.Exists(ip, ClientSourceRDNS) { return @@ -70,7 +71,7 @@ func (r *RDNS) Begin(ip string) { } // Use rDNS to get hostname by IP address -func (r *RDNS) resolve(ip string) string { +func (r *RDNS) resolve(ip net.IP) string { log.Tracef("Resolving host for %s", ip) req := dns.Msg{} @@ -83,7 +84,7 @@ func (r *RDNS) resolve(ip string) string { }, } var err error - req.Question[0].Name, err = dns.ReverseAddr(ip) + req.Question[0].Name, err = dns.ReverseAddr(ip.String()) if err != nil { log.Debug("Error while calling dns.ReverseAddr(%s): %s", ip, err) return "" @@ -123,6 +124,6 @@ func (r *RDNS) workerLoop() { continue } - _, _ = r.clients.AddHost(ip, host, ClientSourceRDNS) + _, _ = r.clients.AddHost(ip.String(), host, ClientSourceRDNS) } } diff --git a/internal/home/rdns_test.go b/internal/home/rdns_test.go index 516b0ed5..53dd093d 100644 --- a/internal/home/rdns_test.go +++ b/internal/home/rdns_test.go @@ -1,6 +1,7 @@ package home import ( + "net" "testing" "github.com/AdguardTeam/AdGuardHome/internal/dnsforward" @@ -16,6 +17,6 @@ func TestResolveRDNS(t *testing.T) { clients := &clientsContainer{} rdns := InitRDNS(dns, clients) - r := rdns.resolve("1.1.1.1") + r := rdns.resolve(net.IP{1, 1, 1, 1}) assert.Equal(t, "one.one.one.one", r, r) } diff --git a/internal/home/web.go b/internal/home/web.go index 83fe9db4..72a2eb62 100644 --- a/internal/home/web.go +++ b/internal/home/web.go @@ -31,7 +31,7 @@ const ( type webConfig struct { firstRun bool - BindHost string + BindHost net.IP BindPort int BetaBindPort int PortHTTPS int @@ -161,10 +161,11 @@ func (web *Web) Start() { printHTTPAddresses("http") errs := make(chan error, 2) + hostStr := web.conf.BindHost.String() // we need to have new instance, because after Shutdown() the Server is not usable web.httpServer = &http.Server{ ErrorLog: log.StdLog("web: http", log.DEBUG), - Addr: net.JoinHostPort(web.conf.BindHost, strconv.Itoa(web.conf.BindPort)), + Addr: net.JoinHostPort(hostStr, strconv.Itoa(web.conf.BindPort)), Handler: withMiddlewares(Context.mux, limitRequestBody), ReadTimeout: web.conf.ReadTimeout, ReadHeaderTimeout: web.conf.ReadHeaderTimeout, @@ -177,7 +178,7 @@ func (web *Web) Start() { if web.conf.BetaBindPort != 0 { web.httpServerBeta = &http.Server{ ErrorLog: log.StdLog("web: http", log.DEBUG), - Addr: net.JoinHostPort(web.conf.BindHost, strconv.Itoa(web.conf.BetaBindPort)), + Addr: net.JoinHostPort(hostStr, strconv.Itoa(web.conf.BetaBindPort)), Handler: withMiddlewares(Context.mux, limitRequestBody, web.wrapIndexBeta), ReadTimeout: web.conf.ReadTimeout, ReadHeaderTimeout: web.conf.ReadHeaderTimeout, @@ -236,7 +237,7 @@ func (web *Web) tlsServerLoop() { web.httpsServer.cond.L.Unlock() // prepare HTTPS server - address := net.JoinHostPort(web.conf.BindHost, strconv.Itoa(web.conf.PortHTTPS)) + address := net.JoinHostPort(web.conf.BindHost.String(), strconv.Itoa(web.conf.PortHTTPS)) web.httpsServer.server = &http.Server{ ErrorLog: log.StdLog("web: https", log.DEBUG), Addr: address, diff --git a/internal/home/whois.go b/internal/home/whois.go index 4884d776..6c40ed54 100644 --- a/internal/home/whois.go +++ b/internal/home/whois.go @@ -26,7 +26,7 @@ const ( // Whois - module context type Whois struct { clients *clientsContainer - ipChan chan string + ipChan chan net.IP timeoutMsec uint // Contains IP addresses of clients @@ -46,7 +46,7 @@ func initWhois(clients *clientsContainer) *Whois { cconf.MaxCount = 10000 w.ipAddrs = cache.New(cconf) - w.ipChan = make(chan string, 255) + w.ipChan = make(chan net.IP, 255) go w.workerLoop() return &w } @@ -183,9 +183,9 @@ func (w *Whois) queryAll(target string) (string, error) { } // Request WHOIS information -func (w *Whois) process(ip string) [][]string { +func (w *Whois) process(ip net.IP) [][]string { data := [][]string{} - resp, err := w.queryAll(ip) + resp, err := w.queryAll(ip.String()) if err != nil { log.Debug("Whois: error: %s IP:%s", err, ip) return data @@ -209,7 +209,7 @@ func (w *Whois) process(ip string) [][]string { } // Begin - begin requesting WHOIS info -func (w *Whois) Begin(ip string) { +func (w *Whois) Begin(ip net.IP) { now := uint64(time.Now().Unix()) expire := w.ipAddrs.Get([]byte(ip)) if len(expire) != 0 { diff --git a/internal/querylog/decode.go b/internal/querylog/decode.go index ed721489..ad0948ba 100644 --- a/internal/querylog/decode.go +++ b/internal/querylog/decode.go @@ -22,9 +22,11 @@ var logEntryHandlers = map[string]logEntryHandler{ if !ok { return nil } - if len(ent.IP) == 0 { - ent.IP = v + + if ent.IP == nil { + ent.IP = net.ParseIP(v) } + return nil }, "T": func(t json.Token, ent *logEntry) error { diff --git a/internal/querylog/decode_test.go b/internal/querylog/decode_test.go index a599084d..40052fea 100644 --- a/internal/querylog/decode_test.go +++ b/internal/querylog/decode_test.go @@ -47,7 +47,7 @@ func TestDecodeLogEntry(t *testing.T) { assert.Nil(t, err) want := &logEntry{ - IP: "127.0.0.1", + IP: net.IPv4(127, 0, 0, 1), Time: time.Date(2020, 11, 25, 15, 55, 56, 519796000, time.UTC), QHost: "an.yandex.ru", QType: "A", diff --git a/internal/querylog/json.go b/internal/querylog/json.go index 3beeb0f1..152f6ce2 100644 --- a/internal/querylog/json.go +++ b/internal/querylog/json.go @@ -14,22 +14,19 @@ import ( // TODO(a.garipov): Use a proper structured approach here. // Get Client IP address -func (l *queryLog) getClientIP(clientIP string) string { - if l.conf.AnonymizeClientIP { - ip := net.ParseIP(clientIP) - if ip != nil { - ip4 := ip.To4() - const AnonymizeClientIP4Mask = 16 - const AnonymizeClientIP6Mask = 112 - if ip4 != nil { - clientIP = ip4.Mask(net.CIDRMask(AnonymizeClientIP4Mask, 32)).String() - } else { - clientIP = ip.Mask(net.CIDRMask(AnonymizeClientIP6Mask, 128)).String() - } +func (l *queryLog) getClientIP(ip net.IP) (clientIP net.IP) { + if l.conf.AnonymizeClientIP && ip != nil { + const AnonymizeClientIPv4Mask = 16 + const AnonymizeClientIPv6Mask = 112 + + if ip.To4() != nil { + return ip.Mask(net.CIDRMask(AnonymizeClientIPv4Mask, 32)) } + + return ip.Mask(net.CIDRMask(AnonymizeClientIPv6Mask, 128)) } - return clientIP + return ip } // jobject is a JSON object alias. @@ -153,9 +150,9 @@ func answerToMap(a *dns.Msg) (answers []jobject) { // try most common record types switch v := k.(type) { case *dns.A: - answer["value"] = v.A.String() + answer["value"] = v.A case *dns.AAAA: - answer["value"] = v.AAAA.String() + answer["value"] = v.AAAA case *dns.MX: answer["value"] = fmt.Sprintf("%v %v", v.Preference, v.Mx) case *dns.CNAME: diff --git a/internal/querylog/qlog.go b/internal/querylog/qlog.go index 97343006..30054a92 100644 --- a/internal/querylog/qlog.go +++ b/internal/querylog/qlog.go @@ -3,6 +3,7 @@ package querylog import ( "fmt" + "net" "os" "path/filepath" "strings" @@ -60,7 +61,7 @@ func NewClientProto(s string) (cp ClientProto, err error) { // logEntry - represents a single log entry type logEntry struct { - IP string `json:"IP"` // Client IP + IP net.IP `json:"IP"` // Client IP Time time.Time `json:"T"` QHost string `json:"QH"` @@ -147,7 +148,7 @@ func (l *queryLog) Add(params AddParams) { now := time.Now() entry := logEntry{ - IP: l.getClientIP(params.ClientIP.String()), + IP: l.getClientIP(params.ClientIP), Time: now, Result: *params.Result, diff --git a/internal/querylog/qlog_test.go b/internal/querylog/qlog_test.go index 0fa072c1..24d9064e 100644 --- a/internal/querylog/qlog_test.go +++ b/internal/querylog/qlog_test.go @@ -40,27 +40,27 @@ func TestQueryLog(t *testing.T) { l := newQueryLog(conf) // add disk entries - addEntry(l, "example.org", "1.1.1.1", "2.2.2.1") + addEntry(l, "example.org", net.IPv4(1, 1, 1, 1), net.IPv4(2, 2, 2, 1)) // write to disk (first file) _ = l.flushLogBuffer(true) // start writing to the second file _ = l.rotate() // add disk entries - addEntry(l, "example.org", "1.1.1.2", "2.2.2.2") + addEntry(l, "example.org", net.IPv4(1, 1, 1, 2), net.IPv4(2, 2, 2, 2)) // write to disk _ = l.flushLogBuffer(true) // add memory entries - addEntry(l, "test.example.org", "1.1.1.3", "2.2.2.3") - addEntry(l, "example.com", "1.1.1.4", "2.2.2.4") + addEntry(l, "test.example.org", net.IPv4(1, 1, 1, 3), net.IPv4(2, 2, 2, 3)) + addEntry(l, "example.com", net.IPv4(1, 1, 1, 4), net.IPv4(2, 2, 2, 4)) // get all entries params := newSearchParams() entries, _ := l.search(params) assert.Len(t, entries, 4) - assertLogEntry(t, entries[0], "example.com", "1.1.1.4", "2.2.2.4") - assertLogEntry(t, entries[1], "test.example.org", "1.1.1.3", "2.2.2.3") - assertLogEntry(t, entries[2], "example.org", "1.1.1.2", "2.2.2.2") - assertLogEntry(t, entries[3], "example.org", "1.1.1.1", "2.2.2.1") + assertLogEntry(t, entries[0], "example.com", net.IPv4(1, 1, 1, 4), net.IPv4(2, 2, 2, 4)) + assertLogEntry(t, entries[1], "test.example.org", net.IPv4(1, 1, 1, 3), net.IPv4(2, 2, 2, 3)) + assertLogEntry(t, entries[2], "example.org", net.IPv4(1, 1, 1, 2), net.IPv4(2, 2, 2, 2)) + assertLogEntry(t, entries[3], "example.org", net.IPv4(1, 1, 1, 1), net.IPv4(2, 2, 2, 1)) // search by domain (strict) params = newSearchParams() @@ -71,7 +71,7 @@ func TestQueryLog(t *testing.T) { }) entries, _ = l.search(params) assert.Len(t, entries, 1) - assertLogEntry(t, entries[0], "test.example.org", "1.1.1.3", "2.2.2.3") + assertLogEntry(t, entries[0], "test.example.org", net.IPv4(1, 1, 1, 3), net.IPv4(2, 2, 2, 3)) // search by domain (not strict) params = newSearchParams() @@ -82,9 +82,9 @@ func TestQueryLog(t *testing.T) { }) entries, _ = l.search(params) assert.Len(t, entries, 3) - assertLogEntry(t, entries[0], "test.example.org", "1.1.1.3", "2.2.2.3") - assertLogEntry(t, entries[1], "example.org", "1.1.1.2", "2.2.2.2") - assertLogEntry(t, entries[2], "example.org", "1.1.1.1", "2.2.2.1") + assertLogEntry(t, entries[0], "test.example.org", net.IPv4(1, 1, 1, 3), net.IPv4(2, 2, 2, 3)) + assertLogEntry(t, entries[1], "example.org", net.IPv4(1, 1, 1, 2), net.IPv4(2, 2, 2, 2)) + assertLogEntry(t, entries[2], "example.org", net.IPv4(1, 1, 1, 1), net.IPv4(2, 2, 2, 1)) // search by client IP (strict) params = newSearchParams() @@ -95,7 +95,7 @@ func TestQueryLog(t *testing.T) { }) entries, _ = l.search(params) assert.Len(t, entries, 1) - assertLogEntry(t, entries[0], "example.org", "1.1.1.2", "2.2.2.2") + assertLogEntry(t, entries[0], "example.org", net.IPv4(1, 1, 1, 2), net.IPv4(2, 2, 2, 2)) // search by client IP (part of) params = newSearchParams() @@ -106,10 +106,10 @@ func TestQueryLog(t *testing.T) { }) entries, _ = l.search(params) assert.Len(t, entries, 4) - assertLogEntry(t, entries[0], "example.com", "1.1.1.4", "2.2.2.4") - assertLogEntry(t, entries[1], "test.example.org", "1.1.1.3", "2.2.2.3") - assertLogEntry(t, entries[2], "example.org", "1.1.1.2", "2.2.2.2") - assertLogEntry(t, entries[3], "example.org", "1.1.1.1", "2.2.2.1") + assertLogEntry(t, entries[0], "example.com", net.IPv4(1, 1, 1, 4), net.IPv4(2, 2, 2, 4)) + assertLogEntry(t, entries[1], "test.example.org", net.IPv4(1, 1, 1, 3), net.IPv4(2, 2, 2, 3)) + assertLogEntry(t, entries[2], "example.org", net.IPv4(1, 1, 1, 2), net.IPv4(2, 2, 2, 2)) + assertLogEntry(t, entries[3], "example.org", net.IPv4(1, 1, 1, 1), net.IPv4(2, 2, 2, 1)) } func TestQueryLogOffsetLimit(t *testing.T) { @@ -124,13 +124,13 @@ func TestQueryLogOffsetLimit(t *testing.T) { // add 10 entries to the log for i := 0; i < 10; i++ { - addEntry(l, "second.example.org", "1.1.1.1", "2.2.2.1") + addEntry(l, "second.example.org", net.IPv4(1, 1, 1, 1), net.IPv4(2, 2, 2, 1)) } // write them to disk (first file) _ = l.flushLogBuffer(true) // add 10 more entries to the log (memory) for i := 0; i < 10; i++ { - addEntry(l, "first.example.org", "1.1.1.1", "2.2.2.1") + addEntry(l, "first.example.org", net.IPv4(1, 1, 1, 1), net.IPv4(2, 2, 2, 1)) } // First page @@ -178,7 +178,7 @@ func TestQueryLogMaxFileScanEntries(t *testing.T) { // add 10 entries to the log for i := 0; i < 10; i++ { - addEntry(l, "example.org", "1.1.1.1", "2.2.2.1") + addEntry(l, "example.org", net.IPv4(1, 1, 1, 1), net.IPv4(2, 2, 2, 1)) } // write them to disk (first file) _ = l.flushLogBuffer(true) @@ -204,9 +204,9 @@ func TestQueryLogFileDisabled(t *testing.T) { defer func() { _ = os.RemoveAll(conf.BaseDir) }() l := newQueryLog(conf) - addEntry(l, "example1.org", "1.1.1.1", "2.2.2.1") - addEntry(l, "example2.org", "1.1.1.1", "2.2.2.1") - addEntry(l, "example3.org", "1.1.1.1", "2.2.2.1") + addEntry(l, "example1.org", net.IPv4(1, 1, 1, 1), net.IPv4(2, 2, 2, 1)) + addEntry(l, "example2.org", net.IPv4(1, 1, 1, 1), net.IPv4(2, 2, 2, 1)) + addEntry(l, "example3.org", net.IPv4(1, 1, 1, 1), net.IPv4(2, 2, 2, 1)) // the oldest entry is now removed from mem buffer params := newSearchParams() @@ -216,7 +216,7 @@ func TestQueryLogFileDisabled(t *testing.T) { assert.Equal(t, "example2.org", ll[1].QHost) } -func addEntry(l *queryLog, host, answerStr, client string) { +func addEntry(l *queryLog, host string, answerStr, client net.IP) { q := dns.Msg{} q.Question = append(q.Question, dns.Question{ Name: host + ".", @@ -232,7 +232,7 @@ func addEntry(l *queryLog, host, answerStr, client string) { Rrtype: dns.TypeA, Class: dns.ClassINET, } - answer.A = net.ParseIP(answerStr) + answer.A = answerStr a.Answer = append(a.Answer, answer) res := dnsfilter.Result{ IsFiltered: true, @@ -248,13 +248,13 @@ func addEntry(l *queryLog, host, answerStr, client string) { Answer: &a, OrigAnswer: &a, Result: &res, - ClientIP: net.ParseIP(client), + ClientIP: client, Upstream: "upstream", } l.Add(params) } -func assertLogEntry(t *testing.T, entry *logEntry, host, answer, client string) bool { +func assertLogEntry(t *testing.T, entry *logEntry, host string, answer, client net.IP) bool { assert.Equal(t, host, entry.QHost) assert.Equal(t, client, entry.IP) assert.Equal(t, "A", entry.QType) @@ -263,9 +263,9 @@ func assertLogEntry(t *testing.T, entry *logEntry, host, answer, client string) msg := new(dns.Msg) assert.Nil(t, msg.Unpack(entry.Answer)) assert.Len(t, msg.Answer, 1) - ip := proxyutil.GetIPFromDNSRecord(msg.Answer[0]) + ip := proxyutil.GetIPFromDNSRecord(msg.Answer[0]).To16() assert.NotNil(t, ip) - assert.Equal(t, answer, ip.String()) + assert.Equal(t, answer, ip) return true } diff --git a/internal/querylog/searchcriteria.go b/internal/querylog/searchcriteria.go index f6b0ee98..1c2b26e3 100644 --- a/internal/querylog/searchcriteria.go +++ b/internal/querylog/searchcriteria.go @@ -94,16 +94,20 @@ func (c *searchCriteria) ctDomainOrClientCase(entry *logEntry) bool { if c.strict && qhost == searchVal { return true } + if !c.strict && strings.Contains(qhost, searchVal) { return true } - if c.strict && entry.IP == c.value { + ipStr := entry.IP.String() + if c.strict && ipStr == c.value { return true } - if !c.strict && strings.Contains(entry.IP, c.value) { + + if !c.strict && strings.Contains(ipStr, c.value) { return true } + return false } diff --git a/internal/stats/stats.go b/internal/stats/stats.go index 5cd5910d..1addbebd 100644 --- a/internal/stats/stats.go +++ b/internal/stats/stats.go @@ -48,7 +48,7 @@ type Stats interface { Update(e Entry) // Get IP addresses of the clients with the most number of requests - GetTopClientsIP(limit uint) []string + GetTopClientsIP(limit uint) []net.IP // WriteDiskConfig - write configuration WriteDiskConfig(dc *DiskConfig) diff --git a/internal/stats/stats_test.go b/internal/stats/stats_test.go index 47e68799..5fd9ff55 100644 --- a/internal/stats/stats_test.go +++ b/internal/stats/stats_test.go @@ -80,7 +80,7 @@ func TestStats(t *testing.T) { assert.EqualValues(t, 0.123456, d["avg_processing_time"].(float64)) topClients := s.GetTopClientsIP(2) - assert.Equal(t, "127.0.0.1", topClients[0]) + assert.True(t, net.IP{127, 0, 0, 1}.Equal(topClients[0])) s.clear() s.Close() diff --git a/internal/stats/unit.go b/internal/stats/unit.go index a8bd224c..35deb327 100644 --- a/internal/stats/unit.go +++ b/internal/stats/unit.go @@ -443,22 +443,19 @@ func (s *statsCtx) clear() { } // Get Client IP address -func (s *statsCtx) getClientIP(clientIP string) string { - if s.conf.AnonymizeClientIP { - ip := net.ParseIP(clientIP) - if ip != nil { - ip4 := ip.To4() - const AnonymizeClientIP4Mask = 16 - const AnonymizeClientIP6Mask = 112 - if ip4 != nil { - clientIP = ip4.Mask(net.CIDRMask(AnonymizeClientIP4Mask, 32)).String() - } else { - clientIP = ip.Mask(net.CIDRMask(AnonymizeClientIP6Mask, 128)).String() - } +func (s *statsCtx) getClientIP(ip net.IP) (clientIP net.IP) { + if s.conf.AnonymizeClientIP && ip != nil { + const AnonymizeClientIP4Mask = 16 + const AnonymizeClientIP6Mask = 112 + + if ip.To4() != nil { + return ip.Mask(net.CIDRMask(AnonymizeClientIP4Mask, 32)) } + + return ip.Mask(net.CIDRMask(AnonymizeClientIP6Mask, 128)) } - return clientIP + return ip } func (s *statsCtx) Update(e Entry) { @@ -468,7 +465,7 @@ func (s *statsCtx) Update(e Entry) { !(len(e.Client) == 4 || len(e.Client) == 16) { return } - client := s.getClientIP(e.Client.String()) + client := s.getClientIP(e.Client) s.unitLock.Lock() u := s.unit @@ -481,7 +478,7 @@ func (s *statsCtx) Update(e Entry) { u.blockedDomains[e.Domain]++ } - u.clients[client]++ + u.clients[client.String()]++ u.timeSum += uint64(e.Time) u.nTotal++ s.unitLock.Unlock() @@ -658,7 +655,7 @@ func (s *statsCtx) getData() map[string]interface{} { return d } -func (s *statsCtx) GetTopClientsIP(maxCount uint) []string { +func (s *statsCtx) GetTopClientsIP(maxCount uint) []net.IP { units, _ := s.loadUnits(s.conf.limit) if units == nil { return nil @@ -672,9 +669,9 @@ func (s *statsCtx) GetTopClientsIP(maxCount uint) []string { } } a := convertMapToArray(m, int(maxCount)) - d := []string{} + d := []net.IP{} for _, it := range a { - d = append(d, it.Name) + d = append(d, net.ParseIP(it.Name)) } return d } diff --git a/internal/sysutil/net_linux.go b/internal/sysutil/net_linux.go index 06d27eb2..8f47cf42 100644 --- a/internal/sysutil/net_linux.go +++ b/internal/sysutil/net_linux.go @@ -119,17 +119,13 @@ func ifacesStaticConfig(r io.Reader, ifaceName string) (has bool, err error) { } func ifaceSetStaticIP(ifaceName string) (err error) { - ip := util.GetSubnet(ifaceName) - if len(ip) == 0 { + ipNet := util.GetSubnet(ifaceName) + if ipNet.IP == nil { return errors.New("can't get IP address") } - ip4, _, err := net.ParseCIDR(ip) - if err != nil { - return err - } gatewayIP := GatewayIP(ifaceName) - add := updateStaticIPdhcpcdConf(ifaceName, ip, gatewayIP, ip4) + add := updateStaticIPdhcpcdConf(ifaceName, ipNet.String(), gatewayIP, ipNet.IP) body, err := ioutil.ReadFile("/etc/dhcpcd.conf") if err != nil { diff --git a/internal/util/autohosts_test.go b/internal/util/autohosts_test.go index 393646c7..b9632855 100644 --- a/internal/util/autohosts_test.go +++ b/internal/util/autohosts_test.go @@ -108,11 +108,11 @@ func TestAutoHostsFSNotify(t *testing.T) { ips = ah.Process("newhost", dns.TypeA) assert.NotNil(t, ips) assert.Len(t, ips, 1) - assert.Equal(t, "127.0.0.2", ips[0].String()) + assert.True(t, net.IP{127, 0, 0, 2}.Equal(ips[0])) } func TestIP(t *testing.T) { - assert.Equal(t, "127.0.0.1", DNSUnreverseAddr("1.0.0.127.in-addr.arpa").String()) + assert.True(t, net.IP{127, 0, 0, 1}.Equal(DNSUnreverseAddr("1.0.0.127.in-addr.arpa"))) assert.Equal(t, "::abcd:1234", DNSUnreverseAddr("4.3.2.1.d.c.b.a.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa").String()) assert.Equal(t, "::abcd:1234", DNSUnreverseAddr("4.3.2.1.d.c.B.A.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa").String()) diff --git a/internal/util/network.go b/internal/util/network.go index 1731ed08..f83bbd63 100644 --- a/internal/util/network.go +++ b/internal/util/network.go @@ -15,12 +15,12 @@ import ( // NetInterface represents a list of network interfaces type NetInterface struct { - Name string // Network interface name - MTU int // MTU - HardwareAddr string // Hardware address - Addresses []string // Array with the network interface addresses - Subnets []string // Array with CIDR addresses of this network interface - Flags string // Network interface flags (up, broadcast, etc) + Name string // Network interface name + MTU int // MTU + HardwareAddr string // Hardware address + Addresses []net.IP // Array with the network interface addresses + Subnets []*net.IPNet // Array with CIDR addresses of this network interface + Flags string // Network interface flags (up, broadcast, etc) } // GetValidNetInterfaces returns interfaces that are eligible for DNS and/or DHCP @@ -78,8 +78,8 @@ func GetValidNetInterfacesForWeb() ([]NetInterface, error) { if ipNet.IP.IsLinkLocalUnicast() { continue } - netIface.Addresses = append(netIface.Addresses, ipNet.IP.String()) - netIface.Subnets = append(netIface.Subnets, ipNet.String()) + netIface.Addresses = append(netIface.Addresses, ipNet.IP) + netIface.Subnets = append(netIface.Subnets, ipNet) } // Discard interfaces with no addresses @@ -91,8 +91,8 @@ func GetValidNetInterfacesForWeb() ([]NetInterface, error) { return netInterfaces, nil } -// GetInterfaceByIP - Get interface name by its IP address. -func GetInterfaceByIP(ip string) string { +// GetInterfaceByIP returns the name of interface containing provided ip. +func GetInterfaceByIP(ip net.IP) string { ifaces, err := GetValidNetInterfacesForWeb() if err != nil { return "" @@ -100,7 +100,7 @@ func GetInterfaceByIP(ip string) string { for _, iface := range ifaces { for _, addr := range iface.Addresses { - if ip == addr { + if ip.Equal(addr) { return iface.Name } } @@ -109,13 +109,13 @@ func GetInterfaceByIP(ip string) string { return "" } -// GetSubnet - Get IP address with netmask for the specified interface -// Returns an empty string if it fails to find it -func GetSubnet(ifaceName string) string { +// GetSubnet returns pointer to net.IPNet for the specified interface or nil if +// the search fails. +func GetSubnet(ifaceName string) *net.IPNet { netIfaces, err := GetValidNetInterfacesForWeb() if err != nil { log.Error("Could not get network interfaces info: %v", err) - return "" + return nil } for _, netIface := range netIfaces { @@ -124,12 +124,12 @@ func GetSubnet(ifaceName string) string { } } - return "" + return nil } // CheckPortAvailable - check if TCP port is available -func CheckPortAvailable(host string, port int) error { - ln, err := net.Listen("tcp", net.JoinHostPort(host, strconv.Itoa(port))) +func CheckPortAvailable(host net.IP, port int) error { + ln, err := net.Listen("tcp", net.JoinHostPort(host.String(), strconv.Itoa(port))) if err != nil { return err } @@ -142,8 +142,8 @@ func CheckPortAvailable(host string, port int) error { } // CheckPacketPortAvailable - check if UDP port is available -func CheckPacketPortAvailable(host string, port int) error { - ln, err := net.ListenPacket("udp", net.JoinHostPort(host, strconv.Itoa(port))) +func CheckPacketPortAvailable(host net.IP, port int) error { + ln, err := net.ListenPacket("udp", net.JoinHostPort(host.String(), strconv.Itoa(port))) if err != nil { return err } From d9482b75884e7c969419bfdb522103c69028adbc Mon Sep 17 00:00:00 2001 From: Eugene Burkov Date: Thu, 21 Jan 2021 19:55:41 +0300 Subject: [PATCH 053/141] Pull request: 2509 type-safety vol.2 Merge in DNS/adguard-home from 2509-type-safety-vol2 to master Updates #2509. Squashed commit of the following: commit c944e4e0a9949fc894c90b4bc1f739148a67fd9d Author: Eugene Burkov Date: Thu Jan 21 19:36:20 2021 +0300 all: imp docs commit e8ac1815c492b0a9434596e35a48755cac2b9f3b Author: Eugene Burkov Date: Wed Jan 20 12:38:48 2021 +0300 all: imp JSON encoding, decoding --- internal/home/control.go | 59 +++++++++++++++++--------------- internal/home/controlinstall.go | 56 +++++++++--------------------- internal/home/controlupdate.go | 58 ++++++++++++++++--------------- internal/stats/http.go | 37 ++++++++++++++------ internal/stats/stats_test.go | 35 ++++++++++--------- internal/stats/unit.go | 45 ++++++++++-------------- internal/updater/check.go | 44 +++++++++++++++--------- internal/updater/updater_test.go | 12 +++++-- internal/util/network.go | 54 ++++++++++++++++++----------- openapi/openapi.yaml | 15 +++++++- 10 files changed, 226 insertions(+), 189 deletions(-) diff --git a/internal/home/control.go b/internal/home/control.go index f309663f..49d6b31c 100644 --- a/internal/home/control.go +++ b/internal/home/control.go @@ -44,41 +44,44 @@ func addDNSAddress(dnsAddresses *[]string, addr net.IP) { *dnsAddresses = append(*dnsAddresses, hostport) } +// statusResponse is a response for /control/status endpoint. +type statusResponse struct { + DNSAddrs []string `json:"dns_addresses"` + DNSPort int `json:"dns_port"` + HTTPPort int `json:"http_port"` + IsProtectionEnabled bool `json:"protection_enabled"` + // TODO(e.burkov): Inspect if front-end doesn't requires this field as + // openapi.yaml declares. + IsDHCPAvailable bool `json:"dhcp_available"` + IsRunning bool `json:"running"` + Version string `json:"version"` + Language string `json:"language"` +} + func handleStatus(w http.ResponseWriter, _ *http.Request) { - c := dnsforward.FilteringConfig{} + resp := statusResponse{ + DNSAddrs: getDNSAddresses(), + DNSPort: config.DNS.Port, + HTTPPort: config.BindPort, + IsRunning: isRunning(), + Version: version.Version(), + Language: config.Language, + } + + var c *dnsforward.FilteringConfig if Context.dnsServer != nil { - Context.dnsServer.WriteDiskConfig(&c) + c = &dnsforward.FilteringConfig{} + Context.dnsServer.WriteDiskConfig(c) + resp.IsProtectionEnabled = c.ProtectionEnabled } - data := map[string]interface{}{ - "dns_addresses": getDNSAddresses(), - "http_port": config.BindPort, - "dns_port": config.DNS.Port, - "running": isRunning(), - "version": version.Version(), - "language": config.Language, - - "protection_enabled": c.ProtectionEnabled, + // IsDHCPAvailable field is now false by default for Windows. + if runtime.GOOS != "windows" { + resp.IsDHCPAvailable = Context.dhcpServer != nil } - if runtime.GOOS == "windows" { - // Set the DHCP to false explicitly, because Context.dhcpServer - // is probably not nil, despite the fact that there is no - // support for DHCP on Windows in AdGuardHome. - // - // See also the TODO in dhcpd.Create. - data["dhcp_available"] = false - } else { - data["dhcp_available"] = (Context.dhcpServer != nil) - } - - jsonVal, err := json.Marshal(data) - if err != nil { - httpError(w, http.StatusInternalServerError, "Unable to marshal status json: %s", err) - return - } w.Header().Set("Content-Type", "application/json") - _, err = w.Write(jsonVal) + err := json.NewEncoder(w).Encode(resp) if err != nil { httpError(w, http.StatusInternalServerError, "Unable to write response json: %s", err) return diff --git a/internal/home/controlinstall.go b/internal/home/controlinstall.go index ae93184c..da223ebd 100644 --- a/internal/home/controlinstall.go +++ b/internal/home/controlinstall.go @@ -21,23 +21,16 @@ import ( "github.com/AdguardTeam/golibs/log" ) -type firstRunData struct { - WebPort int `json:"web_port"` - DNSPort int `json:"dns_port"` - Interfaces map[string]interface{} `json:"interfaces"` +// getAddrsResponse is the response for /install/get_addresses endpoint. +type getAddrsResponse struct { + WebPort int `json:"web_port"` + DNSPort int `json:"dns_port"` + Interfaces map[string]*util.NetInterface `json:"interfaces"` } -type netInterfaceJSON struct { - Name string `json:"name"` - MTU int `json:"mtu"` - HardwareAddr string `json:"hardware_address"` - Addresses []net.IP `json:"ip_addresses"` - Flags string `json:"flags"` -} - -// Get initial installation settings +// handleInstallGetAddresses is the handler for /install/get_addresses endpoint. func (web *Web) handleInstallGetAddresses(w http.ResponseWriter, r *http.Request) { - data := firstRunData{} + data := getAddrsResponse{} data.WebPort = 80 data.DNSPort = 53 @@ -47,16 +40,9 @@ func (web *Web) handleInstallGetAddresses(w http.ResponseWriter, r *http.Request return } - data.Interfaces = make(map[string]interface{}) + data.Interfaces = make(map[string]*util.NetInterface) for _, iface := range ifaces { - ifaceJSON := netInterfaceJSON{ - Name: iface.Name, - MTU: iface.MTU, - HardwareAddr: iface.HardwareAddr, - Addresses: iface.Addresses, - Flags: iface.Flags, - } - data.Interfaces[iface.Name] = ifaceJSON + data.Interfaces[iface.Name] = iface } w.Header().Set("Content-Type", "application/json") @@ -520,15 +506,15 @@ func (web *Web) handleInstallConfigureBeta(w http.ResponseWriter, r *http.Reques web.handleInstallConfigure(w, r) } -// firstRunDataBeta is a struct representing new client's getting addresses +// getAddrsResponseBeta is a struct representing new client's getting addresses // request body. It uses array of structs instead of map. // // TODO(e.burkov): This should removed with the API v1 when the appropriate // functionality will appear in default firstRunData. -type firstRunDataBeta struct { - WebPort int `json:"web_port"` - DNSPort int `json:"dns_port"` - Interfaces []netInterfaceJSON `json:"interfaces"` +type getAddrsResponseBeta struct { + WebPort int `json:"web_port"` + DNSPort int `json:"dns_port"` + Interfaces []*util.NetInterface `json:"interfaces"` } // handleInstallConfigureBeta is a substitution of /install/get_addresses @@ -537,7 +523,7 @@ type firstRunDataBeta struct { // TODO(e.burkov): This should removed with the API v1 when the appropriate // functionality will appear in default handleInstallGetAddresses. func (web *Web) handleInstallGetAddressesBeta(w http.ResponseWriter, r *http.Request) { - data := firstRunDataBeta{} + data := getAddrsResponseBeta{} data.WebPort = 80 data.DNSPort = 53 @@ -547,17 +533,7 @@ func (web *Web) handleInstallGetAddressesBeta(w http.ResponseWriter, r *http.Req return } - data.Interfaces = make([]netInterfaceJSON, 0, len(ifaces)) - for _, iface := range ifaces { - ifaceJSON := netInterfaceJSON{ - Name: iface.Name, - MTU: iface.MTU, - HardwareAddr: iface.HardwareAddr, - Addresses: iface.Addresses, - Flags: iface.Flags, - } - data.Interfaces = append(data.Interfaces, ifaceJSON) - } + data.Interfaces = ifaces w.Header().Set("Content-Type", "application/json") err = json.NewEncoder(w).Encode(data) diff --git a/internal/home/controlupdate.go b/internal/home/controlupdate.go index a502b902..327f2f90 100644 --- a/internal/home/controlupdate.go +++ b/internal/home/controlupdate.go @@ -16,10 +16,6 @@ import ( "github.com/AdguardTeam/golibs/log" ) -type getVersionJSONRequest struct { - RecheckNow bool `json:"recheck_now"` -} - // temporaryError is the interface for temporary errors from the Go standard // library. type temporaryError interface { @@ -29,31 +25,34 @@ type temporaryError interface { // Get the latest available version from the Internet func handleGetVersionJSON(w http.ResponseWriter, r *http.Request) { + resp := &versionResponse{} if Context.disableUpdate { - resp := make(map[string]interface{}) - resp["disabled"] = true - d, _ := json.Marshal(resp) - _, _ = w.Write(d) + // w.Header().Set("Content-Type", "application/json") + resp.Disabled = true + _ = json.NewEncoder(w).Encode(resp) + // TODO(e.burkov): Add error handling and deal with headers. return } - req := getVersionJSONRequest{} + req := &struct { + Recheck bool `json:"recheck_now"` + }{} + var err error if r.ContentLength != 0 { - err = json.NewDecoder(r.Body).Decode(&req) + err = json.NewDecoder(r.Body).Decode(req) if err != nil { httpError(w, http.StatusBadRequest, "JSON parse: %s", err) return } } - var info updater.VersionInfo for i := 0; i != 3; i++ { func() { Context.controlLock.Lock() defer Context.controlLock.Unlock() - info, err = Context.updater.VersionInfo(req.RecheckNow) + resp.VersionInfo, err = Context.updater.VersionInfo(req.Recheck) }() if err != nil { @@ -76,13 +75,16 @@ func handleGetVersionJSON(w http.ResponseWriter, r *http.Request) { } if err != nil { vcu := Context.updater.VersionCheckURL() + // TODO(a.garipov): Figure out the purpose of %T verb. httpError(w, http.StatusBadGateway, "Couldn't get version check json from %s: %T %s\n", vcu, err, err) return } + resp.confirmAutoUpdate() + w.Header().Set("Content-Type", "application/json") - _, err = w.Write(getVersionResp(info)) + err = json.NewEncoder(w).Encode(resp) if err != nil { httpError(w, http.StatusInternalServerError, "Couldn't write body: %s", err) } @@ -109,21 +111,24 @@ func handleUpdate(w http.ResponseWriter, _ *http.Request) { go finishUpdate() } -// Convert version.json data to our JSON response -func getVersionResp(info updater.VersionInfo) []byte { - ret := make(map[string]interface{}) - ret["can_autoupdate"] = false - ret["new_version"] = info.NewVersion - ret["announcement"] = info.Announcement - ret["announcement_url"] = info.AnnouncementURL +// versionResponse is the response for /control/version.json endpoint. +type versionResponse struct { + Disabled bool `json:"disabled"` + updater.VersionInfo +} - if info.CanAutoUpdate { +// confirmAutoUpdate checks the real possibility of auto update. +func (vr *versionResponse) confirmAutoUpdate() { + if vr.CanAutoUpdate != nil && *vr.CanAutoUpdate { canUpdate := true - tlsConf := tlsConfigSettings{} - Context.tls.WriteDiskConfig(&tlsConf) + var tlsConf *tlsConfigSettings + if runtime.GOOS != "windows" { + tlsConf = &tlsConfigSettings{} + Context.tls.WriteDiskConfig(tlsConf) + } - if runtime.GOOS != "windows" && + if tlsConf != nil && ((tlsConf.Enabled && (tlsConf.PortHTTPS < 1024 || tlsConf.PortDNSOverTLS < 1024 || tlsConf.PortDNSOverQUIC < 1024)) || @@ -131,11 +136,8 @@ func getVersionResp(info updater.VersionInfo) []byte { config.DNS.Port < 1024) { canUpdate, _ = sysutil.CanBindPrivilegedPorts() } - ret["can_autoupdate"] = canUpdate + vr.CanAutoUpdate = &canUpdate } - - d, _ := json.Marshal(ret) - return d } // Complete an update procedure diff --git a/internal/stats/http.go b/internal/stats/http.go index 794bedf1..4d7c0b15 100644 --- a/internal/stats/http.go +++ b/internal/stats/http.go @@ -19,26 +19,43 @@ func httpError(r *http.Request, w http.ResponseWriter, code int, format string, http.Error(w, text, code) } -// Return data +// statsResponse is a response for getting statistics. +type statsResponse struct { + TimeUnits string `json:"time_units"` + + NumDNSQueries uint64 `json:"num_dns_queries"` + NumBlockedFiltering uint64 `json:"num_blocked_filtering"` + NumReplacedSafebrowsing uint64 `json:"num_replaced_safebrowsing"` + NumReplacedSafesearch uint64 `json:"num_replaced_safesearch"` + NumReplacedParental uint64 `json:"num_replaced_parental"` + + AvgProcessingTime float64 `json:"avg_processing_time"` + + TopQueried []map[string]uint64 `json:"top_queried_domains"` + TopClients []map[string]uint64 `json:"top_clients"` + TopBlocked []map[string]uint64 `json:"top_blocked_domains"` + + DNSQueries []uint64 `json:"dns_queries"` + + BlockedFiltering []uint64 `json:"blocked_filtering"` + ReplacedSafebrowsing []uint64 `json:"replaced_safebrowsing"` + ReplacedParental []uint64 `json:"replaced_parental"` +} + +// handleStats is a handler for getting statistics. func (s *statsCtx) handleStats(w http.ResponseWriter, r *http.Request) { start := time.Now() - d := s.getData() + response, ok := s.getData() log.Debug("Stats: prepared data in %v", time.Since(start)) - if d == nil { + if !ok { httpError(r, w, http.StatusInternalServerError, "Couldn't get statistics data") - return - } - data, err := json.Marshal(d) - if err != nil { - httpError(r, w, http.StatusInternalServerError, "json encode: %s", err) return } w.Header().Set("Content-Type", "application/json") - - _, err = w.Write(data) + err := json.NewEncoder(w).Encode(response) if err != nil { httpError(r, w, http.StatusInternalServerError, "json encode: %s", err) diff --git a/internal/stats/stats_test.go b/internal/stats/stats_test.go index 5fd9ff55..b4be4db0 100644 --- a/internal/stats/stats_test.go +++ b/internal/stats/stats_test.go @@ -50,34 +50,36 @@ func TestStats(t *testing.T) { e.Time = 123456 s.Update(e) - d := s.getData() + d, ok := s.getData() + assert.True(t, ok) + a := []uint64{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2} - assert.True(t, UIntArrayEquals(d["dns_queries"].([]uint64), a)) + assert.True(t, UIntArrayEquals(d.DNSQueries, a)) a = []uint64{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1} - assert.True(t, UIntArrayEquals(d["blocked_filtering"].([]uint64), a)) + assert.True(t, UIntArrayEquals(d.BlockedFiltering, a)) a = []uint64{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} - assert.True(t, UIntArrayEquals(d["replaced_safebrowsing"].([]uint64), a)) + assert.True(t, UIntArrayEquals(d.ReplacedSafebrowsing, a)) a = []uint64{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} - assert.True(t, UIntArrayEquals(d["replaced_parental"].([]uint64), a)) + assert.True(t, UIntArrayEquals(d.ReplacedParental, a)) - m := d["top_queried_domains"].([]map[string]uint64) + m := d.TopQueried assert.EqualValues(t, 1, m[0]["domain"]) - m = d["top_blocked_domains"].([]map[string]uint64) + m = d.TopBlocked assert.EqualValues(t, 1, m[0]["domain"]) - m = d["top_clients"].([]map[string]uint64) + m = d.TopClients assert.EqualValues(t, 2, m[0]["127.0.0.1"]) - assert.EqualValues(t, 2, d["num_dns_queries"].(uint64)) - assert.EqualValues(t, 1, d["num_blocked_filtering"].(uint64)) - assert.EqualValues(t, 0, d["num_replaced_safebrowsing"].(uint64)) - assert.EqualValues(t, 0, d["num_replaced_safesearch"].(uint64)) - assert.EqualValues(t, 0, d["num_replaced_parental"].(uint64)) - assert.EqualValues(t, 0.123456, d["avg_processing_time"].(float64)) + assert.EqualValues(t, 2, d.NumDNSQueries) + assert.EqualValues(t, 1, d.NumBlockedFiltering) + assert.EqualValues(t, 0, d.NumReplacedSafebrowsing) + assert.EqualValues(t, 0, d.NumReplacedSafesearch) + assert.EqualValues(t, 0, d.NumReplacedParental) + assert.EqualValues(t, 0.123456, d.AvgProcessingTime) topClients := s.GetTopClientsIP(2) assert.True(t, net.IP{127, 0, 0, 1}.Equal(topClients[0])) @@ -120,8 +122,9 @@ func TestLargeNumbers(t *testing.T) { } } - d := s.getData() - assert.EqualValues(t, int(hour)*n, d["num_dns_queries"]) + d, ok := s.getData() + assert.True(t, ok) + assert.EqualValues(t, int(hour)*n, d.NumDNSQueries) s.Close() os.Remove(conf.Filename) diff --git a/internal/stats/unit.go b/internal/stats/unit.go index 35deb327..962fe85b 100644 --- a/internal/stats/unit.go +++ b/internal/stats/unit.go @@ -545,10 +545,9 @@ func (s *statsCtx) loadUnits(limit uint32) ([]*unitDB, uint32) { * parental-blocked These values are just the sum of data for all units. */ -func (s *statsCtx) getData() map[string]interface{} { +func (s *statsCtx) getData() (statsResponse, bool) { limit := s.conf.limit - d := map[string]interface{}{} timeUnit := Hours if limit/24 > 7 { timeUnit = Days @@ -556,7 +555,7 @@ func (s *statsCtx) getData() map[string]interface{} { units, firstID := s.loadUnits(limit) if units == nil { - return nil + return statsResponse{}, false } // per time unit counters: @@ -604,18 +603,14 @@ func (s *statsCtx) getData() map[string]interface{} { log.Fatalf("len(dnsQueries) != limit: %d %d", len(dnsQueries), limit) } - statsData := map[string]interface{}{ - "dns_queries": dnsQueries, - "blocked_filtering": statsCollector(func(u *unitDB) (num uint64) { return u.NResult[RFiltered] }), - "replaced_safebrowsing": statsCollector(func(u *unitDB) (num uint64) { return u.NResult[RSafeBrowsing] }), - "replaced_parental": statsCollector(func(u *unitDB) (num uint64) { return u.NResult[RParental] }), - "top_queried_domains": topsCollector(maxDomains, func(u *unitDB) (pairs []countPair) { return u.Domains }), - "top_blocked_domains": topsCollector(maxDomains, func(u *unitDB) (pairs []countPair) { return u.BlockedDomains }), - "top_clients": topsCollector(maxClients, func(u *unitDB) (pairs []countPair) { return u.Clients }), - } - - for dataKey, dataValue := range statsData { - d[dataKey] = dataValue + data := statsResponse{ + DNSQueries: dnsQueries, + BlockedFiltering: statsCollector(func(u *unitDB) (num uint64) { return u.NResult[RFiltered] }), + ReplacedSafebrowsing: statsCollector(func(u *unitDB) (num uint64) { return u.NResult[RSafeBrowsing] }), + ReplacedParental: statsCollector(func(u *unitDB) (num uint64) { return u.NResult[RParental] }), + TopQueried: topsCollector(maxDomains, func(u *unitDB) (pairs []countPair) { return u.Domains }), + TopBlocked: topsCollector(maxDomains, func(u *unitDB) (pairs []countPair) { return u.BlockedDomains }), + TopClients: topsCollector(maxClients, func(u *unitDB) (pairs []countPair) { return u.Clients }), } // total counters: @@ -635,24 +630,22 @@ func (s *statsCtx) getData() map[string]interface{} { sum.NResult[RParental] += u.NResult[RParental] } - d["num_dns_queries"] = sum.NTotal - d["num_blocked_filtering"] = sum.NResult[RFiltered] - d["num_replaced_safebrowsing"] = sum.NResult[RSafeBrowsing] - d["num_replaced_safesearch"] = sum.NResult[RSafeSearch] - d["num_replaced_parental"] = sum.NResult[RParental] + data.NumDNSQueries = sum.NTotal + data.NumBlockedFiltering = sum.NResult[RFiltered] + data.NumReplacedSafebrowsing = sum.NResult[RSafeBrowsing] + data.NumReplacedSafesearch = sum.NResult[RSafeSearch] + data.NumReplacedParental = sum.NResult[RParental] - avgTime := float64(0) if timeN != 0 { - avgTime = float64(sum.TimeAvg/uint32(timeN)) / 1000000 + data.AvgProcessingTime = float64(sum.TimeAvg/uint32(timeN)) / 1000000 } - d["avg_processing_time"] = avgTime - d["time_units"] = "hours" + data.TimeUnits = "hours" if timeUnit == Days { - d["time_units"] = "days" + data.TimeUnits = "days" } - return d + return data, true } func (s *statsCtx) GetTopClientsIP(maxCount uint) []net.IP { diff --git a/internal/updater/check.go b/internal/updater/check.go index 6418ffb6..a23cfc05 100644 --- a/internal/updater/check.go +++ b/internal/updater/check.go @@ -15,11 +15,11 @@ const versionCheckPeriod = 8 * time.Hour // VersionInfo contains information about a new version. type VersionInfo struct { - NewVersion string - Announcement string - AnnouncementURL string - SelfUpdateMinVersion string - CanAutoUpdate bool + NewVersion string `json:"new_version,omitempty"` + Announcement string `json:"announcement,omitempty"` + AnnouncementURL string `json:"announcement_url,omitempty"` + SelfUpdateMinVersion string `json:"-"` + CanAutoUpdate *bool `json:"can_autoupdate,omitempty"` } // MaxResponseSize is responses on server's requests maximum length in bytes. @@ -64,27 +64,37 @@ func (u *Updater) VersionInfo(forceRecheck bool) (VersionInfo, error) { } func (u *Updater) parseVersionResponse(data []byte) (VersionInfo, error) { - info := VersionInfo{} - versionJSON := make(map[string]interface{}) + var canAutoUpdate bool + info := VersionInfo{ + CanAutoUpdate: &canAutoUpdate, + } + versionJSON := map[string]string{ + "version": "", + "announcement": "", + "announcement_url": "", + "selfupdate_min_version": "", + } err := json.Unmarshal(data, &versionJSON) if err != nil { return info, fmt.Errorf("version.json: %w", err) } - var ok1, ok2, ok3, ok4 bool - info.NewVersion, ok1 = versionJSON["version"].(string) - info.Announcement, ok2 = versionJSON["announcement"].(string) - info.AnnouncementURL, ok3 = versionJSON["announcement_url"].(string) - info.SelfUpdateMinVersion, ok4 = versionJSON["selfupdate_min_version"].(string) - if !ok1 || !ok2 || !ok3 || !ok4 { - return info, fmt.Errorf("version.json: invalid data") + for _, v := range versionJSON { + if v == "" { + return info, fmt.Errorf("version.json: invalid data") + } } + info.NewVersion = versionJSON["version"] + info.Announcement = versionJSON["announcement"] + info.AnnouncementURL = versionJSON["announcement_url"] + info.SelfUpdateMinVersion = versionJSON["selfupdate_min_version"] + packageURL, ok := u.downloadURL(versionJSON) if ok && info.NewVersion != u.version && strings.TrimPrefix(u.version, "v") >= strings.TrimPrefix(info.SelfUpdateMinVersion, "v") { - info.CanAutoUpdate = true + canAutoUpdate = true } u.newVersion = info.NewVersion @@ -94,7 +104,7 @@ func (u *Updater) parseVersionResponse(data []byte) (VersionInfo, error) { } // downloadURL returns the download URL for current build. -func (u *Updater) downloadURL(json map[string]interface{}) (string, bool) { +func (u *Updater) downloadURL(json map[string]string) (string, bool) { var key string if u.goarch == "arm" && u.goarm != "" { @@ -113,5 +123,5 @@ func (u *Updater) downloadURL(json map[string]interface{}) (string, bool) { return "", false } - return val.(string), true + return val, true } diff --git a/internal/updater/updater_test.go b/internal/updater/updater_test.go index abadc17b..adb12c2d 100644 --- a/internal/updater/updater_test.go +++ b/internal/updater/updater_test.go @@ -90,7 +90,9 @@ func TestUpdateGetVersion(t *testing.T) { assert.Equal(t, "AdGuard Home v0.103.0-beta.2 is now available!", info.Announcement) assert.Equal(t, "https://github.com/AdguardTeam/AdGuardHome/internal/releases", info.AnnouncementURL) assert.Equal(t, "v0.0", info.SelfUpdateMinVersion) - assert.True(t, info.CanAutoUpdate) + if assert.NotNil(t, info.CanAutoUpdate) { + assert.True(t, *info.CanAutoUpdate) + } // check cached _, err = u.VersionInfo(false) @@ -275,7 +277,9 @@ func TestUpdater_VersionInto_ARM(t *testing.T) { assert.Equal(t, "AdGuard Home v0.103.0-beta.2 is now available!", info.Announcement) assert.Equal(t, "https://github.com/AdguardTeam/AdGuardHome/internal/releases", info.AnnouncementURL) assert.Equal(t, "v0.0", info.SelfUpdateMinVersion) - assert.True(t, info.CanAutoUpdate) + if assert.NotNil(t, info.CanAutoUpdate) { + assert.True(t, *info.CanAutoUpdate) + } } func TestUpdater_VersionInto_MIPS(t *testing.T) { @@ -312,5 +316,7 @@ func TestUpdater_VersionInto_MIPS(t *testing.T) { assert.Equal(t, "AdGuard Home v0.103.0-beta.2 is now available!", info.Announcement) assert.Equal(t, "https://github.com/AdguardTeam/AdGuardHome/internal/releases", info.AnnouncementURL) assert.Equal(t, "v0.0", info.SelfUpdateMinVersion) - assert.True(t, info.CanAutoUpdate) + if assert.NotNil(t, info.CanAutoUpdate) { + assert.True(t, *info.CanAutoUpdate) + } } diff --git a/internal/util/network.go b/internal/util/network.go index f83bbd63..52507617 100644 --- a/internal/util/network.go +++ b/internal/util/network.go @@ -1,6 +1,7 @@ package util import ( + "encoding/json" "errors" "fmt" "net" @@ -13,14 +14,30 @@ import ( "github.com/AdguardTeam/golibs/log" ) -// NetInterface represents a list of network interfaces +// NetInterface represents an entry of network interfaces map. type NetInterface struct { - Name string // Network interface name - MTU int // MTU - HardwareAddr string // Hardware address - Addresses []net.IP // Array with the network interface addresses - Subnets []*net.IPNet // Array with CIDR addresses of this network interface - Flags string // Network interface flags (up, broadcast, etc) + MTU int `json:"mtu"` + Name string `json:"name"` + HardwareAddr net.HardwareAddr `json:"hardware_address"` + Flags net.Flags `json:"flags"` + // Array with the network interface addresses. + Addresses []net.IP `json:"ip_addresses,omitempty"` + // Array with IP networks for this network interface. + Subnets []*net.IPNet `json:"-"` +} + +// MarshalJSON implements the json.Marshaler interface for *NetInterface. +func (iface *NetInterface) MarshalJSON() ([]byte, error) { + type netInterface NetInterface + return json.Marshal(&struct { + HardwareAddr string `json:"hardware_address"` + Flags string `json:"flags"` + *netInterface + }{ + HardwareAddr: iface.HardwareAddr.String(), + Flags: iface.Flags.String(), + netInterface: (*netInterface)(iface), + }) } // GetValidNetInterfaces returns interfaces that are eligible for DNS and/or DHCP @@ -40,7 +57,7 @@ func GetValidNetInterfaces() ([]net.Interface, error) { // GetValidNetInterfacesForWeb returns interfaces that are eligible for DNS and WEB only // we do not return link-local addresses here -func GetValidNetInterfacesForWeb() ([]NetInterface, error) { +func GetValidNetInterfacesForWeb() ([]*NetInterface, error) { ifaces, err := GetValidNetInterfaces() if err != nil { return nil, fmt.Errorf("couldn't get interfaces: %w", err) @@ -49,7 +66,7 @@ func GetValidNetInterfacesForWeb() ([]NetInterface, error) { return nil, errors.New("couldn't find any legible interface") } - var netInterfaces []NetInterface + var netInterfaces []*NetInterface for _, iface := range ifaces { addrs, err := iface.Addrs() @@ -57,24 +74,21 @@ func GetValidNetInterfacesForWeb() ([]NetInterface, error) { return nil, fmt.Errorf("failed to get addresses for interface %s: %w", iface.Name, err) } - netIface := NetInterface{ - Name: iface.Name, + netIface := &NetInterface{ MTU: iface.MTU, - HardwareAddr: iface.HardwareAddr.String(), + Name: iface.Name, + HardwareAddr: iface.HardwareAddr, + Flags: iface.Flags, } - if iface.Flags != 0 { - netIface.Flags = iface.Flags.String() - } - - // Collect network interface addresses + // Collect network interface addresses. for _, addr := range addrs { ipNet, ok := addr.(*net.IPNet) if !ok { - // not an IPNet, should not happen + // Should be net.IPNet, this is weird. return nil, fmt.Errorf("got iface.Addrs() element %s that is not net.IPNet, it is %T", addr, addr) } - // ignore link-local + // Ignore link-local. if ipNet.IP.IsLinkLocalUnicast() { continue } @@ -82,7 +96,7 @@ func GetValidNetInterfacesForWeb() ([]NetInterface, error) { netIface.Subnets = append(netIface.Subnets, ipNet) } - // Discard interfaces with no addresses + // Discard interfaces with no addresses. if len(netIface.Addresses) != 0 { netInterfaces = append(netInterfaces, netIface) } diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index e41eac24..490fd65d 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -1452,7 +1452,13 @@ 'type': 'object' 'description': > Information about the latest available version of AdGuard Home. + 'required': + - 'disabled' 'properties': + 'disabled': + 'type': 'boolean' + 'description': > + If true then other fields doesn't appear. 'new_version': 'type': 'string' 'example': 'v0.9' @@ -1471,7 +1477,10 @@ 'properties': 'time_units': 'type': 'string' - 'description': 'Time units (hours | days)' + 'enum': + - 'hours' + - 'days' + 'description': 'Time units' 'example': 'hours' 'num_dns_queries': 'type': 'integer' @@ -1988,6 +1997,10 @@ 'properties': 'flags': 'type': 'string' + 'description': > + Flags could be any combination of the following values, divided by + the "|" character: "up", "broadcast", "loopback", "pointtopoint" and + "multicast". 'example': 'up|broadcast|multicast' 'hardware_address': 'type': 'string' From ecd9ef47b0b742ea74561b7e89a3bce431b23d05 Mon Sep 17 00:00:00 2001 From: Eugene Burkov Date: Fri, 22 Jan 2021 15:00:45 +0300 Subject: [PATCH 054/141] Pull request: 2509 type-safety vol.3 Merge in DNS/adguard-home from 2509-type-safety-vol2-1 to master Squashed commit of the following: commit d58efb32396328247e1bb044f2b01145530cd84c Author: Eugene Burkov Date: Fri Jan 22 14:27:54 2021 +0300 home: imp JSON encoding --- internal/home/clientshttp.go | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/internal/home/clientshttp.go b/internal/home/clientshttp.go index aecc68e5..e39f4767 100644 --- a/internal/home/clientshttp.go +++ b/internal/home/clientshttp.go @@ -22,7 +22,7 @@ type clientJSON struct { Upstreams []string `json:"upstreams"` - WhoisInfo map[string]interface{} `json:"whois_info"` + WhoisInfo map[string]string `json:"whois_info"` // Disallowed - if true -- client's IP is not disallowed // Otherwise, it is blocked. @@ -39,7 +39,7 @@ type clientHostJSON struct { Name string `json:"name"` Source string `json:"source"` - WhoisInfo map[string]interface{} `json:"whois_info"` + WhoisInfo map[string]string `json:"whois_info"` } type clientListJSON struct { @@ -75,7 +75,7 @@ func (clients *clientsContainer) handleGetClients(w http.ResponseWriter, _ *http cj.Source = "WHOIS" } - cj.WhoisInfo = make(map[string]interface{}) + cj.WhoisInfo = map[string]string{} for _, wi := range ch.WhoisInfo { cj.WhoisInfo[wi[0]] = wi[1] } @@ -140,7 +140,7 @@ func clientHostToJSON(ip string, ch ClientHost) clientJSON { IDs: []string{ip}, } - cj.WhoisInfo = make(map[string]interface{}) + cj.WhoisInfo = map[string]string{} for _, wi := range ch.WhoisInfo { cj.WhoisInfo[wi[0]] = wi[1] } @@ -228,7 +228,7 @@ func (clients *clientsContainer) handleUpdateClient(w http.ResponseWriter, r *ht // Get the list of clients by IP address list func (clients *clientsContainer) handleFindClient(w http.ResponseWriter, r *http.Request) { q := r.URL.Query() - data := []map[string]interface{}{} + data := []map[string]clientJSON{} for i := 0; ; i++ { ipStr := q.Get(fmt.Sprintf("ip%d", i)) ip := net.ParseIP(ipStr) @@ -236,7 +236,6 @@ func (clients *clientsContainer) handleFindClient(w http.ResponseWriter, r *http break } - el := map[string]interface{}{} c, ok := clients.Find(ip) var cj clientJSON if !ok { @@ -250,18 +249,13 @@ func (clients *clientsContainer) handleFindClient(w http.ResponseWriter, r *http cj.Disallowed, cj.DisallowedRule = clients.dnsServer.IsBlockedIP(ip) } - el[ipStr] = cj - data = append(data, el) - } - - js, err := json.Marshal(data) - if err != nil { - httpError(w, http.StatusInternalServerError, "json.Marshal: %s", err) - return + data = append(data, map[string]clientJSON{ + ipStr: cj, + }) } w.Header().Set("Content-Type", "application/json") - _, err = w.Write(js) + err := json.NewEncoder(w).Encode(data) if err != nil { httpError(w, http.StatusInternalServerError, "Couldn't write response: %s", err) } From 5dffef53cd0d343179cc0eda9bbd4f93b8d510e8 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Fri, 22 Jan 2021 17:30:08 +0300 Subject: [PATCH 055/141] Pull request: scripts: add doq, dnscrypt, and beta client ports to docker Merge in DNS/adguard-home from docker-ports to master Squashed commit of the following: commit 8f4f7cbd321d415634880fc1c23f50c488be907b Author: Ainar Garipov Date: Fri Jan 22 16:05:29 2021 +0300 scripts: add more ports, imp docs commit 758a95a20cfecdaa098589b95f2a938f8610c7d4 Author: Ainar Garipov Date: Fri Jan 22 15:17:21 2021 +0300 scripts: add doq, dnscrypt, and beta client ports to docker --- scripts/make/Dockerfile | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/scripts/make/Dockerfile b/scripts/make/Dockerfile index 7e9bec86..f019345f 100644 --- a/scripts/make/Dockerfile +++ b/scripts/make/Dockerfile @@ -33,7 +33,17 @@ COPY --chown=nobody:nogroup\ RUN setcap 'cap_net_bind_service=+eip' /opt/adguardhome/AdGuardHome -EXPOSE 53/tcp 53/udp 67/udp 68/udp 80/tcp 443/tcp 853/tcp 3000/tcp +# 53 : DNS +# 67, 68 : DHCP +# 80 : HTTP +# 443 : HTTPS, DNS-over-HTTPS, DNSCrypt +# 784 : DNS-over-QUIC +# 853 : DNS-over-TLS +# 3000 : HTTP alt +# 3001 : HTTP beta +# 5443 : DNSCrypt alt +EXPOSE 53/tcp 53/udp 67/udp 68/udp 80/tcp 443/tcp 443/udp 784/udp\ + 853/tcp 3000/tcp 3001/tcp 5443/tcp 5443/udp VOLUME ["/opt/adguardhome/conf", "/opt/adguardhome/work"] From 684017a75f918aa0dcd76792c34aecd35fa01b01 Mon Sep 17 00:00:00 2001 From: Eugene Burkov Date: Fri, 22 Jan 2021 17:56:41 +0300 Subject: [PATCH 056/141] all: log changes --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a7951ca..1b0d565a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,8 +44,8 @@ and this project adheres to improve error response ([#2358]). - Improved HTTP requests handling and timeouts ([#2343]). - Our snap package now uses the `core20` image as its base ([#2306]). -- New build system and various internal improvements ([#2276], [#2271], - [#2297]). +- New build system and various internal improvements ([#2271], [#2276], [#2297], + [#2509]). [#2231]: https://github.com/AdguardTeam/AdGuardHome/issues/2231 [#2276]: https://github.com/AdguardTeam/AdGuardHome/issues/2276 @@ -56,6 +56,7 @@ and this project adheres to [#2358]: https://github.com/AdguardTeam/AdGuardHome/issues/2358 [#2391]: https://github.com/AdguardTeam/AdGuardHome/issues/2391 [#2394]: https://github.com/AdguardTeam/AdGuardHome/issues/2394 +[#2509]: https://github.com/AdguardTeam/AdGuardHome/issues/2509 ### Deprecated From e71019a1f3548fa834e5538e8ad4ce173baabdc4 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Mon, 25 Jan 2021 14:09:29 +0300 Subject: [PATCH 057/141] Pull request: all: imp dev version handling Merge in DNS/adguard-home from fix-version to master Squashed commit of the following: commit ecef63315fb49ae33b4c3f13c0e0be0668340e2b Author: Ainar Garipov Date: Fri Jan 22 18:59:17 2021 +0300 updater: imp tests commit f5243918567430e467c44a48e45169db4560b58b Author: Ainar Garipov Date: Fri Jan 22 18:48:10 2021 +0300 all: imp dev version handling --- internal/home/config.go | 2 +- internal/home/home.go | 3 ++- internal/updater/updater_test.go | 13 +++++++------ internal/version/version.go | 10 +++++++++- 4 files changed, 19 insertions(+), 9 deletions(-) diff --git a/internal/home/config.go b/internal/home/config.go index 6c8381ab..afe96812 100644 --- a/internal/home/config.go +++ b/internal/home/config.go @@ -179,7 +179,7 @@ func initConfig() { config.DHCP.Conf4.ICMPTimeout = 1000 config.DHCP.Conf6.LeaseDuration = 86400 - if ch := version.Channel(); ch == "edge" || ch == "development" { + if ch := version.Channel(); ch == version.ChannelEdge || ch == version.ChannelDevelopment { config.BetaBindPort = 3001 } } diff --git a/internal/home/home.go b/internal/home/home.go index 55d6c20d..4ece8f85 100644 --- a/internal/home/home.go +++ b/internal/home/home.go @@ -126,7 +126,8 @@ func Main() { func setupContext(args options) { Context.runningAsService = args.runningAsService - Context.disableUpdate = args.disableUpdate + Context.disableUpdate = args.disableUpdate || + version.Channel() == version.ChannelDevelopment Context.firstRun = detectFirstRun() if Context.firstRun { diff --git a/internal/updater/updater_test.go b/internal/updater/updater_test.go index adb12c2d..a8923ee2 100644 --- a/internal/updater/updater_test.go +++ b/internal/updater/updater_test.go @@ -12,6 +12,7 @@ import ( "testing" "github.com/AdguardTeam/AdGuardHome/internal/testutil" + "github.com/AdguardTeam/AdGuardHome/internal/version" "github.com/stretchr/testify/assert" ) @@ -72,7 +73,7 @@ func TestUpdateGetVersion(t *testing.T) { u := NewUpdater(&Config{ Client: &http.Client{}, Version: "v0.103.0-beta.1", - Channel: "beta", + Channel: version.ChannelBeta, GOARCH: "arm", GOOS: "linux", }) @@ -80,7 +81,7 @@ func TestUpdateGetVersion(t *testing.T) { fakeURL := &url.URL{ Scheme: "http", Host: net.JoinHostPort("127.0.0.1", lport), - Path: path.Join("adguardhome", "beta", "version.json"), + Path: path.Join("adguardhome", version.ChannelBeta, "version.json"), } u.versionCheckURL = fakeURL.String() @@ -258,7 +259,7 @@ func TestUpdater_VersionInto_ARM(t *testing.T) { u := NewUpdater(&Config{ Client: &http.Client{}, Version: "v0.103.0-beta.1", - Channel: "beta", + Channel: version.ChannelBeta, GOARCH: "arm", GOOS: "linux", GOARM: "7", @@ -267,7 +268,7 @@ func TestUpdater_VersionInto_ARM(t *testing.T) { fakeURL := &url.URL{ Scheme: "http", Host: net.JoinHostPort("127.0.0.1", lport), - Path: path.Join("adguardhome", "beta", "version.json"), + Path: path.Join("adguardhome", version.ChannelBeta, "version.json"), } u.versionCheckURL = fakeURL.String() @@ -297,7 +298,7 @@ func TestUpdater_VersionInto_MIPS(t *testing.T) { u := NewUpdater(&Config{ Client: &http.Client{}, Version: "v0.103.0-beta.1", - Channel: "beta", + Channel: version.ChannelBeta, GOARCH: "mips", GOOS: "linux", GOMIPS: "softfloat", @@ -306,7 +307,7 @@ func TestUpdater_VersionInto_MIPS(t *testing.T) { fakeURL := &url.URL{ Scheme: "http", Host: net.JoinHostPort("127.0.0.1", lport), - Path: path.Join("adguardhome", "beta", "version.json"), + Path: path.Join("adguardhome", version.ChannelBeta, "version.json"), } u.versionCheckURL = fakeURL.String() diff --git a/internal/version/version.go b/internal/version/version.go index 25fff528..df79a9c0 100644 --- a/internal/version/version.go +++ b/internal/version/version.go @@ -13,12 +13,20 @@ import ( // TODO(a.garipov): Find out if we can get GOARM and GOMIPS values the same way // we can GOARCH and GOOS. var ( - channel string + channel string = ChannelDevelopment goarm string gomips string version string ) +// Channel constants. +const ( + ChannelDevelopment = "development" + ChannelEdge = "edge" + ChannelBeta = "beta" + ChannelRelease = "release" +) + // Channel returns the current AdGuard Home release channel. func Channel() (v string) { return channel From abf8f65f05ae51119909a90380fce5b18d3ec663 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Mon, 25 Jan 2021 18:07:49 +0300 Subject: [PATCH 058/141] Pull request: Remove Docker VOLUME instruction for more flexibility Merge in DNS/adguard-home from 2589-docker-volumes to master Squashed commit of the following: commit 5bff6ffa3138e9c09b786e4ff26fe286daa78c05 Merge: f0e9a74c e71019a1 Author: Ainar Garipov Date: Mon Jan 25 14:45:15 2021 +0300 Merge branch 'master' into 2589-docker-volumes commit f0e9a74cc3d8baecaac68730b684e65ec3178886 Author: Ainar Garipov Date: Mon Jan 25 14:44:07 2021 +0300 all: doc changes commit 76e538ca5f086fa99328dc10a05e7ca581457192 Author: Alex Povel Date: Thu Jan 21 15:07:11 2021 +0100 Remove Docker VOLUME instruction for more flexibility Inheriting from the base image is made easier without the VOLUME instruction, since it cannot be reverted. All guides contain `--volume` explanations/usage examples anyway, so the VOLUME instructions in the Dockerfile aren't necessary. See also https://stackoverflow.com/a/62068396/11477374 for more examples for why that instruction can be harmful. --- CHANGELOG.md | 5 ++++- scripts/make/Dockerfile | 2 -- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b0d565a..fb8cb3d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,8 @@ and this project adheres to ### Changed +- Stopped mounting together the directories `/opt/adguardhome/conf` and + `/opt/adguardhome/work` in our Docker images ([#2589]). - When `dns.bogus_nxdomain` option is used, the server will now transform responses if there is at least one bogus address instead of all of them ([#2394]). The new behavior is the same as in `dnsmasq`. @@ -48,8 +50,8 @@ and this project adheres to [#2509]). [#2231]: https://github.com/AdguardTeam/AdGuardHome/issues/2231 -[#2276]: https://github.com/AdguardTeam/AdGuardHome/issues/2276 [#2271]: https://github.com/AdguardTeam/AdGuardHome/issues/2271 +[#2276]: https://github.com/AdguardTeam/AdGuardHome/issues/2276 [#2297]: https://github.com/AdguardTeam/AdGuardHome/issues/2297 [#2306]: https://github.com/AdguardTeam/AdGuardHome/issues/2306 [#2343]: https://github.com/AdguardTeam/AdGuardHome/issues/2343 @@ -57,6 +59,7 @@ and this project adheres to [#2391]: https://github.com/AdguardTeam/AdGuardHome/issues/2391 [#2394]: https://github.com/AdguardTeam/AdGuardHome/issues/2394 [#2509]: https://github.com/AdguardTeam/AdGuardHome/issues/2509 +[#2589]: https://github.com/AdguardTeam/AdGuardHome/issues/2589 ### Deprecated diff --git a/scripts/make/Dockerfile b/scripts/make/Dockerfile index f019345f..ce10b621 100644 --- a/scripts/make/Dockerfile +++ b/scripts/make/Dockerfile @@ -45,8 +45,6 @@ RUN setcap 'cap_net_bind_service=+eip' /opt/adguardhome/AdGuardHome EXPOSE 53/tcp 53/udp 67/udp 68/udp 80/tcp 443/tcp 443/udp 784/udp\ 853/tcp 3000/tcp 3001/tcp 5443/tcp 5443/udp -VOLUME ["/opt/adguardhome/conf", "/opt/adguardhome/work"] - WORKDIR /opt/adguardhome/work ENTRYPOINT ["/opt/adguardhome/AdGuardHome"] From 44c7221ae932a35954ed137db0e952caff809e21 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Tue, 26 Jan 2021 18:35:51 +0300 Subject: [PATCH 059/141] Pull request: all: rm ignored test, outdated tag Merge in DNS/adguard-home from rm-outdated to master Squashed commit of the following: commit 38669542f98a8267b06197518078a8c06de670b7 Author: Ainar Garipov Date: Tue Jan 26 18:04:08 2021 +0300 all: rm ignored test, outdated tag --- internal/home/controlupdate_test.go | 102 ---------------------------- internal/sysutil/syslog_others.go | 2 +- internal/sysutil/syslog_windows.go | 2 +- 3 files changed, 2 insertions(+), 104 deletions(-) delete mode 100644 internal/home/controlupdate_test.go diff --git a/internal/home/controlupdate_test.go b/internal/home/controlupdate_test.go deleted file mode 100644 index 45112f50..00000000 --- a/internal/home/controlupdate_test.go +++ /dev/null @@ -1,102 +0,0 @@ -// +build ignore - -package home - -import ( - "os" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestDoUpdate(t *testing.T) { - config.DNS.Port = 0 - Context.workDir = "..." // set absolute path - newver := "v0.96" - - data := `{ - "version": "v0.96", - "announcement": "AdGuard Home v0.96 is now available!", - "announcement_url": "", - "download_windows_amd64": "https://static.adguard.com/adguardhome/beta/AdGuardHome_windows_amd64.zip", - "download_windows_386": "https://static.adguard.com/adguardhome/beta/AdGuardHome_windows_386.zip", - "download_darwin_amd64": "https://static.adguard.com/adguardhome/beta/AdGuardHome_darwin_amd64.zip", - "download_darwin_386": "https://static.adguard.com/adguardhome/beta/AdGuardHome_darwin_386.zip", - "download_linux_amd64": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_amd64.tar.gz", - "download_linux_386": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_386.tar.gz", - "download_linux_arm": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv6.tar.gz", - "download_linux_armv5": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv5.tar.gz", - "download_linux_armv6": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv6.tar.gz", - "download_linux_armv7": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv7.tar.gz", - "download_linux_arm64": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_arm64.tar.gz", - "download_linux_mips": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mips_softfloat.tar.gz", - "download_linux_mipsle": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mipsle_softfloat.tar.gz", - "download_linux_mips64": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mips64_softfloat.tar.gz", - "download_linux_mips64le": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mips64le_softfloat.tar.gz", - "download_freebsd_386": "https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_386.tar.gz", - "download_freebsd_amd64": "https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_amd64.tar.gz", - "download_freebsd_arm": "https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_armv6.tar.gz", - "download_freebsd_armv5": "https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_armv5.tar.gz", - "download_freebsd_armv6": "https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_armv6.tar.gz", - "download_freebsd_armv7": "https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_armv7.tar.gz", - "download_freebsd_arm64": "https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_arm64.tar.gz" - }` - uu, err := getUpdateInfo([]byte(data)) - if err != nil { - t.Fatalf("getUpdateInfo: %s", err) - } - - u := updateInfo{ - pkgURL: "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv6.tar.gz", - pkgName: Context.workDir + "/agh-update-" + newver + "/AdGuardHome_linux_amd64.tar.gz", - newVer: newver, - updateDir: Context.workDir + "/agh-update-" + newver, - backupDir: Context.workDir + "/agh-backup", - configName: Context.workDir + "/AdGuardHome.yaml", - updateConfigName: Context.workDir + "/agh-update-" + newver + "/AdGuardHome/internal/AdGuardHome.yaml", - curBinName: Context.workDir + "/AdGuardHome", - bkpBinName: Context.workDir + "/agh-backup/AdGuardHome", - newBinName: Context.workDir + "/agh-update-" + newver + "/AdGuardHome/internal/AdGuardHome", - } - - assert.Equal(t, uu.pkgURL, u.pkgURL) - assert.Equal(t, uu.pkgName, u.pkgName) - assert.Equal(t, uu.newVer, u.newVer) - assert.Equal(t, uu.updateDir, u.updateDir) - assert.Equal(t, uu.backupDir, u.backupDir) - assert.Equal(t, uu.configName, u.configName) - assert.Equal(t, uu.updateConfigName, u.updateConfigName) - assert.Equal(t, uu.curBinName, u.curBinName) - assert.Equal(t, uu.bkpBinName, u.bkpBinName) - assert.Equal(t, uu.newBinName, u.newBinName) - - e := doUpdate(&u) - if e != nil { - t.Fatalf("FAILED: %s", e) - } - os.RemoveAll(u.backupDir) -} - -func TestTargzFileUnpack(t *testing.T) { - fn := "../dist/AdGuardHome_linux_amd64.tar.gz" - outdir := "../test-unpack" - defer os.RemoveAll(outdir) - _ = os.Mkdir(outdir, 0o755) - files, e := targzFileUnpack(fn, outdir) - if e != nil { - t.Fatalf("FAILED: %s", e) - } - t.Logf("%v", files) -} - -func TestZipFileUnpack(t *testing.T) { - fn := "../dist/AdGuardHome_windows_amd64.zip" - outdir := "../test-unpack" - _ = os.Mkdir(outdir, 0o755) - files, e := zipFileUnpack(fn, outdir) - if e != nil { - t.Fatalf("FAILED: %s", e) - } - t.Logf("%v", files) - os.RemoveAll(outdir) -} diff --git a/internal/sysutil/syslog_others.go b/internal/sysutil/syslog_others.go index 0e0e1c3f..91fe27cf 100644 --- a/internal/sysutil/syslog_others.go +++ b/internal/sysutil/syslog_others.go @@ -1,4 +1,4 @@ -// +build !windows,!nacl,!plan9 +// +build !windows,!plan9 package sysutil diff --git a/internal/sysutil/syslog_windows.go b/internal/sysutil/syslog_windows.go index 2160ea43..1d5cd03f 100644 --- a/internal/sysutil/syslog_windows.go +++ b/internal/sysutil/syslog_windows.go @@ -1,4 +1,4 @@ -// +build windows nacl plan9 +// +build windows plan9 package sysutil From c215b820046e6da5fd50c06149c904cd509066c8 Mon Sep 17 00:00:00 2001 From: Eugene Burkov Date: Tue, 26 Jan 2021 19:44:19 +0300 Subject: [PATCH 060/141] Pull request: 2552 rm context.TODO() instances Merge in DNS/adguard-home from 2552-context to master Closes #2552. Squashed commit of the following: commit 3d1cef33da529f4611869c4a0f2f294a3c8afcaf Author: Eugene Burkov Date: Tue Jan 26 19:28:23 2021 +0300 all: fix docs commit d08c78cf4b96419b928e73c497768f40c9e47bc2 Author: Eugene Burkov Date: Tue Jan 26 19:22:00 2021 +0300 all: doc changes commit c2814f4d0025be74f38299e7e66e7c0193b6c15f Merge: 100a1a09 44c7221a Author: Eugene Burkov Date: Tue Jan 26 19:12:55 2021 +0300 Merge branch 'master' into 2552-context commit 100a1a0957bc22bfaccb1693e6b9b1c5cb53ed13 Author: Eugene Burkov Date: Tue Jan 26 19:10:03 2021 +0300 home: imp docs, fix naming commit 22717abe6c0e4c1016a53ff2fac1689d0762c462 Author: Eugene Burkov Date: Tue Jan 26 18:14:07 2021 +0300 home: improve code quality commit 5c96f77a2b315e2c1ad4a11cc7a64f61bdba52a3 Author: Eugene Burkov Date: Mon Jan 25 20:28:51 2021 +0300 home: add docs commit 323fc013a57a5c06ec391003133b12f4eb2721cd Author: Eugene Burkov Date: Mon Jan 25 14:50:11 2021 +0300 home: rm context.TODO() instances --- CHANGELOG.md | 3 ++- internal/home/controlinstall.go | 30 +++++++++++++++++++------- internal/home/controlupdate.go | 17 ++++++++++----- internal/home/home.go | 9 ++++---- internal/home/home_test.go | 2 +- internal/home/tls.go | 24 +++++++++++++++------ internal/home/web.go | 38 ++++++++++++++++++++++----------- internal/home/whois.go | 38 +++++++++++++++++---------------- internal/home/whois_test.go | 3 ++- 9 files changed, 106 insertions(+), 58 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb8cb3d1..8a3456a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,7 +47,7 @@ and this project adheres to - Improved HTTP requests handling and timeouts ([#2343]). - Our snap package now uses the `core20` image as its base ([#2306]). - New build system and various internal improvements ([#2271], [#2276], [#2297], - [#2509]). + [#2509], [#2552]). [#2231]: https://github.com/AdguardTeam/AdGuardHome/issues/2231 [#2271]: https://github.com/AdguardTeam/AdGuardHome/issues/2271 @@ -59,6 +59,7 @@ and this project adheres to [#2391]: https://github.com/AdguardTeam/AdGuardHome/issues/2391 [#2394]: https://github.com/AdguardTeam/AdGuardHome/issues/2394 [#2509]: https://github.com/AdguardTeam/AdGuardHome/issues/2509 +[#2552]: https://github.com/AdguardTeam/AdGuardHome/issues/2552 [#2589]: https://github.com/AdguardTeam/AdGuardHome/issues/2589 ### Deprecated diff --git a/internal/home/controlinstall.go b/internal/home/controlinstall.go index da223ebd..0ab24f60 100644 --- a/internal/home/controlinstall.go +++ b/internal/home/controlinstall.go @@ -13,6 +13,7 @@ import ( "runtime" "strconv" "strings" + "time" "github.com/AdguardTeam/AdGuardHome/internal/util" @@ -268,6 +269,9 @@ func copyInstallSettings(dst, src *configuration) { dst.DNS.Port = src.DNS.Port } +// shutdownTimeout is the timeout for shutting HTTP server down operation. +const shutdownTimeout = 5 * time.Second + // Apply new configuration, start DNS server, restart Web server func (web *Web) handleInstallConfigure(w http.ResponseWriter, r *http.Request) { newSettings := applyConfigReq{} @@ -320,6 +324,10 @@ func (web *Web) handleInstallConfigure(w http.ResponseWriter, r *http.Request) { config.DNS.BindHost = newSettings.DNS.IP config.DNS.Port = newSettings.DNS.Port + // TODO(e.burkov): StartMods() should be put in a separate goroutine at + // the moment we'll allow setting up TLS in the initial configuration or + // the configuration itself will use HTTPS protocol, because the + // underlying functions potentially restart the HTTPS server. err = StartMods() if err != nil { Context.firstRun = true @@ -351,16 +359,22 @@ func (web *Web) handleInstallConfigure(w http.ResponseWriter, r *http.Request) { f.Flush() } - // this needs to be done in a goroutine because Shutdown() is a blocking call, and it will block - // until all requests are finished, and _we_ are inside a request right now, so it will block indefinitely + // The Shutdown() method of (*http.Server) needs to be called in a + // separate goroutine, because it waits until all requests are handled + // and will be blocked by it's own caller. if restartHTTP { - go func() { - _ = web.httpServer.Shutdown(context.TODO()) - }() + ctx, cancel := context.WithTimeout(context.Background(), shutdownTimeout) + + shut := func(srv *http.Server) { + defer cancel() + err := srv.Shutdown(ctx) + if err != nil { + log.Debug("error while shutting down HTTP server: %s", err) + } + } + go shut(web.httpServer) if web.httpServerBeta != nil { - go func() { - _ = web.httpServerBeta.Shutdown(context.TODO()) - }() + go shut(web.httpServerBeta) } } } diff --git a/internal/home/controlupdate.go b/internal/home/controlupdate.go index 327f2f90..8b3f1bf5 100644 --- a/internal/home/controlupdate.go +++ b/internal/home/controlupdate.go @@ -1,6 +1,7 @@ package home import ( + "context" "encoding/json" "errors" "net/http" @@ -90,7 +91,7 @@ func handleGetVersionJSON(w http.ResponseWriter, r *http.Request) { } } -// Perform an update procedure to the latest available version +// handleUpdate performs an update to the latest available version procedure. func handleUpdate(w http.ResponseWriter, _ *http.Request) { if Context.updater.NewVersion() == "" { httpError(w, http.StatusBadRequest, "/update request isn't allowed now") @@ -108,7 +109,13 @@ func handleUpdate(w http.ResponseWriter, _ *http.Request) { f.Flush() } - go finishUpdate() + // The background context is used because the underlying functions wrap + // it with timeout and shut down the server, which handles current + // request. It also should be done in a separate goroutine due to the + // same reason. + go func() { + finishUpdate(context.Background()) + }() } // versionResponse is the response for /control/version.json endpoint. @@ -140,10 +147,10 @@ func (vr *versionResponse) confirmAutoUpdate() { } } -// Complete an update procedure -func finishUpdate() { +// finishUpdate completes an update procedure. +func finishUpdate(ctx context.Context) { log.Info("Stopping all tasks") - cleanup() + cleanup(ctx) cleanupAlways() exeName := "AdGuardHome" diff --git a/internal/home/home.go b/internal/home/home.go index 4ece8f85..3c068912 100644 --- a/internal/home/home.go +++ b/internal/home/home.go @@ -108,7 +108,7 @@ func Main() { Context.tls.Reload() default: - cleanup() + cleanup(context.Background()) cleanupAlways() os.Exit(0) } @@ -334,7 +334,7 @@ func run(args options) { select {} } -// StartMods - initialize and start DNS after installation +// StartMods initializes and starts the DNS server after installation. func StartMods() error { err := initDNSServer() if err != nil { @@ -501,11 +501,12 @@ func configureLogger(args options) { } } -func cleanup() { +// cleanup stops and resets all the modules. +func cleanup(ctx context.Context) { log.Info("Stopping AdGuard Home") if Context.web != nil { - Context.web.Close() + Context.web.Close(ctx) Context.web = nil } if Context.auth != nil { diff --git a/internal/home/home_test.go b/internal/home/home_test.go index 344657e9..5f3f3d81 100644 --- a/internal/home/home_test.go +++ b/internal/home/home_test.go @@ -186,6 +186,6 @@ func TestHome(t *testing.T) { time.Sleep(1 * time.Second) } - cleanup() + cleanup(context.Background()) cleanupAlways() } diff --git a/internal/home/tls.go b/internal/home/tls.go index de6dcd18..1f68cc39 100644 --- a/internal/home/tls.go +++ b/internal/home/tls.go @@ -1,6 +1,7 @@ package home import ( + "context" "crypto" "crypto/ecdsa" "crypto/rsa" @@ -92,7 +93,7 @@ func (t *TLSMod) setCertFileTime() { t.certLastMod = fi.ModTime().UTC() } -// Start - start the module +// Start updates the configuration of TLSMod and starts it. func (t *TLSMod) Start() { if !tlsWebHandlersRegistered { tlsWebHandlersRegistered = true @@ -102,10 +103,14 @@ func (t *TLSMod) Start() { t.confLock.Lock() tlsConf := t.conf t.confLock.Unlock() - Context.web.TLSConfigChanged(tlsConf) + + // The background context is used because the TLSConfigChanged wraps + // context with timeout on its own and shuts down the server, which + // handles current request. + Context.web.TLSConfigChanged(context.Background(), tlsConf) } -// Reload - reload certificate file +// Reload updates the configuration of TLSMod and restarts it. func (t *TLSMod) Reload() { t.confLock.Lock() tlsConf := t.conf @@ -139,7 +144,10 @@ func (t *TLSMod) Reload() { t.confLock.Lock() tlsConf = t.conf t.confLock.Unlock() - Context.web.TLSConfigChanged(tlsConf) + // The background context is used because the TLSConfigChanged wraps + // context with timeout on its own and shuts down the server, which + // handles current request. + Context.web.TLSConfigChanged(context.Background(), tlsConf) } // Set certificate and private key data @@ -296,11 +304,13 @@ func (t *TLSMod) handleTLSConfigure(w http.ResponseWriter, r *http.Request) { f.Flush() } - // this needs to be done in a goroutine because Shutdown() is a blocking call, and it will block - // until all requests are finished, and _we_ are inside a request right now, so it will block indefinitely + // The background context is used because the TLSConfigChanged wraps + // context with timeout on its own and shuts down the server, which + // handles current request. It is also should be done in a separate + // goroutine due to the same reason. if restartHTTPS { go func() { - Context.web.TLSConfigChanged(data) + Context.web.TLSConfigChanged(context.Background(), data) }() } } diff --git a/internal/home/web.go b/internal/home/web.go index 72a2eb62..0048f427 100644 --- a/internal/home/web.go +++ b/internal/home/web.go @@ -122,8 +122,9 @@ func WebCheckPortAvailable(port int) bool { return true } -// TLSConfigChanged - called when TLS configuration has changed -func (web *Web) TLSConfigChanged(tlsConf tlsConfigSettings) { +// TLSConfigChanged updates the TLS configuration and restarts the HTTPS server +// if necessary. +func (web *Web) TLSConfigChanged(ctx context.Context, tlsConf tlsConfigSettings) { log.Debug("Web: applying new TLS configuration") web.conf.PortHTTPS = tlsConf.PortHTTPS web.forceHTTPS = (tlsConf.ForceHTTPS && tlsConf.Enabled && tlsConf.PortHTTPS != 0) @@ -143,7 +144,12 @@ func (web *Web) TLSConfigChanged(tlsConf tlsConfigSettings) { web.httpsServer.cond.L.Lock() if web.httpsServer.server != nil { - _ = web.httpsServer.server.Shutdown(context.TODO()) + ctx, cancel := context.WithTimeout(ctx, shutdownTimeout) + err = web.httpsServer.server.Shutdown(ctx) + cancel() + if err != nil { + log.Debug("error while shutting down HTTP server: %s", err) + } } web.httpsServer.enabled = enabled web.httpsServer.cert = cert @@ -198,22 +204,28 @@ func (web *Web) Start() { } } -// Close - stop HTTP server, possibly waiting for all active connections to be closed -func (web *Web) Close() { +// Close gracefully shuts down the HTTP servers. +func (web *Web) Close(ctx context.Context) { log.Info("Stopping HTTP server...") web.httpsServer.cond.L.Lock() web.httpsServer.shutdown = true web.httpsServer.cond.L.Unlock() - if web.httpsServer.server != nil { - _ = web.httpsServer.server.Shutdown(context.TODO()) - } - if web.httpServer != nil { - _ = web.httpServer.Shutdown(context.TODO()) - } - if web.httpServerBeta != nil { - _ = web.httpServerBeta.Shutdown(context.TODO()) + + shut := func(srv *http.Server) { + if srv == nil { + return + } + ctx, cancel := context.WithTimeout(ctx, shutdownTimeout) + defer cancel() + if err := srv.Shutdown(ctx); err != nil { + log.Debug("error while shutting down HTTP server: %s", err) + } } + shut(web.httpsServer.server) + shut(web.httpServer) + shut(web.httpServerBeta) + log.Info("Stopped HTTP server") } diff --git a/internal/home/whois.go b/internal/home/whois.go index 6c40ed54..1d849673 100644 --- a/internal/home/whois.go +++ b/internal/home/whois.go @@ -35,19 +35,20 @@ type Whois struct { ipAddrs cache.Cache } -// Create module context +// initWhois creates the Whois module context. func initWhois(clients *clientsContainer) *Whois { - w := Whois{} - w.timeoutMsec = 5000 - w.clients = clients + w := Whois{ + timeoutMsec: 5000, + clients: clients, + ipAddrs: cache.New(cache.Config{ + EnableLRU: true, + MaxCount: 10000, + }), + ipChan: make(chan net.IP, 255), + } - cconf := cache.Config{} - cconf.EnableLRU = true - cconf.MaxCount = 10000 - w.ipAddrs = cache.New(cconf) - - w.ipChan = make(chan net.IP, 255) go w.workerLoop() + return &w } @@ -120,12 +121,12 @@ func whoisParse(data string) map[string]string { const MaxConnReadSize = 64 * 1024 // Send request to a server and receive the response -func (w *Whois) query(target, serverAddr string) (string, error) { +func (w *Whois) query(ctx context.Context, target, serverAddr string) (string, error) { addr, _, _ := net.SplitHostPort(serverAddr) if addr == "whois.arin.net" { target = "n + " + target } - conn, err := customDialContext(context.TODO(), "tcp", serverAddr) + conn, err := customDialContext(ctx, "tcp", serverAddr) if err != nil { return "", err } @@ -153,11 +154,11 @@ func (w *Whois) query(target, serverAddr string) (string, error) { } // Query WHOIS servers (handle redirects) -func (w *Whois) queryAll(target string) (string, error) { +func (w *Whois) queryAll(ctx context.Context, target string) (string, error) { server := net.JoinHostPort(defaultServer, defaultPort) const maxRedirects = 5 for i := 0; i != maxRedirects; i++ { - resp, err := w.query(target, server) + resp, err := w.query(ctx, target, server) if err != nil { return "", err } @@ -183,9 +184,9 @@ func (w *Whois) queryAll(target string) (string, error) { } // Request WHOIS information -func (w *Whois) process(ip net.IP) [][]string { +func (w *Whois) process(ctx context.Context, ip net.IP) [][]string { data := [][]string{} - resp, err := w.queryAll(ip.String()) + resp, err := w.queryAll(ctx, ip.String()) if err != nil { log.Debug("Whois: error: %s IP:%s", err, ip) return data @@ -232,12 +233,13 @@ func (w *Whois) Begin(ip net.IP) { } } -// Get IP address from channel; get WHOIS info; associate info with a client +// workerLoop processes the IP addresses it got from the channel and associates +// the retrieving WHOIS info with a client. func (w *Whois) workerLoop() { for { ip := <-w.ipChan - info := w.process(ip) + info := w.process(context.Background(), ip) if len(info) == 0 { continue } diff --git a/internal/home/whois_test.go b/internal/home/whois_test.go index f8109e92..a160cdda 100644 --- a/internal/home/whois_test.go +++ b/internal/home/whois_test.go @@ -1,6 +1,7 @@ package home import ( + "context" "testing" "github.com/AdguardTeam/AdGuardHome/internal/dnsforward" @@ -19,7 +20,7 @@ func TestWhois(t *testing.T) { assert.Nil(t, prepareTestDNSServer()) w := Whois{timeoutMsec: 5000} - resp, err := w.queryAll("8.8.8.8") + resp, err := w.queryAll(context.Background(), "8.8.8.8") assert.Nil(t, err) m := whoisParse(resp) assert.Equal(t, "Google LLC", m["orgname"]) From 1c1a7683ef729928cdbf26f7b219ff31ebb35758 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Wed, 27 Jan 2021 14:24:41 +0300 Subject: [PATCH 061/141] Pull request: all: imp github issue template Merge in DNS/adguard-home from imp-issue-tmpl to master Squashed commit of the following: commit bc918f70367d2b1f0e9531068f0a60b4dc123807 Author: Ainar Garipov Date: Wed Jan 27 14:12:06 2021 +0300 all: imp github issue template --- .github/ISSUE_TEMPLATE/Bug_report.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/Bug_report.md b/.github/ISSUE_TEMPLATE/Bug_report.md index 6e565fe3..5603c7d7 100644 --- a/.github/ISSUE_TEMPLATE/Bug_report.md +++ b/.github/ISSUE_TEMPLATE/Bug_report.md @@ -21,6 +21,8 @@ Please answer the following questions for yourself before submitting an issue. * * **Version of AdGuard Home server:** * +* **How did you install AdGuard Home:** + * * **How did you setup DNS configuration:** * * **If it's a router or IoT, please write device model:** From 3e9edd9eac774f54eaa2041fb703ddb12dacd4b4 Mon Sep 17 00:00:00 2001 From: Andrey Meshkov Date: Wed, 27 Jan 2021 15:21:39 +0300 Subject: [PATCH 062/141] Pull request: Minor code improvement - added tests for safebrowsing and parental control services Merge in DNS/adguard-home from sbpctests to master Squashed commit of the following: commit 56df4dac20fcdba319543e8b15d9420ec247b68d Merge: 0dcec1d8 1c1a7683 Author: Andrey Meshkov Date: Wed Jan 27 15:06:53 2021 +0300 Merge branch 'master' into sbpctests commit 0dcec1d85362142b51443ab2ae0de0b4fd2a9676 Author: Andrey Meshkov Date: Wed Jan 27 14:13:18 2021 +0300 applied suggestion commit 166b659458a68e0c395028aa3dea580c5118544c Merge: e15e5cf6 dde5d798 Author: Andrey Meshkov Date: Wed Jan 27 14:02:47 2021 +0300 merge commit e15e5cf6a7a9cbf4ffdb24197fc7062e931bdf34 Author: Andrey Meshkov Date: Wed Jan 27 14:00:27 2021 +0300 Fix review comments commit dde5d798faa0f20b41f9252acfd4a83f9b8b19e3 Author: Andrey Meshkov Date: Wed Jan 27 13:10:03 2021 +0300 fixed comment style commit 3a77bc89cc0d1994d7a9de450b2796b6c27bd3dc Author: Andrey Meshkov Date: Wed Jan 27 12:57:11 2021 +0300 Minor code improvement - added tests for safebrowsing and parental control services --- internal/dnsfilter/safebrowsing.go | 1 - internal/dnsfilter/safebrowsing_test.go | 118 ++++++++++++++++++++++++ 2 files changed, 118 insertions(+), 1 deletion(-) diff --git a/internal/dnsfilter/safebrowsing.go b/internal/dnsfilter/safebrowsing.go index cec3081b..ad99bf80 100644 --- a/internal/dnsfilter/safebrowsing.go +++ b/internal/dnsfilter/safebrowsing.go @@ -200,7 +200,6 @@ func (c *sbCtx) processTXT(resp *dns.Msg) (bool, [][]byte) { log.Debug("%s: received hashes for %s: %v", c.svc, c.host, txt.Txt) for _, t := range txt.Txt { - if len(t) != 32*2 { continue } diff --git a/internal/dnsfilter/safebrowsing_test.go b/internal/dnsfilter/safebrowsing_test.go index 060664b5..21ea2277 100644 --- a/internal/dnsfilter/safebrowsing_test.go +++ b/internal/dnsfilter/safebrowsing_test.go @@ -2,6 +2,7 @@ package dnsfilter import ( "crypto/sha256" + "encoding/hex" "strings" "testing" @@ -134,3 +135,120 @@ func TestSBPC_checkErrorUpstream(t *testing.T) { _, err = d.checkParental("smthng.com") assert.NotNil(t, err) } + +// testSbUpstream implements upstream.Upstream interface for replacing real +// upstream in tests. +type testSbUpstream struct { + hostname string + block bool + requestsCount int +} + +// Exchange returns a message depending on the upstream settings (hostname, block) +func (u *testSbUpstream) Exchange(r *dns.Msg) (*dns.Msg, error) { + u.requestsCount++ + + hash := sha256.Sum256([]byte(u.hostname)) + prefix := hash[0:2] + hashToReturn := hex.EncodeToString(prefix) + strings.Repeat("ab", 28) + if u.block { + hashToReturn = hex.EncodeToString(hash[:]) + } + + m := &dns.Msg{} + m.Answer = []dns.RR{ + &dns.TXT{ + Hdr: dns.RR_Header{ + Name: r.Question[0].Name, + }, + Txt: []string{ + hashToReturn, + }, + }, + } + + return m, nil +} + +func (u *testSbUpstream) Address() string { + return "" +} + +func TestSBPC_sbValidResponse(t *testing.T) { + d := NewForTest(&Config{SafeBrowsingEnabled: true}, nil) + defer d.Close() + + ups := &testSbUpstream{} + d.safeBrowsingUpstream = ups + d.parentalUpstream = ups + + // Prepare the upstream + ups.hostname = "example.org" + ups.block = false + ups.requestsCount = 0 + + // First - check that the request is not blocked + res, err := d.checkSafeBrowsing("example.org") + assert.Nil(t, err) + assert.False(t, res.IsFiltered) + + // Check the cache state, check that the response is now cached + assert.Equal(t, 1, gctx.safebrowsingCache.Stats().Count) + assert.Equal(t, 0, gctx.safebrowsingCache.Stats().Hit) + + // There was one request to an upstream + assert.Equal(t, 1, ups.requestsCount) + + // Now make the same request to check that the cache was used + res, err = d.checkSafeBrowsing("example.org") + assert.Nil(t, err) + assert.False(t, res.IsFiltered) + + // Check the cache state, it should've been used + assert.Equal(t, 1, gctx.safebrowsingCache.Stats().Count) + assert.Equal(t, 1, gctx.safebrowsingCache.Stats().Hit) + + // Check that there were no additional requests + assert.Equal(t, 1, ups.requestsCount) +} + +func TestSBPC_pcBlockedResponse(t *testing.T) { + d := NewForTest(&Config{SafeBrowsingEnabled: true}, nil) + defer d.Close() + + ups := &testSbUpstream{} + d.safeBrowsingUpstream = ups + d.parentalUpstream = ups + + // Prepare the upstream + // Make sure that the upstream will return a response that matches the queried domain + ups.hostname = "example.com" + ups.block = true + ups.requestsCount = 0 + + // Make a lookup + res, err := d.checkParental("example.com") + assert.Nil(t, err) + assert.True(t, res.IsFiltered) + assert.Len(t, res.Rules, 1) + + // Check the cache state, check that the response is now cached + assert.Equal(t, 1, gctx.parentalCache.Stats().Count) + assert.Equal(t, 1, gctx.parentalCache.Stats().Hit) + + // There was one request to an upstream + assert.Equal(t, 1, ups.requestsCount) + + // Make a second lookup for the same domain + res, err = d.checkParental("example.com") + assert.Nil(t, err) + assert.True(t, res.IsFiltered) + assert.Len(t, res.Rules, 1) + + // Check the cache state, it should've been used + assert.Equal(t, 1, gctx.parentalCache.Stats().Count) + assert.Equal(t, 2, gctx.parentalCache.Stats().Hit) + + // Check that there were no additional requests + assert.Equal(t, 1, ups.requestsCount) +} From fc9ddcf9416141f513b81dc815bb5199911b32f3 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Wed, 27 Jan 2021 18:32:13 +0300 Subject: [PATCH 063/141] Pull request: all: client id support Merge in DNS/adguard-home from 1383-client-id to master Updates #1383. Squashed commit of the following: commit ebe2678bfa9bf651a2cb1e64499b38edcf19a7ad Author: Ildar Kamalov Date: Wed Jan 27 17:51:59 2021 +0300 - client: check if IP is valid commit 0c330585a170ea149ee75e43dfa65211e057299c Author: Ildar Kamalov Date: Wed Jan 27 17:07:50 2021 +0300 - client: find clients by client_id commit 71c9593ee35d996846f061e114b7867c3aa3c978 Merge: 9104f161 3e9edd9e Author: Ainar Garipov Date: Wed Jan 27 16:09:45 2021 +0300 Merge branch 'master' into 1383-client-id commit 9104f1615d2d462606c52017df25a422df872cea Author: Ainar Garipov Date: Wed Jan 27 13:28:50 2021 +0300 dnsforward: imp tests commit ed47f26e611ade625a2cc2c2f71a291b796bbf8f Author: Ainar Garipov Date: Wed Jan 27 12:39:52 2021 +0300 dnsforward: fix address commit 98b222ba69a5d265f620c180c960d01c84a1fb3b Author: Ainar Garipov Date: Tue Jan 26 19:50:31 2021 +0300 home: imp code commit 4f3966548a2d8437d0b68207dd108dd1a6cb7d20 Merge: 199fdc05 c215b820 Author: Ainar Garipov Date: Tue Jan 26 19:45:13 2021 +0300 Merge branch 'master' into 1383-client-id commit 199fdc056f8a8be5500584f3aaee32865188aedc Author: Ainar Garipov Date: Tue Jan 26 19:20:37 2021 +0300 all: imp tests, logging, etc commit 35ff14f4d534251aecb2ea60baba225f3eed8a3e Author: Ildar Kamalov Date: Tue Jan 26 18:55:19 2021 +0300 + client: remove block button from clients with client_id commit 32991a0b4c56583a02fb5e00bba95d96000bce20 Author: Ildar Kamalov Date: Tue Jan 26 18:54:25 2021 +0300 + client: add requests count for client_id commit 2d68df4d2eac4a296d7469923e601dad4575c1a1 Author: Ainar Garipov Date: Tue Jan 26 15:49:50 2021 +0300 stats: handle client ids commit 4e14ab3590328f93a8cd6e9cbe1665baf74f220b Author: Ainar Garipov Date: Tue Jan 26 13:45:25 2021 +0300 openapi: fix example commit ca9cf3f744fe197cace2c28ddc5bc68f71dad1f3 Author: Ainar Garipov Date: Tue Jan 26 13:37:10 2021 +0300 openapi: improve clients find api docs commit f79876e550c424558b704bc316a4cd04f25db011 Author: Ainar Garipov Date: Tue Jan 26 13:18:52 2021 +0300 home: accept ids in clients find commit 5b72595122aa0bd64debadfd753ed8a0e0840629 Merge: 607e241f abf8f65f Author: Ainar Garipov Date: Mon Jan 25 18:34:56 2021 +0300 Merge branch 'master' into 1383-client-id commit 607e241f1c339dd6397218f70b8301e3de6a1ee0 Author: Ainar Garipov Date: Mon Jan 25 18:30:39 2021 +0300 dnsforward: fix quic commit f046352fef93e46234c2bbe8ae316d21034260e5 Author: Ainar Garipov Date: Mon Jan 25 16:53:09 2021 +0300 all: remove wildcard requirement commit 3b679489bae82c54177372be453fe184d8f0bab6 Author: Andrey Meshkov Date: Mon Jan 25 16:02:28 2021 +0300 workDir now supports symlinks commit 0647ab4f113de2223f6949df001f42ecab05c995 Author: Ildar Kamalov Date: Mon Jan 25 14:59:46 2021 +0300 - client: remove wildcard from domain validation commit b1aec04a4ecadc9d65648ed6d284188fecce01c3 Author: Ildar Kamalov Date: Mon Jan 25 14:55:39 2021 +0300 + client: add form to download mobileconfig ... and 12 more commits --- CHANGELOG.md | 6 +- HACKING.md | 9 +- Makefile | 3 + README.md | 21 +- client/package-lock.json | 12 +- client/src/__locales/en.json | 10 +- client/src/actions/queryLogs.js | 2 +- client/src/components/App/index.css | 4 + client/src/components/Dashboard/Clients.js | 34 +- .../src/components/Logs/Cells/ClientCell.js | 107 ++++-- client/src/components/Logs/Cells/index.js | 4 +- .../src/components/Settings/Clients/Form.js | 2 +- .../Settings/Encryption/CertificateStatus.js | 4 +- .../components/Settings/Encryption/Form.js | 5 +- client/src/components/ui/{ => Guide}/Guide.js | 195 +++++----- .../components/ui/Guide/MobileConfigForm.js | 131 +++++++ client/src/components/ui/Guide/index.js | 1 + client/src/helpers/constants.js | 11 + client/src/helpers/helpers.js | 49 ++- client/src/helpers/validators.js | 18 + go.mod | 3 +- go.sum | 4 +- internal/dnsforward/config.go | 9 +- internal/dnsforward/dns.go | 285 ++++++++++++--- internal/dnsforward/dns_test.go | 235 ++++++++++++ internal/dnsforward/dnsforward_test.go | 5 +- internal/dnsforward/filter.go | 9 +- internal/dnsforward/http.go | 2 +- internal/dnsforward/ipset.go | 8 +- internal/dnsforward/ipset_test.go | 2 +- internal/dnsforward/stats.go | 47 +-- internal/dnsforward/stats_test.go | 198 ++++++++++ internal/home/clients.go | 344 +++++++++--------- internal/home/clients_test.go | 138 +++---- internal/home/clientshttp.go | 35 +- internal/home/config.go | 3 +- internal/home/dns.go | 52 ++- internal/home/home.go | 7 +- internal/home/mobileconfig.go | 61 +++- internal/home/mobileconfig_test.go | 41 +++ internal/home/rdns.go | 3 +- internal/home/whois.go | 12 +- internal/querylog/decode.go | 10 + internal/querylog/decode_test.go | 2 + internal/querylog/json.go | 4 + internal/querylog/qlog.go | 23 +- internal/querylog/qlogfile.go | 12 +- internal/querylog/querylog.go | 1 + internal/querylog/querylogfile.go | 12 +- internal/querylog/searchcriteria.go | 19 +- internal/stats/stats.go | 8 +- internal/stats/stats_test.go | 11 +- internal/stats/unit.go | 44 ++- openapi/CHANGELOG.md | 5 + openapi/openapi.yaml | 74 +++- scripts/make/go-build.sh | 2 +- 56 files changed, 1735 insertions(+), 623 deletions(-) rename client/src/components/ui/{ => Guide}/Guide.js (71%) create mode 100644 client/src/components/ui/Guide/MobileConfigForm.js create mode 100644 client/src/components/ui/Guide/index.js create mode 100644 internal/dnsforward/dns_test.go create mode 100644 internal/dnsforward/stats_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a3456a3..367a9940 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,11 +10,13 @@ and this project adheres to ## [Unreleased] ### Added +- Client ID support for DNS-over-HTTPS, DNS-over-QUIC, and DNS-over-TLS + ([#1383]). - `$dnsrewrite` modifier for filters ([#2102]). - The host checking API and the query logs API can now return multiple matched rules ([#2102]). @@ -27,6 +29,7 @@ and this project adheres to - HTTP API request body size limit ([#2305]). [#1361]: https://github.com/AdguardTeam/AdGuardHome/issues/1361 +[#1383]: https://github.com/AdguardTeam/AdGuardHome/issues/1383 [#2102]: https://github.com/AdguardTeam/AdGuardHome/issues/2102 [#2302]: https://github.com/AdguardTeam/AdGuardHome/issues/2302 [#2304]: https://github.com/AdguardTeam/AdGuardHome/issues/2304 @@ -35,6 +38,7 @@ and this project adheres to ### Changed +- `workDir` now supports symlinks. - Stopped mounting together the directories `/opt/adguardhome/conf` and `/opt/adguardhome/work` in our Docker images ([#2589]). - When `dns.bogus_nxdomain` option is used, the server will now transform diff --git a/HACKING.md b/HACKING.md index dc291bb7..0a4d102e 100644 --- a/HACKING.md +++ b/HACKING.md @@ -62,9 +62,12 @@ The rules are mostly sorted in the alphabetical order. * Don't use underscores in file and package names, unless they're build tags or for tests. This is to prevent accidental build errors with weird tags. - * Don't write code with more than four (**4**) levels of indentation. Just - like [Linus said], plus an additional level for an occasional error check or - struct initialization. + * Don't write non-test code with more than four (**4**) levels of indentation. + Just like [Linus said], plus an additional level for an occasional error + check or struct initialization. + + The exception proving the rule is the table-driven test code, where an + additional level of indentation is allowed. * Eschew external dependencies, including transitive, unless absolutely necessary. diff --git a/Makefile b/Makefile index d116d4e4..3d8625e6 100644 --- a/Makefile +++ b/Makefile @@ -80,7 +80,10 @@ go-lint: ; $(ENV) "$(SHELL)" ./scripts/make/go-lint.sh go-test: ; $(ENV) "$(SHELL)" ./scripts/make/go-test.sh go-tools: ; $(ENV) "$(SHELL)" ./scripts/make/go-tools.sh +go-check: go-tools go-lint go-test + openapi-lint: ; cd ./openapi/ && $(YARN) test +openapi-show: ; cd ./openapi/ && $(YARN) start # TODO(a.garipov): Remove the legacy targets once the build # infrastructure stops using them. diff --git a/README.md b/README.md index e694452f..6b60d888 100644 --- a/README.md +++ b/README.md @@ -87,12 +87,21 @@ If you're running **Linux**, there's a secure and easy way to install AdGuard Ho ### Guides -* [FAQ](https://github.com/AdguardTeam/AdGuardHome/wiki/FAQ) -* [Configuration](https://github.com/AdguardTeam/AdGuardHome/wiki/Configuration) -* [AdGuard Home as a DNS-over-HTTPS or DNS-over-TLS server](https://github.com/AdguardTeam/AdGuardHome/wiki/Encryption) -* [How to install and run AdGuard Home on Raspberry Pi](https://github.com/AdguardTeam/AdGuardHome/wiki/Raspberry-Pi) -* [How to install and run AdGuard Home on a Virtual Private Server](https://github.com/AdguardTeam/AdGuardHome/wiki/VPS) -* [How to write your own hosts blocklists properly](https://github.com/AdguardTeam/AdGuardHome/wiki/Hosts-Blocklists) +* [Getting Started](https://github.com/AdguardTeam/AdGuardHome/wiki/Getting-Started) + * [FAQ](https://github.com/AdguardTeam/AdGuardHome/wiki/FAQ) + * [How to Write Hosts Blocklists](https://github.com/AdguardTeam/AdGuardHome/wiki/Hosts-Blocklists) + * [Comparing AdGuard Home to Other Solutions](https://github.com/AdguardTeam/AdGuardHome/wiki/Comparison) +* Configuring AdGuard + * [Configuration](https://github.com/AdguardTeam/AdGuardHome/wiki/Configuration) + * [Configuring AdGuard Home Clients](https://github.com/AdguardTeam/AdGuardHome/wiki/Clients) + * [AdGuard Home as a DoH, DoT, or DoQ Server](https://github.com/AdguardTeam/AdGuardHome/wiki/Encryption) + * [AdGuard Home as a DNSCrypt Server](https://github.com/AdguardTeam/AdGuardHome/wiki/DNSCrypt) + * [AdGuard Home as a DHCP Server](https://github.com/AdguardTeam/AdGuardHome/wiki/DHCP) +* Installing AdGuard Home + * [Docker](https://github.com/AdguardTeam/AdGuardHome/wiki/Docker) + * [How to Install and Run AdGuard Home on a Raspberry Pi](https://github.com/AdguardTeam/AdGuardHome/wiki/Raspberry-Pi) + * [How to Install and Run AdGuard Home on a Virtual Private Server](https://github.com/AdguardTeam/AdGuardHome/wiki/VPS) +* [Verifying Releases](https://github.com/AdguardTeam/AdGuardHome/wiki/Verify-Releases) ### API diff --git a/client/package-lock.json b/client/package-lock.json index f1172e00..56bf51c4 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -3066,12 +3066,6 @@ "pkg-up": "^2.0.0" } }, - "caniuse-lite": { - "version": "1.0.30001062", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001062.tgz", - "integrity": "sha512-ei9ZqeOnN7edDrb24QfJ0OZicpEbsWxv7WusOiQGz/f2SfvBgHHbOEwBJ8HKGVSyx8Z6ndPjxzR6m0NQq+0bfw==", - "dev": true - }, "postcss": { "version": "7.0.30", "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.30.tgz", @@ -3928,9 +3922,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001059", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001059.tgz", - "integrity": "sha512-oOrc+jPJWooKIA0IrNZ5sYlsXc7NP7KLhNWrSGEJhnfSzDvDJ0zd3i6HXsslExY9bbu+x0FQ5C61LcqmPt7bOQ==", + "version": "1.0.30001165", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001165.tgz", + "integrity": "sha512-8cEsSMwXfx7lWSUMA2s08z9dIgsnR5NAqjXP23stdsU3AUWkCr/rr4s4OFtHXn5XXr6+7kam3QFVoYyXNPdJPA==", "dev": true }, "capture-exit": { diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json index afc2f105..a13f6cb4 100644 --- a/client/src/__locales/en.json +++ b/client/src/__locales/en.json @@ -32,6 +32,7 @@ "form_error_ip_format": "Invalid IP format", "form_error_mac_format": "Invalid MAC format", "form_error_client_id_format": "Invalid client ID format", + "form_error_server_name": "Invalid server name", "form_error_positive": "Must be greater than 0", "form_error_negative": "Must be equal to 0 or greater", "range_end_error": "Must be greater than range start", @@ -250,8 +251,12 @@ "dns_over_https": "DNS-over-HTTPS", "dns_over_tls": "DNS-over-TLS", "dns_over_quic": "DNS-over-QUIC", + "client_id": "Client ID", + "client_id_placeholder": "Enter client ID", + "client_id_desc": "Different clients can be identified by a special client ID. Here you can learn more about how to identify clients.", "download_mobileconfig_doh": "Download .mobileconfig for DNS-over-HTTPS", "download_mobileconfig_dot": "Download .mobileconfig for DNS-over-TLS", + "download_mobileconfig": "Download configuration file", "plain_dns": "Plain DNS", "form_enter_rate_limit": "Enter rate limit", "rate_limit": "Rate limit", @@ -331,7 +336,7 @@ "encryption_config_saved": "Encryption config saved", "encryption_server": "Server name", "encryption_server_enter": "Enter your domain name", - "encryption_server_desc": "In order to use HTTPS, you need to enter the server name that matches your SSL certificate.", + "encryption_server_desc": "In order to use HTTPS, you need to enter the server name that matches your SSL certificate or wildcard certificate. If the field is not set, it will accept TLS connections for any domain.", "encryption_redirect": "Redirect to HTTPS automatically", "encryption_redirect_desc": "If checked, AdGuard Home will automatically redirect you from HTTP to HTTPS addresses.", "encryption_https": "HTTPS port", @@ -387,7 +392,7 @@ "client_edit": "Edit Client", "client_identifier": "Identifier", "ip_address": "IP address", - "client_identifier_desc": "Clients can be identified by the IP address, CIDR, MAC address. Please note that using MAC as identifier is possible only if AdGuard Home is also a <0>DHCP server", + "client_identifier_desc": "Clients can be identified by the IP address, CIDR, MAC address or a special client ID (can be used for DoT/DoH/DoQ). <0>Here you can learn more about how to identify clients.", "form_enter_ip": "Enter IP", "form_enter_mac": "Enter MAC", "form_enter_id": "Enter identifier", @@ -431,6 +436,7 @@ "setup_dns_privacy_other_3": "<0>dnscrypt-proxy supports <1>DNS-over-HTTPS.", "setup_dns_privacy_other_4": "<0>Mozilla Firefox supports <1>DNS-over-HTTPS.", "setup_dns_privacy_other_5": "You will find more implementations <0>here and <1>here.", + "setup_dns_privacy_ioc_mac": "iOS and macOS configuration", "setup_dns_notice": "In order to use <1>DNS-over-HTTPS or <1>DNS-over-TLS, you need to <0>configure Encryption in AdGuard Home settings.", "rewrite_added": "DNS rewrite for \"{{key}}\" successfully added", "rewrite_deleted": "DNS rewrite for \"{{key}}\" successfully deleted", diff --git a/client/src/actions/queryLogs.js b/client/src/actions/queryLogs.js index 1c653fb3..de8af4a3 100644 --- a/client/src/actions/queryLogs.js +++ b/client/src/actions/queryLogs.js @@ -8,7 +8,7 @@ import { import { addErrorToast, addSuccessToast } from './toasts'; const enrichWithClientInfo = async (logs) => { - const clientsParams = getParamsForClientsSearch(logs, 'client'); + const clientsParams = getParamsForClientsSearch(logs, 'client', 'client_id'); if (Object.keys(clientsParams).length > 0) { const clients = await apiClient.findClients(clientsParams); diff --git a/client/src/components/App/index.css b/client/src/components/App/index.css index 0832e790..2b1ee76d 100644 --- a/client/src/components/App/index.css +++ b/client/src/components/App/index.css @@ -81,3 +81,7 @@ body { .ReactModal__Body--open { overflow: hidden; } + +a.btn-success.disabled { + color: #fff; +} diff --git a/client/src/components/Dashboard/Clients.js b/client/src/components/Dashboard/Clients.js index ccac7baf..46edc46f 100644 --- a/client/src/components/Dashboard/Clients.js +++ b/client/src/components/Dashboard/Clients.js @@ -9,7 +9,7 @@ import Card from '../ui/Card'; import Cell from '../ui/Cell'; import { getPercent, sortIp } from '../../helpers/helpers'; -import { BLOCK_ACTIONS, STATUS_COLORS } from '../../helpers/constants'; +import { BLOCK_ACTIONS, R_CLIENT_ID, STATUS_COLORS } from '../../helpers/constants'; import { toggleClientBlock } from '../../actions/access'; import { renderFormattedClientCell } from '../../helpers/renderFormattedClientCell'; import { getStats } from '../../actions/stats'; @@ -35,6 +35,10 @@ const CountCell = (row) => { }; const renderBlockingButton = (ip, disallowed, disallowed_rule) => { + if (R_CLIENT_ID.test(ip)) { + return null; + } + const dispatch = useDispatch(); const { t } = useTranslation(); const processingSet = useSelector((state) => state.access.processingSet); @@ -59,17 +63,19 @@ const renderBlockingButton = (ip, disallowed, disallowed_rule) => { const text = disallowed ? BLOCK_ACTIONS.UNBLOCK : BLOCK_ACTIONS.BLOCK; const isNotInAllowedList = disallowed && disallowed_rule === ''; - return
- -
; + > + {text} + +
+ ); }; const ClientCell = (row) => { @@ -90,13 +96,14 @@ const Clients = ({ const { t } = useTranslation(); const topClients = useSelector((state) => state.stats.topClients, shallowEqual); - return - + ({ @@ -107,7 +114,7 @@ const Clients = ({ }))} columns={[ { - Header: 'IP', + Header: client_table_header, accessor: 'ip', sortMethod: sortIp, Cell: ClientCell, @@ -134,8 +141,9 @@ const Clients = ({ return disallowed ? { className: 'logs__row--red' } : {}; }} - /> - ; + /> + + ); }; Clients.propTypes = { diff --git a/client/src/components/Logs/Cells/ClientCell.js b/client/src/components/Logs/Cells/ClientCell.js index 56a3440c..863cd726 100644 --- a/client/src/components/Logs/Cells/ClientCell.js +++ b/client/src/components/Logs/Cells/ClientCell.js @@ -16,6 +16,7 @@ import { updateLogs } from '../../../actions/queryLogs'; const ClientCell = ({ client, + client_id, domain, info, info: { @@ -33,12 +34,14 @@ const ClientCell = ({ const autoClient = autoClients.find((autoClient) => autoClient.name === client); const source = autoClient?.source; const whoisAvailable = whois_info && Object.keys(whois_info).length > 0; + const clientName = name || client_id; + const clientInfo = { ...info, name: clientName }; const id = nanoid(); const data = { address: client, - name, + name: clientName, country: whois_info?.country, city: whois_info?.city, network: whois_info?.orgname, @@ -99,13 +102,20 @@ const ClientCell = ({ if (options.length === 0) { return null; } - return <>{options.map(({ name, onClick, disabled }) => )}; + return ( + <> + {options.map(({ name, onClick, disabled }) => ( + + ))} + + ); }; const content = getOptions(BUTTON_OPTIONS); @@ -125,45 +135,70 @@ const ClientCell = ({ 'button-action__container--detailed': isDetailed, }); - return
- - {content && } -
; + > + {t(buttonType)} + + {content && ( + + )} +
+ ); }; - return
- -
-
- {renderFormattedClientCell(client, info, isDetailed, true)} + return ( +
+ +
+
+ {renderFormattedClientCell(client, clientInfo, isDetailed, true)} +
+ {isDetailed && clientName && !whoisAvailable && ( +
+ {clientName} +
+ )}
- {isDetailed && name && !whoisAvailable - &&
{name}
} + {renderBlockingButton(isFiltered, domain)}
- {renderBlockingButton(isFiltered, domain)} -
; + ); }; ClientCell.propTypes = { client: propTypes.string.isRequired, + client_id: propTypes.string, domain: propTypes.string.isRequired, info: propTypes.oneOfType([ propTypes.string, diff --git a/client/src/components/Logs/Cells/index.js b/client/src/components/Logs/Cells/index.js index bc1fc2ed..5ec64f1f 100644 --- a/client/src/components/Logs/Cells/index.js +++ b/client/src/components/Logs/Cells/index.js @@ -70,6 +70,7 @@ const Row = memo(({ upstream, type, client_proto, + client_id, rules, originalResponse, status, @@ -176,7 +177,7 @@ const Row = memo(({ response_code: status, client_details: 'title', ip_address: client, - name: info?.name, + name: info?.name || client_id, country, city, network, @@ -233,6 +234,7 @@ Row.propTypes = { upstream: propTypes.string.isRequired, type: propTypes.string.isRequired, client_proto: propTypes.string.isRequired, + client_id: propTypes.string, rules: propTypes.arrayOf(propTypes.shape({ text: propTypes.string.isRequired, filter_list_id: propTypes.number.isRequired, diff --git a/client/src/components/Settings/Clients/Form.js b/client/src/components/Settings/Clients/Form.js index 20f1f828..631272d8 100644 --- a/client/src/components/Settings/Clients/Form.js +++ b/client/src/components/Settings/Clients/Form.js @@ -282,7 +282,7 @@ let Form = (props) => {
+ link , ]} diff --git a/client/src/components/Settings/Encryption/CertificateStatus.js b/client/src/components/Settings/Encryption/CertificateStatus.js index a93389f5..911d1d36 100644 --- a/client/src/components/Settings/Encryption/CertificateStatus.js +++ b/client/src/components/Settings/Encryption/CertificateStatus.js @@ -50,7 +50,7 @@ const CertificateStatus = ({ {dnsNames && (
  • encryption_hostnames:  - {dnsNames} + {dnsNames.join(', ')}
  • )} @@ -65,7 +65,7 @@ CertificateStatus.propTypes = { subject: PropTypes.string, issuer: PropTypes.string, notAfter: PropTypes.string, - dnsNames: PropTypes.string, + dnsNames: PropTypes.arrayOf(PropTypes.string), }; export default withTranslation()(CertificateStatus); diff --git a/client/src/components/Settings/Encryption/Form.js b/client/src/components/Settings/Encryption/Form.js index 1619ea3e..58cd181e 100644 --- a/client/src/components/Settings/Encryption/Form.js +++ b/client/src/components/Settings/Encryption/Form.js @@ -12,7 +12,7 @@ import { toNumber, } from '../../../helpers/form'; import { - validateIsSafePort, validatePort, validatePortQuic, validatePortTLS, + validateServerName, validateIsSafePort, validatePort, validatePortQuic, validatePortTLS, } from '../../../helpers/validators'; import i18n from '../../../i18n'; import KeyStatus from './KeyStatus'; @@ -127,6 +127,7 @@ let Form = (props) => { placeholder={t('encryption_server_enter')} onChange={handleChange} disabled={!isEnabled} + validate={validateServerName} />
    encryption_server_desc @@ -413,7 +414,7 @@ Form.propTypes = { valid_key: PropTypes.bool, valid_cert: PropTypes.bool, valid_pair: PropTypes.bool, - dns_names: PropTypes.string, + dns_names: PropTypes.arrayOf(PropTypes.string), key_type: PropTypes.string, issuer: PropTypes.string, subject: PropTypes.string, diff --git a/client/src/components/ui/Guide.js b/client/src/components/ui/Guide/Guide.js similarity index 71% rename from client/src/components/ui/Guide.js rename to client/src/components/ui/Guide/Guide.js index a06af83b..cf1af0b0 100644 --- a/client/src/components/ui/Guide.js +++ b/client/src/components/ui/Guide/Guide.js @@ -3,27 +3,12 @@ import PropTypes from 'prop-types'; import { Trans, useTranslation } from 'react-i18next'; import i18next from 'i18next'; import { useSelector } from 'react-redux'; -import Tabs from './Tabs'; -import Icons from './Icons'; -import { getPathWithQueryString } from '../../helpers/helpers'; -const MOBILE_CONFIG_LINKS = { - DOT: '/apple/dot.mobileconfig', - DOH: '/apple/doh.mobileconfig', -}; -const renderMobileconfigInfo = ({ label, components, server_name }) =>
  • - {label} - -
  • ; +import { MOBILE_CONFIG_LINKS } from '../../../helpers/constants'; + +import Tabs from '../Tabs'; +import Icons from '../Icons'; +import MobileConfigForm from './MobileConfigForm'; const renderLi = ({ label, components }) =>
  • { @@ -41,49 +26,8 @@ const renderLi = ({ label, components }) =>
  • ; -const getDnsPrivacyList = (server_name) => { - const iosList = [ - { - label: 'setup_dns_privacy_ios_2', - components: [ - { - key: 0, - href: 'https://adguard.com/adguard-ios/overview.html', - }, - text, - ], - }, - { - label: 'setup_dns_privacy_ios_1', - components: [ - { - key: 0, - href: 'https://itunes.apple.com/app/id1452162351', - }, - text, - { - key: 2, - href: 'https://dnscrypt.info/stamps', - }, - - ], - }]; - /* Insert second element if can generate .mobileconfig links */ - if (server_name) { - iosList.splice(1, 0, { - label: 'setup_dns_privacy_4', - components: { - highlight: , - }, - renderComponent: ({ label, components }) => renderMobileconfigInfo({ - label, - components, - server_name, - }), - }); - } - - return [{ +const getDnsPrivacyList = () => [ + { title: 'Android', list: [ { @@ -113,7 +57,32 @@ const getDnsPrivacyList = (server_name) => { }, { title: 'iOS', - list: iosList, + list: [ + { + label: 'setup_dns_privacy_ios_2', + components: [ + { + key: 0, + href: 'https://adguard.com/adguard-ios/overview.html', + }, + text, + ], + }, + { + label: 'setup_dns_privacy_ios_1', + components: [ + { + key: 0, + href: 'https://itunes.apple.com/app/id1452162351', + }, + text, + { + key: 2, + href: 'https://dnscrypt.info/stamps', + }, + ], + }, + ], }, { title: 'setup_dns_privacy_other_title', @@ -166,20 +135,20 @@ const getDnsPrivacyList = (server_name) => { }, ], }, - ]; -}; +]; -const renderDnsPrivacyList = ({ title, list }) =>
    - {title} -
      {list.map( - ({ - label, - components, - renderComponent = renderLi, - }) => renderComponent({ label, components }), - )} -
    -
    ; +const renderDnsPrivacyList = ({ title, list }) => ( +
    + + {title} + +
      + {list.map(({ label, components, renderComponent = renderLi }) => ( + renderComponent({ label, components }) + ))} +
    +
    +); const getTabs = ({ tlsAddress, @@ -267,8 +236,8 @@ const getTabs = ({
    )} - {showDnsPrivacyNotice - ?
    + {showDnsPrivacyNotice ? ( +
    - : <> + ) : ( + <>
    text

    ]}> setup_dns_privacy_3
    - {getDnsPrivacyList(server_name).map(renderDnsPrivacyList)} - } + {getDnsPrivacyList().map(renderDnsPrivacyList)} +
    + + + setup_dns_privacy_ioc_mac + + +
    +
    + }}> + setup_dns_privacy_4 + +
    + + + )}
    ; }, }, }); -const renderContent = ({ title, list, getTitle }) =>
    -
    {i18next.t(title)}
    -
    - {getTitle?.()} - {list - &&
      {list.map((item) =>
    1. - {item} -
    2. )} -
    } +const renderContent = ({ title, list, getTitle }) => ( +
    +
    + {i18next.t(title)} +
    +
    + {getTitle?.()} + {list && ( +
      + {list.map((item) => ( +
    1. + {item} +
    2. + ))} +
    + )} +
    -
    ; +); const Guide = ({ dnsAddresses }) => { const { t } = useTranslation(); - const server_name = useSelector((state) => state.encryption.server_name); + const server_name = useSelector((state) => state.encryption?.server_name); const tlsAddress = dnsAddresses?.filter((item) => item.includes('tls://')) ?? ''; const httpsAddress = dnsAddresses?.filter((item) => item.includes('https://')) ?? ''; const showDnsPrivacyNotice = httpsAddress.length < 1 && tlsAddress.length < 1; @@ -332,9 +330,14 @@ const Guide = ({ dnsAddresses }) => { return (
    + + {activeTab} + - {activeTab}
    ); }; @@ -364,6 +367,4 @@ renderLi.propTypes = { components: PropTypes.string, }; -renderMobileconfigInfo.propTypes = renderLi.propTypes; - export default Guide; diff --git a/client/src/components/ui/Guide/MobileConfigForm.js b/client/src/components/ui/Guide/MobileConfigForm.js new file mode 100644 index 00000000..e1726d99 --- /dev/null +++ b/client/src/components/ui/Guide/MobileConfigForm.js @@ -0,0 +1,131 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Trans } from 'react-i18next'; +import { useSelector } from 'react-redux'; +import { Field, reduxForm } from 'redux-form'; +import i18next from 'i18next'; +import cn from 'classnames'; + +import { getPathWithQueryString } from '../../../helpers/helpers'; +import { FORM_NAME, MOBILE_CONFIG_LINKS } from '../../../helpers/constants'; +import { + renderInputField, + renderSelectField, +} from '../../../helpers/form'; +import { + validateClientId, + validateServerName, +} from '../../../helpers/validators'; + +const getDownloadLink = (host, clientId, protocol, invalid) => { + if (!host || invalid) { + return ( + + ); + } + + const linkParams = { host }; + + if (clientId) { + linkParams.client_id = clientId; + } + + return ( + + download_mobileconfig + + ); +}; + +const MobileConfigForm = ({ invalid }) => { + const formValues = useSelector((state) => state.form[FORM_NAME.MOBILE_CONFIG]?.values); + + if (!formValues) { + return null; + } + + const { host, clientId, protocol } = formValues; + + const githubLink = ( + + text + + ); + + return ( + e.preventDefault()}> +
    +
    + + +
    +
    + +
    + + client_id_desc + +
    + +
    +
    + + + + + +
    +
    + + {getDownloadLink(host, clientId, protocol, invalid)} + + ); +}; + +MobileConfigForm.propTypes = { + invalid: PropTypes.bool.isRequired, +}; + +export default reduxForm({ form: FORM_NAME.MOBILE_CONFIG })(MobileConfigForm); diff --git a/client/src/components/ui/Guide/index.js b/client/src/components/ui/Guide/index.js new file mode 100644 index 00000000..ee660aeb --- /dev/null +++ b/client/src/components/ui/Guide/index.js @@ -0,0 +1 @@ +export { default } from './Guide'; diff --git a/client/src/helpers/constants.js b/client/src/helpers/constants.js index 76be76a6..e05ee9dc 100644 --- a/client/src/helpers/constants.js +++ b/client/src/helpers/constants.js @@ -13,6 +13,8 @@ export const R_MAC = /^((([a-fA-F0-9][a-fA-F0-9]+[-]){5}|([a-fA-F0-9][a-fA-F0-9] export const R_CIDR_IPV6 = /^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*(\/(12[0-8]|1[0-1][0-9]|[1-9][0-9]|[0-9]))$/; +export const R_DOMAIN = /^[a-zA-Z0-9][a-zA-Z0-9-]{1,61}[a-zA-Z0-9]\.[a-zA-Z]{2,}$/; + export const R_PATH_LAST_PART = /\/[^/]*$/; // eslint-disable-next-line no-control-regex @@ -21,6 +23,8 @@ export const R_UNIX_ABSOLUTE_PATH = /^(\/[^/\x00]+)+$/; // eslint-disable-next-line no-control-regex export const R_WIN_ABSOLUTE_PATH = /^([a-zA-Z]:)?(\\|\/)(?:[^\\/:*?"<>|\x00]+\\)*[^\\/:*?"<>|\x00]*$/; +export const R_CLIENT_ID = /^[a-z0-9-]{1,64}$/; + export const HTML_PAGES = { INSTALL: '/install.html', LOGIN: '/login.html', @@ -514,6 +518,7 @@ export const FORM_NAME = { INSTALL: 'install', LOGIN: 'login', CACHE: 'cache', + MOBILE_CONFIG: 'mobileConfig', ...DHCP_FORM_NAMES, }; @@ -574,6 +579,7 @@ export const TOAST_TIMEOUTS = { export const ADDRESS_TYPES = { IP: 'IP', CIDR: 'CIDR', + CLIENT_ID: 'CLIENT_ID', UNKNOWN: 'UNKNOWN', }; @@ -585,3 +591,8 @@ export const CACHE_CONFIG_FIELDS = { export const isFirefox = navigator.userAgent.indexOf('Firefox') !== -1; export const COMMENT_LINE_DEFAULT_TOKEN = '#'; + +export const MOBILE_CONFIG_LINKS = { + DOT: '/apple/dot.mobileconfig', + DOH: '/apple/doh.mobileconfig', +}; diff --git a/client/src/helpers/helpers.js b/client/src/helpers/helpers.js index 82f30245..2acf5315 100644 --- a/client/src/helpers/helpers.js +++ b/client/src/helpers/helpers.js @@ -4,7 +4,6 @@ import dateFormat from 'date-fns/format'; import round from 'lodash/round'; import axios from 'axios'; import i18n from 'i18next'; -import uniqBy from 'lodash/uniqBy'; import ipaddr from 'ipaddr.js'; import queryString from 'query-string'; import React from 'react'; @@ -22,6 +21,7 @@ import { DHCP_VALUES_PLACEHOLDERS, FILTERED, FILTERED_STATUS, + R_CLIENT_ID, SERVICES_ID_NAME_MAP, STANDARD_DNS_PORT, STANDARD_HTTPS_PORT, @@ -62,6 +62,7 @@ export const normalizeLogs = (logs) => logs.map((log) => { answer_dnssec, client, client_proto, + client_id, elapsedMs, question, reason, @@ -99,6 +100,7 @@ export const normalizeLogs = (logs) => logs.map((log) => { reason, client, client_proto, + client_id, /* TODO 'filterId' and 'rule' are deprecated, will be removed in 0.106 */ filterId, rule, @@ -414,14 +416,21 @@ export const getPathWithQueryString = (path, params) => { return `${path}?${searchParams.toString()}`; }; -export const getParamsForClientsSearch = (data, param) => { - const uniqueClients = uniqBy(data, param); - return uniqueClients - .reduce((acc, item, idx) => { - const key = `ip${idx}`; - acc[key] = item[param]; - return acc; - }, {}); +export const getParamsForClientsSearch = (data, param, additionalParam) => { + const clients = new Set(); + data.forEach((e) => { + clients.add(e[param]); + if (e[additionalParam]) { + clients.add(e[additionalParam]); + } + }); + const params = {}; + const ids = Array.from(clients.values()); + ids.forEach((id, i) => { + params[`ip${i}`] = id; + }); + + return params; }; /** @@ -534,7 +543,7 @@ export const isIpInCidr = (ip, cidr) => { /** * * @param ipOrCidr - * @returns {'IP' | 'CIDR' | 'UNKNOWN'} + * @returns {'IP' | 'CIDR' | 'CLIENT_ID' | 'UNKNOWN'} * */ export const findAddressType = (address) => { @@ -547,6 +556,9 @@ export const findAddressType = (address) => { if (cidrMaybe && ipaddr.parseCIDR(address)) { return ADDRESS_TYPES.CIDR; } + if (R_CLIENT_ID.test(address)) { + return ADDRESS_TYPES.CLIENT_ID; + } return ADDRESS_TYPES.UNKNOWN; } catch (e) { @@ -567,20 +579,31 @@ export const separateIpsAndCidrs = (ids) => ids.reduce((acc, curr) => { if (addressType === ADDRESS_TYPES.CIDR) { acc.cidrs.push(curr); } + if (addressType === ADDRESS_TYPES.CLIENT_ID) { + acc.clientIds.push(curr); + } return acc; -}, { ips: [], cidrs: [] }); +}, { ips: [], cidrs: [], clientIds: [] }); export const countClientsStatistics = (ids, autoClients) => { - const { ips, cidrs } = separateIpsAndCidrs(ids); + const { ips, cidrs, clientIds } = separateIpsAndCidrs(ids); const ipsCount = ips.reduce((acc, curr) => { const count = autoClients[curr] || 0; return acc + count; }, 0); + const clientIdsCount = clientIds.reduce((acc, curr) => { + const count = autoClients[curr] || 0; + return acc + count; + }, 0); + const cidrsCount = Object.entries(autoClients) .reduce((acc, curr) => { const [id, count] = curr; + if (!ipaddr.isValid(id)) { + return false; + } if (cidrs.some((cidr) => isIpInCidr(id, cidr))) { // eslint-disable-next-line no-param-reassign acc += count; @@ -588,7 +611,7 @@ export const countClientsStatistics = (ids, autoClients) => { return acc; }, 0); - return ipsCount + cidrsCount; + return ipsCount + cidrsCount + clientIdsCount; }; /** diff --git a/client/src/helpers/validators.js b/client/src/helpers/validators.js index 2632df3f..c26bbb8e 100644 --- a/client/src/helpers/validators.js +++ b/client/src/helpers/validators.js @@ -9,6 +9,8 @@ import { R_URL_REQUIRES_PROTOCOL, STANDARD_WEB_PORT, UNSAFE_PORTS, + R_CLIENT_ID, + R_DOMAIN, } from './constants'; import { getLastIpv4Octet, isValidAbsolutePath } from './form'; @@ -71,12 +73,28 @@ export const validateClientId = (value) => { || R_MAC.test(formattedValue) || R_CIDR.test(formattedValue) || R_CIDR_IPV6.test(formattedValue) + || R_CLIENT_ID.test(formattedValue) )) { return 'form_error_client_id_format'; } return undefined; }; +/** + * @param value {string} + * @returns {undefined|string} + */ +export const validateServerName = (value) => { + if (!value) { + return undefined; + } + const formattedValue = value ? value.trim() : value; + if (formattedValue && !R_DOMAIN.test(formattedValue)) { + return 'form_error_server_name'; + } + return undefined; +}; + /** * @param value {string} * @returns {undefined|string} diff --git a/go.mod b/go.mod index f22141c2..a4f86658 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/AdguardTeam/AdGuardHome go 1.14 require ( - github.com/AdguardTeam/dnsproxy v0.33.7 + github.com/AdguardTeam/dnsproxy v0.33.9 github.com/AdguardTeam/golibs v0.4.4 github.com/AdguardTeam/urlfilter v0.14.2 github.com/NYTimes/gziphandler v1.1.1 @@ -17,6 +17,7 @@ require ( github.com/insomniacslk/dhcp v0.0.0-20201112113307-4de412bc85d8 github.com/kardianos/service v1.2.0 github.com/karrick/godirwalk v1.16.1 // indirect + github.com/lucas-clemente/quic-go v0.19.3 github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7 github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065 github.com/miekg/dns v1.1.35 diff --git a/go.sum b/go.sum index 1c488f95..b298768b 100644 --- a/go.sum +++ b/go.sum @@ -18,8 +18,8 @@ dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBr dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= -github.com/AdguardTeam/dnsproxy v0.33.7 h1:DXsLTJoBSUejB2ZqVHyMG0/kXD8PzuVPbLCsGKBdaDc= -github.com/AdguardTeam/dnsproxy v0.33.7/go.mod h1:dkI9VWh43XlOzF2XogDm1EmoVl7PANOR4isQV6X9LZs= +github.com/AdguardTeam/dnsproxy v0.33.9 h1:HUwywkhUV/M73E7qWcBAF+SdsNq742s82Lvox4pr/tM= +github.com/AdguardTeam/dnsproxy v0.33.9/go.mod h1:dkI9VWh43XlOzF2XogDm1EmoVl7PANOR4isQV6X9LZs= github.com/AdguardTeam/golibs v0.4.0/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4= github.com/AdguardTeam/golibs v0.4.2 h1:7M28oTZFoFwNmp8eGPb3ImmYbxGaJLyQXeIFVHjME0o= github.com/AdguardTeam/golibs v0.4.2/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4= diff --git a/internal/dnsforward/config.go b/internal/dnsforward/config.go index ee3f6aa0..748b14b2 100644 --- a/internal/dnsforward/config.go +++ b/internal/dnsforward/config.go @@ -24,11 +24,12 @@ type FilteringConfig struct { // Callbacks for other modules // -- - // Filtering callback function - FilterHandler func(clientAddr net.IP, settings *dnsfilter.RequestFilteringSettings) `yaml:"-"` + // FilterHandler is an optional additional filtering callback. + FilterHandler func(clientAddr net.IP, clientID string, settings *dnsfilter.RequestFilteringSettings) `yaml:"-"` // GetCustomUpstreamByClient - a callback function that returns upstreams configuration // based on the client IP address. Returns nil if there are no custom upstreams for the client + // // TODO(e.burkov): Replace argument type with net.IP. GetCustomUpstreamByClient func(clientAddr string) *proxy.UpstreamConfig `yaml:"-"` @@ -109,6 +110,10 @@ type TLSConfig struct { CertificateChainData []byte `yaml:"-" json:"-"` PrivateKeyData []byte `yaml:"-" json:"-"` + // ServerName is the hostname of the server. Currently, it is only + // being used for client ID checking. + ServerName string `yaml:"-" json:"-"` + cert tls.Certificate // DNS names from certificate (SAN) or CN value from Subject dnsNames []string diff --git a/internal/dnsforward/dns.go b/internal/dnsforward/dns.go index 10a965e2..f8e7bff0 100644 --- a/internal/dnsforward/dns.go +++ b/internal/dnsforward/dns.go @@ -1,7 +1,10 @@ package dnsforward import ( + "crypto/tls" + "fmt" "net" + "path" "strings" "time" @@ -10,37 +13,64 @@ import ( "github.com/AdguardTeam/AdGuardHome/internal/util" "github.com/AdguardTeam/dnsproxy/proxy" "github.com/AdguardTeam/golibs/log" + "github.com/lucas-clemente/quic-go" "github.com/miekg/dns" ) // To transfer information between modules type dnsContext struct { - srv *Server - proxyCtx *proxy.DNSContext - setts *dnsfilter.RequestFilteringSettings // filtering settings for this client - startTime time.Time - result *dnsfilter.Result - origResp *dns.Msg // response received from upstream servers. Set when response is modified by filtering - origQuestion dns.Question // question received from client. Set when Rewrites are used. - err error // error returned from the module - protectionEnabled bool // filtering is enabled, dnsfilter object is ready - responseFromUpstream bool // response is received from upstream servers - origReqDNSSEC bool // DNSSEC flag in the original request from user + srv *Server + proxyCtx *proxy.DNSContext + // setts are the filtering settings for the client. + setts *dnsfilter.RequestFilteringSettings + startTime time.Time + result *dnsfilter.Result + // origResp is the response received from upstream. It is set when the + // response is modified by filters. + origResp *dns.Msg + // err is the error returned from a processing function. + err error + // clientID is the clientID from DOH, DOQ, or DOT, if provided. + clientID string + // origQuestion is the question received from the client. It is set + // when the request is modified by rewrites. + origQuestion dns.Question + // protectionEnabled shows if the filtering is enabled, and if the + // server's DNS filter is ready. + protectionEnabled bool + // responseFromUpstream shows if the response is received from the + // upstream servers. + responseFromUpstream bool + // origReqDNSSEC shows if the DNSSEC flag in the original request from + // the client is set. + origReqDNSSEC bool } +// resultCode is the result of a request processing function. +type resultCode int + const ( - resultDone = iota // module has completed its job, continue - resultFinish // module has completed its job, exit normally - resultError // an error occurred, exit with an error + // resultCodeSuccess is returned when a handler performed successfully, + // and the next handler must be called. + resultCodeSuccess resultCode = iota + // resultCodeFinish is returned when a handler performed successfully, + // and the processing of the request must be stopped. + resultCodeFinish + // resultCodeError is returned when a handler failed, and the processing + // of the request must be stopped. + resultCodeError ) // handleDNSRequest filters the incoming DNS requests and writes them to the query log func (s *Server) handleDNSRequest(_ *proxy.Proxy, d *proxy.DNSContext) error { - ctx := &dnsContext{srv: s, proxyCtx: d} - ctx.result = &dnsfilter.Result{} - ctx.startTime = time.Now() + ctx := &dnsContext{ + srv: s, + proxyCtx: d, + result: &dnsfilter.Result{}, + startTime: time.Now(), + } - type modProcessFunc func(ctx *dnsContext) int + type modProcessFunc func(ctx *dnsContext) (rc resultCode) // Since (*dnsforward.Server).handleDNSRequest(...) is used as // proxy.(Config).RequestHandler, there is no need for additional index @@ -51,6 +81,7 @@ func (s *Server) handleDNSRequest(_ *proxy.Proxy, d *proxy.DNSContext) error { processInitial, processInternalHosts, processInternalIPAddrs, + processClientID, processFilteringBeforeRequest, processUpstream, processDNSSECAfterResponse, @@ -61,13 +92,13 @@ func (s *Server) handleDNSRequest(_ *proxy.Proxy, d *proxy.DNSContext) error { for _, process := range mods { r := process(ctx) switch r { - case resultDone: + case resultCodeSuccess: // continue: call the next filter - case resultFinish: + case resultCodeFinish: return nil - case resultError: + case resultCodeError: return ctx.err } } @@ -79,12 +110,12 @@ func (s *Server) handleDNSRequest(_ *proxy.Proxy, d *proxy.DNSContext) error { } // Perform initial checks; process WHOIS & rDNS -func processInitial(ctx *dnsContext) int { +func processInitial(ctx *dnsContext) (rc resultCode) { s := ctx.srv d := ctx.proxyCtx if s.conf.AAAADisabled && d.Req.Question[0].Qtype == dns.TypeAAAA { _ = proxy.CheckDisabledAAAARequest(d, true) - return resultFinish + return resultCodeFinish } if s.conf.OnDNSRequest != nil { @@ -96,10 +127,10 @@ func processInitial(ctx *dnsContext) int { if (d.Req.Question[0].Qtype == dns.TypeA || d.Req.Question[0].Qtype == dns.TypeAAAA) && d.Req.Question[0].Name == "use-application-dns.net." { d.Res = s.genNXDomain(d.Req) - return resultFinish + return resultCodeFinish } - return resultDone + return resultCodeSuccess } // Return TRUE if host names doesn't contain disallowed characters @@ -157,29 +188,29 @@ func (s *Server) onDHCPLeaseChanged(flags int) { } // Respond to A requests if the target host name is associated with a lease from our DHCP server -func processInternalHosts(ctx *dnsContext) int { +func processInternalHosts(ctx *dnsContext) (rc resultCode) { s := ctx.srv req := ctx.proxyCtx.Req if !(req.Question[0].Qtype == dns.TypeA || req.Question[0].Qtype == dns.TypeAAAA) { - return resultDone + return resultCodeSuccess } host := req.Question[0].Name host = strings.ToLower(host) if !strings.HasSuffix(host, ".lan.") { - return resultDone + return resultCodeSuccess } host = strings.TrimSuffix(host, ".lan.") s.tableHostToIPLock.Lock() if s.tableHostToIP == nil { s.tableHostToIPLock.Unlock() - return resultDone + return resultCodeSuccess } ip, ok := s.tableHostToIP[host] s.tableHostToIPLock.Unlock() if !ok { - return resultDone + return resultCodeSuccess } log.Debug("DNS: internal record: %s -> %s", req.Question[0].Name, ip) @@ -200,15 +231,163 @@ func processInternalHosts(ctx *dnsContext) int { } ctx.proxyCtx.Res = resp - return resultDone + return resultCodeSuccess +} + +const maxDomainPartLen = 64 + +// ValidateClientID returns an error if clientID is not a valid client ID. +func ValidateClientID(clientID string) (err error) { + if len(clientID) > maxDomainPartLen { + return fmt.Errorf("client id %q is too long, max: %d", clientID, maxDomainPartLen) + } + + for i, r := range clientID { + if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == '-' { + continue + } + + return fmt.Errorf("invalid char %q at index %d in client id %q", r, i, clientID) + } + + return nil +} + +// clientIDFromClientServerName extracts and validates a client ID. hostSrvName +// is the server name of the host. cliSrvName is the server name as sent by the +// client. +func clientIDFromClientServerName(hostSrvName, cliSrvName string) (clientID string, err error) { + if hostSrvName == cliSrvName { + return "", nil + } + + if !strings.HasSuffix(cliSrvName, hostSrvName) { + return "", fmt.Errorf("client server name %q doesn't match host server name %q", cliSrvName, hostSrvName) + } + + clientID = cliSrvName[:len(cliSrvName)-len(hostSrvName)-1] + err = ValidateClientID(clientID) + if err != nil { + return "", fmt.Errorf("invalid client id: %w", err) + } + + return clientID, nil +} + +// processClientIDHTTPS extracts the client's ID from the path of the +// client's DNS-over-HTTPS request. +func processClientIDHTTPS(ctx *dnsContext) (rc resultCode) { + pctx := ctx.proxyCtx + r := pctx.HTTPRequest + if r == nil { + ctx.err = fmt.Errorf("proxy ctx http request of proto %s is nil", pctx.Proto) + + return resultCodeError + } + + origPath := r.URL.Path + parts := strings.Split(path.Clean(origPath), "/") + if parts[0] == "" { + parts = parts[1:] + } + + if len(parts) == 0 || parts[0] != "dns-query" { + ctx.err = fmt.Errorf("client id check: invalid path %q", origPath) + + return resultCodeError + } + + clientID := "" + switch len(parts) { + case 1: + // Just /dns-query, no client ID. + return resultCodeSuccess + case 2: + clientID = parts[1] + default: + ctx.err = fmt.Errorf("client id check: invalid path %q: extra parts", origPath) + + return resultCodeError + } + + err := ValidateClientID(clientID) + if err != nil { + ctx.err = fmt.Errorf("client id check: invalid client id: %w", err) + + return resultCodeError + } + + ctx.clientID = clientID + + return resultCodeSuccess +} + +// tlsConn is a narrow interface for *tls.Conn to simplify testing. +type tlsConn interface { + ConnectionState() (cs tls.ConnectionState) +} + +// quicSession is a narrow interface for quic.Session to simplify testing. +type quicSession interface { + ConnectionState() (cs quic.ConnectionState) +} + +// processClientID extracts the client's ID from the server name of the client's +// DOT or DOQ request or the path of the client's DOH. +func processClientID(ctx *dnsContext) (rc resultCode) { + pctx := ctx.proxyCtx + proto := pctx.Proto + if proto == proxy.ProtoHTTPS { + return processClientIDHTTPS(ctx) + } else if proto != proxy.ProtoTLS && proto != proxy.ProtoQUIC { + return resultCodeSuccess + } + + hostSrvName := ctx.srv.conf.TLSConfig.ServerName + if hostSrvName == "" { + return resultCodeSuccess + } + + cliSrvName := "" + if proto == proxy.ProtoTLS { + conn := pctx.Conn + tc, ok := conn.(tlsConn) + if !ok { + ctx.err = fmt.Errorf("proxy ctx conn of proto %s is %T, want *tls.Conn", proto, conn) + + return resultCodeError + } + + cliSrvName = tc.ConnectionState().ServerName + } else if proto == proxy.ProtoQUIC { + qs, ok := pctx.QUICSession.(quicSession) + if !ok { + ctx.err = fmt.Errorf("proxy ctx quic session of proto %s is %T, want quic.Session", proto, pctx.QUICSession) + + return resultCodeError + } + + cliSrvName = qs.ConnectionState().ServerName + } + + clientID, err := clientIDFromClientServerName(hostSrvName, cliSrvName) + if err != nil { + ctx.err = fmt.Errorf("client id check: %w", err) + + return resultCodeError + } + + ctx.clientID = clientID + + return resultCodeSuccess } // Respond to PTR requests if the target IP address is leased by our DHCP server -func processInternalIPAddrs(ctx *dnsContext) int { +func processInternalIPAddrs(ctx *dnsContext) (rc resultCode) { s := ctx.srv req := ctx.proxyCtx.Req if req.Question[0].Qtype != dns.TypePTR { - return resultDone + return resultCodeSuccess } arpa := req.Question[0].Name @@ -216,18 +395,18 @@ func processInternalIPAddrs(ctx *dnsContext) int { arpa = strings.ToLower(arpa) ip := util.DNSUnreverseAddr(arpa) if ip == nil { - return resultDone + return resultCodeSuccess } s.tablePTRLock.Lock() if s.tablePTR == nil { s.tablePTRLock.Unlock() - return resultDone + return resultCodeSuccess } host, ok := s.tablePTR[ip.String()] s.tablePTRLock.Unlock() if !ok { - return resultDone + return resultCodeSuccess } log.Debug("DNS: reverse-lookup: %s -> %s", arpa, host) @@ -243,16 +422,16 @@ func processInternalIPAddrs(ctx *dnsContext) int { ptr.Ptr = host + "." resp.Answer = append(resp.Answer, ptr) ctx.proxyCtx.Res = resp - return resultDone + return resultCodeSuccess } // Apply filtering logic -func processFilteringBeforeRequest(ctx *dnsContext) int { +func processFilteringBeforeRequest(ctx *dnsContext) (rc resultCode) { s := ctx.srv d := ctx.proxyCtx if d.Res != nil { - return resultDone // response is already set - nothing to do + return resultCodeSuccess // response is already set - nothing to do } s.RLock() @@ -266,24 +445,24 @@ func processFilteringBeforeRequest(ctx *dnsContext) int { var err error ctx.protectionEnabled = s.conf.ProtectionEnabled && s.dnsFilter != nil if ctx.protectionEnabled { - ctx.setts = s.getClientRequestFilteringSettings(d) + ctx.setts = s.getClientRequestFilteringSettings(ctx) ctx.result, err = s.filterDNSRequest(ctx) } s.RUnlock() if err != nil { ctx.err = err - return resultError + return resultCodeError } - return resultDone + return resultCodeSuccess } // processUpstream passes request to upstream servers and handles the response. -func processUpstream(ctx *dnsContext) int { +func processUpstream(ctx *dnsContext) (rc resultCode) { s := ctx.srv d := ctx.proxyCtx if d.Res != nil { - return resultDone // response is already set - nothing to do + return resultCodeSuccess // response is already set - nothing to do } if d.Addr != nil && s.conf.GetCustomUpstreamByClient != nil { @@ -311,26 +490,26 @@ func processUpstream(ctx *dnsContext) int { err := s.dnsProxy.Resolve(d) if err != nil { ctx.err = err - return resultError + return resultCodeError } ctx.responseFromUpstream = true - return resultDone + return resultCodeSuccess } // Process DNSSEC after response from upstream server -func processDNSSECAfterResponse(ctx *dnsContext) int { +func processDNSSECAfterResponse(ctx *dnsContext) (rc resultCode) { d := ctx.proxyCtx if !ctx.responseFromUpstream || // don't process response if it's not from upstream servers !ctx.srv.conf.EnableDNSSEC { - return resultDone + return resultCodeSuccess } if !ctx.origReqDNSSEC { optResp := d.Res.IsEdns0() if optResp != nil && !optResp.Do() { - return resultDone + return resultCodeSuccess } // Remove RRSIG records from response @@ -361,11 +540,11 @@ func processDNSSECAfterResponse(ctx *dnsContext) int { d.Res.Ns = answers } - return resultDone + return resultCodeSuccess } // Apply filtering logic after we have received response from upstream servers -func processFilteringAfterResponse(ctx *dnsContext) int { +func processFilteringAfterResponse(ctx *dnsContext) (rc resultCode) { s := ctx.srv d := ctx.proxyCtx res := ctx.result @@ -402,7 +581,7 @@ func processFilteringAfterResponse(ctx *dnsContext) int { ctx.result, err = s.filterDNSResponse(ctx) if err != nil { ctx.err = err - return resultError + return resultCodeError } if ctx.result != nil { ctx.origResp = origResp2 // matched by response @@ -411,5 +590,5 @@ func processFilteringAfterResponse(ctx *dnsContext) int { } } - return resultDone + return resultCodeSuccess } diff --git a/internal/dnsforward/dns_test.go b/internal/dnsforward/dns_test.go new file mode 100644 index 00000000..bd0ef4ab --- /dev/null +++ b/internal/dnsforward/dns_test.go @@ -0,0 +1,235 @@ +package dnsforward + +import ( + "crypto/tls" + "net" + "net/http" + "net/url" + "testing" + + "github.com/AdguardTeam/dnsproxy/proxy" + "github.com/lucas-clemente/quic-go" + "github.com/stretchr/testify/assert" +) + +// testTLSConn is a tlsConn for tests. +type testTLSConn struct { + // Conn is embedded here simply to make testTLSConn a net.Conn without + // acctually implementing all methods. + net.Conn + + serverName string +} + +// ConnectionState implements the tlsConn interface for testTLSConn. +func (c testTLSConn) ConnectionState() (cs tls.ConnectionState) { + cs.ServerName = c.serverName + + return cs +} + +// testQUICSession is a quicSession for tests. +type testQUICSession struct { + // Session is embedded here simply to make testQUICSession + // a quic.Session without acctually implementing all methods. + quic.Session + + serverName string +} + +// ConnectionState implements the quicSession interface for testQUICSession. +func (c testQUICSession) ConnectionState() (cs quic.ConnectionState) { + cs.ServerName = c.serverName + + return cs +} + +func TestProcessClientID(t *testing.T) { + testCases := []struct { + name string + proto string + hostSrvName string + cliSrvName string + wantClientID string + wantErrMsg string + wantRes resultCode + }{{ + name: "udp", + proto: proxy.ProtoUDP, + hostSrvName: "", + cliSrvName: "", + wantClientID: "", + wantErrMsg: "", + wantRes: resultCodeSuccess, + }, { + name: "tls_no_client_id", + proto: proxy.ProtoTLS, + hostSrvName: "example.com", + cliSrvName: "example.com", + wantClientID: "", + wantErrMsg: "", + wantRes: resultCodeSuccess, + }, { + name: "tls_client_id", + proto: proxy.ProtoTLS, + hostSrvName: "example.com", + cliSrvName: "cli.example.com", + wantClientID: "cli", + wantErrMsg: "", + wantRes: resultCodeSuccess, + }, { + name: "tls_client_id_hostname_error", + proto: proxy.ProtoTLS, + hostSrvName: "example.com", + cliSrvName: "cli.example.net", + wantClientID: "", + wantErrMsg: `client id check: client server name "cli.example.net" doesn't match host server name "example.com"`, + wantRes: resultCodeError, + }, { + name: "tls_invalid_client_id", + proto: proxy.ProtoTLS, + hostSrvName: "example.com", + cliSrvName: "!!!.example.com", + wantClientID: "", + wantErrMsg: `client id check: invalid client id: invalid char '!' at index 0 in client id "!!!"`, + wantRes: resultCodeError, + }, { + name: "tls_client_id_too_long", + proto: proxy.ProtoTLS, + hostSrvName: "example.com", + cliSrvName: "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789.example.com", + wantClientID: "", + wantErrMsg: `client id check: invalid client id: client id "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789" is too long, max: 64`, + wantRes: resultCodeError, + }, { + name: "quic_client_id", + proto: proxy.ProtoQUIC, + hostSrvName: "example.com", + cliSrvName: "cli.example.com", + wantClientID: "cli", + wantErrMsg: "", + wantRes: resultCodeSuccess, + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + srv := &Server{ + conf: ServerConfig{ + TLSConfig: TLSConfig{ServerName: tc.hostSrvName}, + }, + } + + var conn net.Conn + if tc.proto == proxy.ProtoTLS { + conn = testTLSConn{ + serverName: tc.cliSrvName, + } + } + + var qs quic.Session + if tc.proto == proxy.ProtoQUIC { + qs = testQUICSession{ + serverName: tc.cliSrvName, + } + } + + dctx := &dnsContext{ + srv: srv, + proxyCtx: &proxy.DNSContext{ + Proto: tc.proto, + Conn: conn, + QUICSession: qs, + }, + } + + res := processClientID(dctx) + assert.Equal(t, tc.wantRes, res) + assert.Equal(t, tc.wantClientID, dctx.clientID) + + if tc.wantErrMsg != "" && assert.NotNil(t, dctx.err) { + assert.Equal(t, tc.wantErrMsg, dctx.err.Error()) + } else { + assert.Nil(t, dctx.err) + } + }) + } +} + +func TestProcessClientID_https(t *testing.T) { + testCases := []struct { + name string + path string + wantClientID string + wantErrMsg string + wantRes resultCode + }{{ + name: "no_client_id", + path: "/dns-query", + wantClientID: "", + wantErrMsg: "", + wantRes: resultCodeSuccess, + }, { + name: "no_client_id_slash", + path: "/dns-query/", + wantClientID: "", + wantErrMsg: "", + wantRes: resultCodeSuccess, + }, { + name: "client_id", + path: "/dns-query/cli", + wantClientID: "cli", + wantErrMsg: "", + wantRes: resultCodeSuccess, + }, { + name: "client_id_slash", + path: "/dns-query/cli/", + wantClientID: "cli", + wantErrMsg: "", + wantRes: resultCodeSuccess, + }, { + name: "bad_url", + path: "/foo", + wantClientID: "", + wantErrMsg: `client id check: invalid path "/foo"`, + wantRes: resultCodeError, + }, { + name: "extra", + path: "/dns-query/cli/foo", + wantClientID: "", + wantErrMsg: `client id check: invalid path "/dns-query/cli/foo": extra parts`, + wantRes: resultCodeError, + }, { + name: "invalid_client_id", + path: "/dns-query/!!!", + wantClientID: "", + wantErrMsg: `client id check: invalid client id: invalid char '!' at index 0 in client id "!!!"`, + wantRes: resultCodeError, + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + r := &http.Request{ + URL: &url.URL{ + Path: tc.path, + }, + } + + dctx := &dnsContext{ + proxyCtx: &proxy.DNSContext{ + Proto: proxy.ProtoHTTPS, + HTTPRequest: r, + }, + } + + res := processClientID(dctx) + assert.Equal(t, tc.wantRes, res) + assert.Equal(t, tc.wantClientID, dctx.clientID) + + if tc.wantErrMsg != "" && assert.NotNil(t, dctx.err) { + assert.Equal(t, tc.wantErrMsg, dctx.err.Error()) + } else { + assert.Nil(t, dctx.err) + } + }) + } +} diff --git a/internal/dnsforward/dnsforward_test.go b/internal/dnsforward/dnsforward_test.go index ec56c9e8..17f4b872 100644 --- a/internal/dnsforward/dnsforward_test.go +++ b/internal/dnsforward/dnsforward_test.go @@ -473,7 +473,7 @@ func TestBlockCNAME(t *testing.T) { func TestClientRulesForCNAMEMatching(t *testing.T) { s := createTestServer(t) testUpstm := &testUpstream{testCNAMEs, testIPv4, nil} - s.conf.FilterHandler = func(_ net.IP, settings *dnsfilter.RequestFilteringSettings) { + s.conf.FilterHandler = func(_ net.IP, _ string, settings *dnsfilter.RequestFilteringSettings) { settings.FilteringEnabled = false } err := s.startWithUpstream(testUpstm) @@ -1033,8 +1033,7 @@ func TestMatchDNSName(t *testing.T) { assert.False(t, matchDNSName(dnsNames, "*.host2")) } -type testDHCP struct { -} +type testDHCP struct{} func (d *testDHCP) Leases(flags int) []dhcpd.Lease { l := dhcpd.Lease{} diff --git a/internal/dnsforward/filter.go b/internal/dnsforward/filter.go index 4d319288..f3d9f758 100644 --- a/internal/dnsforward/filter.go +++ b/internal/dnsforward/filter.go @@ -30,14 +30,15 @@ func (s *Server) beforeRequestHandler(_ *proxy.Proxy, d *proxy.DNSContext) (bool return true, nil } -// getClientRequestFilteringSettings lookups client filtering settings -// using the client's IP address from the DNSContext -func (s *Server) getClientRequestFilteringSettings(d *proxy.DNSContext) *dnsfilter.RequestFilteringSettings { +// getClientRequestFilteringSettings looks up client filtering settings using +// the client's IP address and ID, if any, from ctx. +func (s *Server) getClientRequestFilteringSettings(ctx *dnsContext) *dnsfilter.RequestFilteringSettings { setts := s.dnsFilter.GetConfig() setts.FilteringEnabled = true if s.conf.FilterHandler != nil { - s.conf.FilterHandler(IPFromAddr(d.Addr), &setts) + s.conf.FilterHandler(IPFromAddr(ctx.proxyCtx.Addr), ctx.clientID, &setts) } + return &setts } diff --git a/internal/dnsforward/http.go b/internal/dnsforward/http.go index 3a6d6578..e96c6c3f 100644 --- a/internal/dnsforward/http.go +++ b/internal/dnsforward/http.go @@ -529,5 +529,5 @@ func (s *Server) registerHandlers() { s.conf.HTTPRegister(http.MethodGet, "/control/access/list", s.handleAccessList) s.conf.HTTPRegister(http.MethodPost, "/control/access/set", s.handleAccessSet) - s.conf.HTTPRegister("", "/dns-query", s.handleDOH) + s.conf.HTTPRegister("", "/dns-query/", s.handleDOH) } diff --git a/internal/dnsforward/ipset.go b/internal/dnsforward/ipset.go index 74a8ecd8..7421edda 100644 --- a/internal/dnsforward/ipset.go +++ b/internal/dnsforward/ipset.go @@ -99,12 +99,12 @@ func (c *ipsetCtx) getIP(rr dns.RR) net.IP { } // Add IP addresses of the specified in configuration domain names to an ipset list -func (c *ipsetCtx) process(ctx *dnsContext) int { +func (c *ipsetCtx) process(ctx *dnsContext) (rc resultCode) { req := ctx.proxyCtx.Req if !(req.Question[0].Qtype == dns.TypeA || req.Question[0].Qtype == dns.TypeAAAA) || !ctx.responseFromUpstream { - return resultDone + return resultCodeSuccess } host := req.Question[0].Name @@ -112,7 +112,7 @@ func (c *ipsetCtx) process(ctx *dnsContext) int { host = strings.ToLower(host) ipsetNames, found := c.ipsetList[host] if !found { - return resultDone + return resultCodeSuccess } log.Debug("IPSET: found ipsets %v for host %s", ipsetNames, host) @@ -138,5 +138,5 @@ func (c *ipsetCtx) process(ctx *dnsContext) int { } } - return resultDone + return resultCodeSuccess } diff --git a/internal/dnsforward/ipset_test.go b/internal/dnsforward/ipset_test.go index 41be83d2..f08e4d08 100644 --- a/internal/dnsforward/ipset_test.go +++ b/internal/dnsforward/ipset_test.go @@ -37,5 +37,5 @@ func TestIPSET(t *testing.T) { }, }, } - assert.Equal(t, resultDone, c.process(ctx)) + assert.Equal(t, resultCodeSuccess, c.process(ctx)) } diff --git a/internal/dnsforward/stats.go b/internal/dnsforward/stats.go index be45b0f9..29e6bb86 100644 --- a/internal/dnsforward/stats.go +++ b/internal/dnsforward/stats.go @@ -1,7 +1,6 @@ package dnsforward import ( - "net" "strings" "time" @@ -13,13 +12,13 @@ import ( ) // Write Stats data and logs -func processQueryLogsAndStats(ctx *dnsContext) int { +func processQueryLogsAndStats(ctx *dnsContext) (rc resultCode) { elapsed := time.Since(ctx.startTime) s := ctx.srv - d := ctx.proxyCtx + pctx := ctx.proxyCtx shouldLog := true - msg := d.Req + msg := pctx.Req // don't log ANY request if refuseAny is enabled if len(msg.Question) >= 1 && msg.Question[0].Qtype == dns.TypeANY && s.conf.RefuseAny { @@ -32,65 +31,67 @@ func processQueryLogsAndStats(ctx *dnsContext) int { if shouldLog && s.queryLog != nil { p := querylog.AddParams{ Question: msg, - Answer: d.Res, + Answer: pctx.Res, OrigAnswer: ctx.origResp, Result: ctx.result, Elapsed: elapsed, - ClientIP: IPFromAddr(d.Addr), + ClientIP: IPFromAddr(pctx.Addr), + ClientID: ctx.clientID, } - switch d.Proto { + switch pctx.Proto { case proxy.ProtoHTTPS: p.ClientProto = querylog.ClientProtoDOH case proxy.ProtoQUIC: p.ClientProto = querylog.ClientProtoDOQ case proxy.ProtoTLS: p.ClientProto = querylog.ClientProtoDOT + case proxy.ProtoDNSCrypt: + p.ClientProto = querylog.ClientProtoDNSCrypt default: - // Consider this a plain DNS-over-UDP or DNS-over-TCL + // Consider this a plain DNS-over-UDP or DNS-over-TCP // request. } - if d.Upstream != nil { - p.Upstream = d.Upstream.Address() + if pctx.Upstream != nil { + p.Upstream = pctx.Upstream.Address() } + s.queryLog.Add(p) } - s.updateStats(d, elapsed, *ctx.result) + s.updateStats(ctx, elapsed, *ctx.result) s.RUnlock() - return resultDone + return resultCodeSuccess } -func (s *Server) updateStats(d *proxy.DNSContext, elapsed time.Duration, res dnsfilter.Result) { +func (s *Server) updateStats(ctx *dnsContext, elapsed time.Duration, res dnsfilter.Result) { if s.stats == nil { return } + pctx := ctx.proxyCtx e := stats.Entry{} - e.Domain = strings.ToLower(d.Req.Question[0].Name) + e.Domain = strings.ToLower(pctx.Req.Question[0].Name) e.Domain = e.Domain[:len(e.Domain)-1] // remove last "." - switch addr := d.Addr.(type) { - case *net.UDPAddr: - e.Client = addr.IP - case *net.TCPAddr: - e.Client = addr.IP + + if clientID := ctx.clientID; clientID != "" { + e.Client = clientID + } else if ip := IPFromAddr(pctx.Addr); ip != nil { + e.Client = ip.String() } + e.Time = uint32(elapsed / 1000) e.Result = stats.RNotFiltered switch res.Reason { - case dnsfilter.FilteredSafeBrowsing: e.Result = stats.RSafeBrowsing - case dnsfilter.FilteredParental: e.Result = stats.RParental - case dnsfilter.FilteredSafeSearch: e.Result = stats.RSafeSearch - case dnsfilter.FilteredBlockList: fallthrough case dnsfilter.FilteredInvalid: diff --git a/internal/dnsforward/stats_test.go b/internal/dnsforward/stats_test.go new file mode 100644 index 00000000..3b5981bb --- /dev/null +++ b/internal/dnsforward/stats_test.go @@ -0,0 +1,198 @@ +package dnsforward + +import ( + "net" + "testing" + "time" + + "github.com/AdguardTeam/AdGuardHome/internal/dnsfilter" + "github.com/AdguardTeam/AdGuardHome/internal/querylog" + "github.com/AdguardTeam/AdGuardHome/internal/stats" + "github.com/AdguardTeam/dnsproxy/proxy" + "github.com/AdguardTeam/dnsproxy/upstream" + "github.com/miekg/dns" + "github.com/stretchr/testify/assert" +) + +// testQueryLog is a simple querylog.QueryLog implementation for tests. +type testQueryLog struct { + // QueryLog is embedded here simply to make testQueryLog + // a querylog.QueryLog without acctually implementing all methods. + querylog.QueryLog + + lastParams querylog.AddParams +} + +// Add implements the querylog.QueryLog interface for *testQueryLog. +func (l *testQueryLog) Add(p querylog.AddParams) { + l.lastParams = p +} + +// testStats is a simple stats.Stats implementation for tests. +type testStats struct { + // Stats is embedded here simply to make testStats a stats.Stats without + // acctually implementing all methods. + stats.Stats + + lastEntry stats.Entry +} + +// Update implements the stats.Stats interface for *testStats. +func (l *testStats) Update(e stats.Entry) { + l.lastEntry = e +} + +func TestProcessQueryLogsAndStats(t *testing.T) { + testCases := []struct { + name string + proto string + addr net.Addr + clientID string + wantLogProto querylog.ClientProto + wantStatClient string + wantCode resultCode + reason dnsfilter.Reason + wantStatResult stats.Result + }{{ + name: "success_udp", + proto: proxy.ProtoUDP, + addr: &net.UDPAddr{IP: net.IP{1, 2, 3, 4}, Port: 1234}, + clientID: "", + wantLogProto: "", + wantStatClient: "1.2.3.4", + wantCode: resultCodeSuccess, + reason: dnsfilter.NotFilteredNotFound, + wantStatResult: stats.RNotFiltered, + }, { + name: "success_tls_client_id", + proto: proxy.ProtoTLS, + addr: &net.TCPAddr{IP: net.IP{1, 2, 3, 4}, Port: 1234}, + clientID: "cli42", + wantLogProto: querylog.ClientProtoDOT, + wantStatClient: "cli42", + wantCode: resultCodeSuccess, + reason: dnsfilter.NotFilteredNotFound, + wantStatResult: stats.RNotFiltered, + }, { + name: "success_tls", + proto: proxy.ProtoTLS, + addr: &net.TCPAddr{IP: net.IP{1, 2, 3, 4}, Port: 1234}, + clientID: "", + wantLogProto: querylog.ClientProtoDOT, + wantStatClient: "1.2.3.4", + wantCode: resultCodeSuccess, + reason: dnsfilter.NotFilteredNotFound, + wantStatResult: stats.RNotFiltered, + }, { + name: "success_quic", + proto: proxy.ProtoQUIC, + addr: &net.UDPAddr{IP: net.IP{1, 2, 3, 4}, Port: 1234}, + clientID: "", + wantLogProto: querylog.ClientProtoDOQ, + wantStatClient: "1.2.3.4", + wantCode: resultCodeSuccess, + reason: dnsfilter.NotFilteredNotFound, + wantStatResult: stats.RNotFiltered, + }, { + name: "success_https", + proto: proxy.ProtoHTTPS, + addr: &net.TCPAddr{IP: net.IP{1, 2, 3, 4}, Port: 1234}, + clientID: "", + wantLogProto: querylog.ClientProtoDOH, + wantStatClient: "1.2.3.4", + wantCode: resultCodeSuccess, + reason: dnsfilter.NotFilteredNotFound, + wantStatResult: stats.RNotFiltered, + }, { + name: "success_dnscrypt", + proto: proxy.ProtoDNSCrypt, + addr: &net.TCPAddr{IP: net.IP{1, 2, 3, 4}, Port: 1234}, + clientID: "", + wantLogProto: querylog.ClientProtoDNSCrypt, + wantStatClient: "1.2.3.4", + wantCode: resultCodeSuccess, + reason: dnsfilter.NotFilteredNotFound, + wantStatResult: stats.RNotFiltered, + }, { + name: "success_udp_filtered", + proto: proxy.ProtoUDP, + addr: &net.UDPAddr{IP: net.IP{1, 2, 3, 4}, Port: 1234}, + clientID: "", + wantLogProto: "", + wantStatClient: "1.2.3.4", + wantCode: resultCodeSuccess, + reason: dnsfilter.FilteredBlockList, + wantStatResult: stats.RFiltered, + }, { + name: "success_udp_sb", + proto: proxy.ProtoUDP, + addr: &net.UDPAddr{IP: net.IP{1, 2, 3, 4}, Port: 1234}, + clientID: "", + wantLogProto: "", + wantStatClient: "1.2.3.4", + wantCode: resultCodeSuccess, + reason: dnsfilter.FilteredSafeBrowsing, + wantStatResult: stats.RSafeBrowsing, + }, { + name: "success_udp_ss", + proto: proxy.ProtoUDP, + addr: &net.UDPAddr{IP: net.IP{1, 2, 3, 4}, Port: 1234}, + clientID: "", + wantLogProto: "", + wantStatClient: "1.2.3.4", + wantCode: resultCodeSuccess, + reason: dnsfilter.FilteredSafeSearch, + wantStatResult: stats.RSafeSearch, + }, { + name: "success_udp_pc", + proto: proxy.ProtoUDP, + addr: &net.UDPAddr{IP: net.IP{1, 2, 3, 4}, Port: 1234}, + clientID: "", + wantLogProto: "", + wantStatClient: "1.2.3.4", + wantCode: resultCodeSuccess, + reason: dnsfilter.FilteredParental, + wantStatResult: stats.RParental, + }} + + ups, err := upstream.AddressToUpstream("1.1.1.1", upstream.Options{}) + assert.Nil(t, err) + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + req := &dns.Msg{ + Question: []dns.Question{{ + Name: "example.com.", + }}, + } + pctx := &proxy.DNSContext{ + Proto: tc.proto, + Req: req, + Res: &dns.Msg{}, + Addr: tc.addr, + Upstream: ups, + } + + ql := &testQueryLog{} + st := &testStats{} + dctx := &dnsContext{ + srv: &Server{ + queryLog: ql, + stats: st, + }, + proxyCtx: pctx, + startTime: time.Now(), + result: &dnsfilter.Result{ + Reason: tc.reason, + }, + clientID: tc.clientID, + } + + code := processQueryLogsAndStats(dctx) + assert.Equal(t, tc.wantCode, code) + assert.Equal(t, tc.wantLogProto, ql.lastParams.ClientProto) + assert.Equal(t, tc.wantStatClient, st.lastEntry.Client) + assert.Equal(t, tc.wantStatResult, st.lastEntry.Result) + }) + } +} diff --git a/internal/home/clients.go b/internal/home/clients.go index b631f63e..c3eb366f 100644 --- a/internal/home/clients.go +++ b/internal/home/clients.go @@ -11,23 +11,21 @@ import ( "sync" "time" - "github.com/AdguardTeam/dnsproxy/proxy" - + "github.com/AdguardTeam/AdGuardHome/internal/agherr" "github.com/AdguardTeam/AdGuardHome/internal/dhcpd" "github.com/AdguardTeam/AdGuardHome/internal/dnsfilter" "github.com/AdguardTeam/AdGuardHome/internal/dnsforward" "github.com/AdguardTeam/AdGuardHome/internal/util" + "github.com/AdguardTeam/dnsproxy/proxy" "github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/utils" ) -const ( - clientsUpdatePeriod = 10 * time.Minute -) +const clientsUpdatePeriod = 10 * time.Minute var webHandlersRegistered = false -// Client information +// Client contains information about persistent clients. type Client struct { IDs []string Tags []string @@ -52,14 +50,13 @@ type Client struct { type clientSource uint -// Client sources +// Client sources. The order determines the priority. const ( - // Priority: etc/hosts > DHCP > ARP > rDNS > WHOIS - ClientSourceWHOIS clientSource = iota // from WHOIS - ClientSourceRDNS // from rDNS - ClientSourceDHCP // from DHCP - ClientSourceARP // from 'arp -a' - ClientSourceHostsFile // from /etc/hosts + ClientSourceWHOIS clientSource = iota + ClientSourceRDNS + ClientSourceDHCP + ClientSourceARP + ClientSourceHostsFile ) // ClientHost information @@ -70,12 +67,12 @@ type ClientHost struct { } type clientsContainer struct { - list map[string]*Client // name -> client - idIndex map[string]*Client // IP -> client - // TODO(e.burkov): Think of a way to not require string conversion for - // IP addresses. - ipHost map[string]*ClientHost // IP -> Hostname - lock sync.Mutex + // TODO(a.garipov): Perhaps use a number of separate indices for + // different types (string, net.IP, and so on). + list map[string]*Client // name -> client + idIndex map[string]*Client // ID -> client + ipHost map[string]*ClientHost // IP -> Hostname + lock sync.Mutex allTags map[string]bool @@ -158,7 +155,7 @@ func (clients *clientsContainer) tagKnown(tag string) bool { func (clients *clientsContainer) addFromConfig(objects []clientObject) { for _, cy := range objects { - cli := Client{ + cli := &Client{ Name: cy.Name, IDs: cy.IDs, UseOwnSettings: !cy.UseGlobalSettings, @@ -174,7 +171,7 @@ func (clients *clientsContainer) addFromConfig(objects []clientObject) { for _, s := range cy.BlockedServices { if !dnsfilter.BlockedSvcKnown(s) { - log.Debug("Clients: skipping unknown blocked-service %q", s) + log.Debug("clients: skipping unknown blocked-service %q", s) continue } cli.BlockedServices = append(cli.BlockedServices, s) @@ -182,7 +179,7 @@ func (clients *clientsContainer) addFromConfig(objects []clientObject) { for _, t := range cy.Tags { if !clients.tagKnown(t) { - log.Debug("Clients: skipping unknown tag %q", t) + log.Debug("clients: skipping unknown tag %q", t) continue } cli.Tags = append(cli.Tags, t) @@ -210,10 +207,10 @@ func (clients *clientsContainer) WriteDiskConfig(objects *[]clientObject) { UseGlobalBlockedServices: !cli.UseOwnBlockedServices, } - cy.Tags = stringArrayDup(cli.Tags) - cy.IDs = stringArrayDup(cli.IDs) - cy.BlockedServices = stringArrayDup(cli.BlockedServices) - cy.Upstreams = stringArrayDup(cli.Upstreams) + cy.Tags = copyStrings(cli.Tags) + cy.IDs = copyStrings(cli.IDs) + cy.BlockedServices = copyStrings(cli.BlockedServices) + cy.Upstreams = copyStrings(cli.Upstreams) *objects = append(*objects, cy) } @@ -240,45 +237,44 @@ func (clients *clientsContainer) onHostsChanged() { clients.addFromHostsFile() } -// Exists checks if client with this IP already exists -func (clients *clientsContainer) Exists(ip net.IP, source clientSource) bool { +// Exists checks if client with this ID already exists. +func (clients *clientsContainer) Exists(id string, source clientSource) (ok bool) { clients.lock.Lock() defer clients.lock.Unlock() - _, ok := clients.findByIP(ip) + _, ok = clients.findLocked(id) if ok { return true } - ch, ok := clients.ipHost[ip.String()] + var ch *ClientHost + ch, ok = clients.ipHost[id] if !ok { return false } - if source > ch.Source { - return false // we're going to overwrite this client's info with a stronger source - } - return true + + // Return false if the new source has higher priority. + return source <= ch.Source } -func stringArrayDup(a []string) []string { - a2 := make([]string, len(a)) - copy(a2, a) - return a2 +func copyStrings(a []string) (b []string) { + return append(b, a...) } -// Find searches for a client by IP -func (clients *clientsContainer) Find(ip net.IP) (Client, bool) { +// Find searches for a client by its ID. +func (clients *clientsContainer) Find(id string) (c *Client, ok bool) { clients.lock.Lock() defer clients.lock.Unlock() - c, ok := clients.findByIP(ip) + c, ok = clients.findLocked(id) if !ok { - return Client{}, false + return nil, false } - c.IDs = stringArrayDup(c.IDs) - c.Tags = stringArrayDup(c.Tags) - c.BlockedServices = stringArrayDup(c.BlockedServices) - c.Upstreams = stringArrayDup(c.Upstreams) + + c.IDs = copyStrings(c.IDs) + c.Tags = copyStrings(c.Tags) + c.BlockedServices = copyStrings(c.BlockedServices) + c.Upstreams = copyStrings(c.Upstreams) return c, true } @@ -289,7 +285,7 @@ func (clients *clientsContainer) FindUpstreams(ip string) *proxy.UpstreamConfig clients.lock.Lock() defer clients.lock.Unlock() - c, ok := clients.findByIP(net.ParseIP(ip)) + c, ok := clients.findLocked(ip) if !ok { return nil } @@ -308,15 +304,16 @@ func (clients *clientsContainer) FindUpstreams(ip string) *proxy.UpstreamConfig return c.upstreamConfig } -// Find searches for a client by IP (and does not lock anything) -func (clients *clientsContainer) findByIP(ip net.IP) (Client, bool) { - if ip == nil { - return Client{}, false +// findLocked searches for a client by its ID. For internal use only. +func (clients *clientsContainer) findLocked(id string) (c *Client, ok bool) { + c, ok = clients.idIndex[id] + if ok { + return c, true } - c, ok := clients.idIndex[ip.String()] - if ok { - return *c, true + ip := net.ParseIP(id) + if ip == nil { + return nil, false } for _, c = range clients.list { @@ -325,88 +322,96 @@ func (clients *clientsContainer) findByIP(ip net.IP) (Client, bool) { if err != nil { continue } + if ipnet.Contains(ip) { - return *c, true + return c, true } } } if clients.dhcpServer == nil { - return Client{}, false + return nil, false } + macFound := clients.dhcpServer.FindMACbyIP(ip) if macFound == nil { - return Client{}, false + return nil, false } + for _, c = range clients.list { for _, id := range c.IDs { hwAddr, err := net.ParseMAC(id) if err != nil { continue } + if bytes.Equal(hwAddr, macFound) { - return *c, true + return c, true } } } - return Client{}, false + return nil, false } // FindAutoClient - search for an auto-client by IP -func (clients *clientsContainer) FindAutoClient(ip net.IP) (ClientHost, bool) { - if ip == nil { +func (clients *clientsContainer) FindAutoClient(ip string) (ClientHost, bool) { + ipAddr := net.ParseIP(ip) + if ipAddr == nil { return ClientHost{}, false } clients.lock.Lock() defer clients.lock.Unlock() - ch, ok := clients.ipHost[ip.String()] + ch, ok := clients.ipHost[ip] if ok { return *ch, true } return ClientHost{}, false } -// Check if Client object's fields are correct -func (clients *clientsContainer) check(c *Client) error { - if len(c.Name) == 0 { - return fmt.Errorf("invalid Name") - } - - if len(c.IDs) == 0 { - return fmt.Errorf("id required") +// check validates the client. +func (clients *clientsContainer) check(c *Client) (err error) { + switch { + case c == nil: + return agherr.Error("client is nil") + case c.Name == "": + return agherr.Error("invalid name") + case len(c.IDs) == 0: + return agherr.Error("id required") + default: + // Go on. } for i, id := range c.IDs { - ip := net.ParseIP(id) - if ip != nil { - c.IDs[i] = ip.String() // normalize IP address - continue + // Normalize structured data. + var ip net.IP + var ipnet *net.IPNet + var mac net.HardwareAddr + if ip = net.ParseIP(id); ip != nil { + c.IDs[i] = ip.String() + } else if ip, ipnet, err = net.ParseCIDR(id); err == nil { + ipnet.IP = ip + c.IDs[i] = ipnet.String() + } else if mac, err = net.ParseMAC(id); err == nil { + c.IDs[i] = mac.String() + } else if err = dnsforward.ValidateClientID(id); err == nil { + c.IDs[i] = id + } else { + return fmt.Errorf("invalid client id at index %d: %q", i, id) } - - _, _, err := net.ParseCIDR(id) - if err == nil { - continue - } - - _, err = net.ParseMAC(id) - if err == nil { - continue - } - - return fmt.Errorf("invalid ID: %s", id) } for _, t := range c.Tags { if !clients.tagKnown(t) { - return fmt.Errorf("invalid tag: %s", t) + return fmt.Errorf("invalid tag: %q", t) } } + sort.Strings(c.Tags) - err := dnsforward.ValidateUpstreams(c.Upstreams) + err = dnsforward.ValidateUpstreams(c.Upstreams) if err != nil { return fmt.Errorf("invalid upstream servers: %w", err) } @@ -414,49 +419,52 @@ func (clients *clientsContainer) check(c *Client) error { return nil } -// Add a new client object -// Return true: success; false: client exists. -func (clients *clientsContainer) Add(c Client) (bool, error) { - e := clients.check(&c) - if e != nil { - return false, e +// Add adds a new client object. ok is false if such client already exists or +// if an error occurred. +func (clients *clientsContainer) Add(c *Client) (ok bool, err error) { + err = clients.check(c) + if err != nil { + return false, err } clients.lock.Lock() defer clients.lock.Unlock() // check Name index - _, ok := clients.list[c.Name] + _, ok = clients.list[c.Name] if ok { return false, nil } // check ID index for _, id := range c.IDs { - c2, ok := clients.idIndex[id] + var c2 *Client + c2, ok = clients.idIndex[id] if ok { - return false, fmt.Errorf("another client uses the same ID (%s): %s", id, c2.Name) + return false, fmt.Errorf("another client uses the same ID (%q): %q", id, c2.Name) } } // update Name index - clients.list[c.Name] = &c + clients.list[c.Name] = c // update ID index for _, id := range c.IDs { - clients.idIndex[id] = &c + clients.idIndex[id] = c } - log.Debug("Clients: added %q: ID:%v [%d]", c.Name, c.IDs, len(clients.list)) + log.Debug("clients: added %q: ID:%q [%d]", c.Name, c.IDs, len(clients.list)) + return true, nil } -// Del removes a client -func (clients *clientsContainer) Del(name string) bool { +// Del removes a client. ok is false if there is no such client. +func (clients *clientsContainer) Del(name string) (ok bool) { clients.lock.Lock() defer clients.lock.Unlock() - c, ok := clients.list[name] + var c *Client + c, ok = clients.list[name] if !ok { return false } @@ -468,25 +476,28 @@ func (clients *clientsContainer) Del(name string) bool { for _, id := range c.IDs { delete(clients.idIndex, id) } + return true } -// Return TRUE if arrays are equal -func arraysEqual(a, b []string) bool { +// equalStringSlices returns true if the slices are equal. +func equalStringSlices(a, b []string) (ok bool) { if len(a) != len(b) { return false } - for i := 0; i != len(a); i++ { + + for i := range a { if a[i] != b[i] { return false } } + return true } -// Update a client -func (clients *clientsContainer) Update(name string, c Client) error { - err := clients.check(&c) +// Update updates a client by its name. +func (clients *clientsContainer) Update(name string, c *Client) (err error) { + err = clients.check(c) if err != nil { return err } @@ -494,66 +505,69 @@ func (clients *clientsContainer) Update(name string, c Client) error { clients.lock.Lock() defer clients.lock.Unlock() - old, ok := clients.list[name] + prev, ok := clients.list[name] if !ok { - return fmt.Errorf("client not found") + return agherr.Error("client not found") } // check Name index - if old.Name != c.Name { + if prev.Name != c.Name { _, ok = clients.list[c.Name] if ok { - return fmt.Errorf("client already exists") + return agherr.Error("client already exists") } } // check IP index - if !arraysEqual(old.IDs, c.IDs) { + if !equalStringSlices(prev.IDs, c.IDs) { for _, id := range c.IDs { c2, ok := clients.idIndex[id] - if ok && c2 != old { - return fmt.Errorf("another client uses the same ID (%s): %s", id, c2.Name) + if ok && c2 != prev { + return fmt.Errorf("another client uses the same ID (%q): %q", id, c2.Name) } } // update ID index - for _, id := range old.IDs { + for _, id := range prev.IDs { delete(clients.idIndex, id) } for _, id := range c.IDs { - clients.idIndex[id] = old + clients.idIndex[id] = prev } } // update Name index - if old.Name != c.Name { - delete(clients.list, old.Name) - clients.list[c.Name] = old + if prev.Name != c.Name { + delete(clients.list, prev.Name) + clients.list[c.Name] = prev } // update upstreams cache c.upstreamConfig = nil - *old = c + *prev = *c + return nil } -// SetWhoisInfo - associate WHOIS information with a client -func (clients *clientsContainer) SetWhoisInfo(ip net.IP, info [][]string) { +// SetWhoisInfo sets the WHOIS information for a client. +// +// TODO(a.garipov): Perhaps replace [][]string with map[string]string. +func (clients *clientsContainer) SetWhoisInfo(ip string, info [][]string) { clients.lock.Lock() defer clients.lock.Unlock() - _, ok := clients.findByIP(ip) + _, ok := clients.findLocked(ip) if ok { - log.Debug("Clients: client for %s is already created, ignore WHOIS info", ip) + log.Debug("clients: client for %s is already created, ignore whois info", ip) return } - ipStr := ip.String() - ch, ok := clients.ipHost[ipStr] + ch, ok := clients.ipHost[ip] if ok { ch.WhoisInfo = info - log.Debug("Clients: set WHOIS info for auto-client %s: %v", ch.Host, ch.WhoisInfo) + log.Debug("clients: set whois info for auto-client %s: %q", ch.Host, info) + return } @@ -562,32 +576,34 @@ func (clients *clientsContainer) SetWhoisInfo(ip net.IP, info [][]string) { Source: ClientSourceWHOIS, } ch.WhoisInfo = info - clients.ipHost[ipStr] = ch - log.Debug("Clients: set WHOIS info for auto-client with IP %s: %v", ip, ch.WhoisInfo) + clients.ipHost[ip] = ch + log.Debug("clients: set whois info for auto-client with IP %s: %q", ip, info) } -// AddHost adds new IP -> Host pair -// Use priority of the source (etc/hosts > ARP > rDNS) -// so we overwrite existing entries with an equal or higher priority -func (clients *clientsContainer) AddHost(ip, host string, source clientSource) (bool, error) { +// AddHost adds a new IP-hostname pairing. The priorities of the sources is +// taken into account. ok is true if the pairing was added. +func (clients *clientsContainer) AddHost(ip, host string, src clientSource) (ok bool, err error) { clients.lock.Lock() - b := clients.addHost(ip, host, source) + ok = clients.addHostLocked(ip, host, src) clients.lock.Unlock() - return b, nil + + return ok, nil } -func (clients *clientsContainer) addHost(ip, host string, source clientSource) (addedNew bool) { - ch, ok := clients.ipHost[ip] +// addHostLocked adds a new IP-hostname pairing. For internal use only. +func (clients *clientsContainer) addHostLocked(ip, host string, src clientSource) (ok bool) { + var ch *ClientHost + ch, ok = clients.ipHost[ip] if ok { - if ch.Source > source { + if ch.Source > src { return false } - ch.Source = source + ch.Source = src } else { ch = &ClientHost{ Host: host, - Source: source, + Source: src, } clients.ipHost[ip] = ch @@ -598,11 +614,11 @@ func (clients *clientsContainer) addHost(ip, host string, source clientSource) ( return true } -// Remove all entries that match the specified source -func (clients *clientsContainer) rmHosts(source clientSource) { +// rmHostsBySrc removes all entries that match the specified source. +func (clients *clientsContainer) rmHostsBySrc(src clientSource) { n := 0 for k, v := range clients.ipHost { - if v.Source == source { + if v.Source == src { delete(clients.ipHost, k) n++ } @@ -611,19 +627,20 @@ func (clients *clientsContainer) rmHosts(source clientSource) { log.Debug("clients: removed %d client aliases", n) } -// addFromHostsFile fills the clients hosts list from the system's hosts files. +// addFromHostsFile fills the client-hostname pairing index from the system's +// hosts files. func (clients *clientsContainer) addFromHostsFile() { hosts := clients.autoHosts.List() clients.lock.Lock() defer clients.lock.Unlock() - clients.rmHosts(ClientSourceHostsFile) + clients.rmHostsBySrc(ClientSourceHostsFile) n := 0 for ip, names := range hosts { for _, name := range names { - ok := clients.addHost(ip, name, ClientSourceHostsFile) + ok := clients.addHostLocked(ip, name, ClientSourceHostsFile) if ok { n++ } @@ -633,31 +650,31 @@ func (clients *clientsContainer) addFromHostsFile() { log.Debug("Clients: added %d client aliases from system hosts-file", n) } -// Add IP -> Host pairs from the system's `arp -a` command output -// The command's output is: -// HOST (IP) at MAC on IFACE +// addFromSystemARP adds the IP-hostname pairings from the output of the arp -a +// command. func (clients *clientsContainer) addFromSystemARP() { if runtime.GOOS == "windows" { return } cmd := exec.Command("arp", "-a") - log.Tracef("executing %s %v", cmd.Path, cmd.Args) + log.Tracef("executing %q %q", cmd.Path, cmd.Args) data, err := cmd.Output() if err != nil || cmd.ProcessState.ExitCode() != 0 { - log.Debug("command %s has failed: %v code:%d", + log.Debug("command %q has failed: %q code:%d", cmd.Path, err, cmd.ProcessState.ExitCode()) return } clients.lock.Lock() defer clients.lock.Unlock() - clients.rmHosts(ClientSourceARP) + + clients.rmHostsBySrc(ClientSourceARP) n := 0 + // TODO(a.garipov): Rewrite to use bufio.Scanner. lines := strings.Split(string(data), "\n") for _, ln := range lines { - open := strings.Index(ln, " (") close := strings.Index(ln, ") ") if open == -1 || close == -1 || open >= close { @@ -670,16 +687,17 @@ func (clients *clientsContainer) addFromSystemARP() { continue } - ok := clients.addHost(ip, host, ClientSourceARP) + ok := clients.addHostLocked(ip, host, ClientSourceARP) if ok { n++ } } - log.Debug("Clients: added %d client aliases from 'arp -a' command output", n) + log.Debug("clients: added %d client aliases from 'arp -a' command output", n) } -// Add clients from DHCP that have non-empty Hostname property +// addFromDHCP adds the clients that have a non-empty hostname from the DHCP +// server. func (clients *clientsContainer) addFromDHCP() { if clients.dhcpServer == nil { return @@ -688,18 +706,20 @@ func (clients *clientsContainer) addFromDHCP() { clients.lock.Lock() defer clients.lock.Unlock() - clients.rmHosts(ClientSourceDHCP) + clients.rmHostsBySrc(ClientSourceDHCP) leases := clients.dhcpServer.Leases(dhcpd.LeasesAll) n := 0 for _, l := range leases { - if len(l.Hostname) == 0 { + if l.Hostname == "" { continue } - ok := clients.addHost(l.IP.String(), l.Hostname, ClientSourceDHCP) + + ok := clients.addHostLocked(l.IP.String(), l.Hostname, ClientSourceDHCP) if ok { n++ } } - log.Debug("Clients: added %d client aliases from DHCP", n) + + log.Debug("clients: added %d client aliases from dhcp", n) } diff --git a/internal/home/clients_test.go b/internal/home/clients_test.go index 94ff8009..a098bf4c 100644 --- a/internal/home/clients_test.go +++ b/internal/home/clients_test.go @@ -18,65 +18,65 @@ func TestClients(t *testing.T) { clients.Init(nil, nil, nil) t.Run("add_success", func(t *testing.T) { - c := Client{ + c := &Client{ IDs: []string{"1.1.1.1", "1:2:3::4", "aa:aa:aa:aa:aa:aa"}, Name: "client1", } - b, err := clients.Add(c) - assert.True(t, b) + ok, err := clients.Add(c) + assert.True(t, ok) assert.Nil(t, err) - c = Client{ + c = &Client{ IDs: []string{"2.2.2.2"}, Name: "client2", } - b, err = clients.Add(c) - assert.True(t, b) + ok, err = clients.Add(c) + assert.True(t, ok) assert.Nil(t, err) - c, b = clients.Find(net.IPv4(1, 1, 1, 1)) - assert.True(t, b) - assert.Equal(t, c.Name, "client1") + c, ok = clients.Find("1.1.1.1") + assert.True(t, ok) + assert.Equal(t, "client1", c.Name) - c, b = clients.Find(net.ParseIP("1:2:3::4")) - assert.True(t, b) - assert.Equal(t, c.Name, "client1") + c, ok = clients.Find("1:2:3::4") + assert.True(t, ok) + assert.Equal(t, "client1", c.Name) - c, b = clients.Find(net.IPv4(2, 2, 2, 2)) - assert.True(t, b) - assert.Equal(t, c.Name, "client2") + c, ok = clients.Find("2.2.2.2") + assert.True(t, ok) + assert.Equal(t, "client2", c.Name) - assert.False(t, clients.Exists(net.IPv4(1, 2, 3, 4), ClientSourceHostsFile)) - assert.True(t, clients.Exists(net.IPv4(1, 1, 1, 1), ClientSourceHostsFile)) - assert.True(t, clients.Exists(net.IPv4(2, 2, 2, 2), ClientSourceHostsFile)) + assert.True(t, !clients.Exists("1.2.3.4", ClientSourceHostsFile)) + assert.True(t, clients.Exists("1.1.1.1", ClientSourceHostsFile)) + assert.True(t, clients.Exists("2.2.2.2", ClientSourceHostsFile)) }) t.Run("add_fail_name", func(t *testing.T) { - c := Client{ + c := &Client{ IDs: []string{"1.2.3.5"}, Name: "client1", } - b, err := clients.Add(c) - assert.False(t, b) + ok, err := clients.Add(c) + assert.False(t, ok) assert.Nil(t, err) }) t.Run("add_fail_ip", func(t *testing.T) { - c := Client{ + c := &Client{ IDs: []string{"2.2.2.2"}, Name: "client3", } - b, err := clients.Add(c) - assert.False(t, b) + ok, err := clients.Add(c) + assert.False(t, ok) assert.NotNil(t, err) }) t.Run("update_fail_name", func(t *testing.T) { - c := Client{ + c := &Client{ IDs: []string{"1.2.3.0"}, Name: "client3", } @@ -84,7 +84,7 @@ func TestClients(t *testing.T) { err := clients.Update("client3", c) assert.NotNil(t, err) - c = Client{ + c = &Client{ IDs: []string{"1.2.3.0"}, Name: "client2", } @@ -94,7 +94,7 @@ func TestClients(t *testing.T) { }) t.Run("update_fail_ip", func(t *testing.T) { - c := Client{ + c := &Client{ IDs: []string{"2.2.2.2"}, Name: "client1", } @@ -104,7 +104,7 @@ func TestClients(t *testing.T) { }) t.Run("update_success", func(t *testing.T) { - c := Client{ + c := &Client{ IDs: []string{"1.1.1.2"}, Name: "client1", } @@ -112,10 +112,10 @@ func TestClients(t *testing.T) { err := clients.Update("client1", c) assert.Nil(t, err) - assert.False(t, clients.Exists(net.IPv4(1, 1, 1, 1), ClientSourceHostsFile)) - assert.True(t, clients.Exists(net.IPv4(1, 1, 1, 2), ClientSourceHostsFile)) + assert.True(t, !clients.Exists("1.1.1.1", ClientSourceHostsFile)) + assert.True(t, clients.Exists("1.1.1.2", ClientSourceHostsFile)) - c = Client{ + c = &Client{ IDs: []string{"1.1.1.2"}, Name: "client1-renamed", UseOwnSettings: true, @@ -124,77 +124,89 @@ func TestClients(t *testing.T) { err = clients.Update("client1", c) assert.Nil(t, err) - c, b := clients.Find(net.IPv4(1, 1, 1, 2)) - assert.True(t, b) + c, ok := clients.Find("1.1.1.2") + assert.True(t, ok) assert.Equal(t, "client1-renamed", c.Name) - assert.Equal(t, "1.1.1.2", c.IDs[0]) assert.True(t, c.UseOwnSettings) assert.Nil(t, clients.list["client1"]) + if assert.Len(t, c.IDs, 1) { + assert.Equal(t, "1.1.1.2", c.IDs[0]) + } }) t.Run("del_success", func(t *testing.T) { - b := clients.Del("client1-renamed") - assert.True(t, b) - assert.False(t, clients.Exists(net.IPv4(1, 1, 1, 2), ClientSourceHostsFile)) + ok := clients.Del("client1-renamed") + assert.True(t, ok) + assert.False(t, clients.Exists("1.1.1.2", ClientSourceHostsFile)) }) t.Run("del_fail", func(t *testing.T) { - b := clients.Del("client3") - assert.False(t, b) + ok := clients.Del("client3") + assert.False(t, ok) }) t.Run("addhost_success", func(t *testing.T) { - b, err := clients.AddHost("1.1.1.1", "host", ClientSourceARP) - assert.True(t, b) + ok, err := clients.AddHost("1.1.1.1", "host", ClientSourceARP) + assert.True(t, ok) assert.Nil(t, err) - b, err = clients.AddHost("1.1.1.1", "host2", ClientSourceARP) - assert.True(t, b) + ok, err = clients.AddHost("1.1.1.1", "host2", ClientSourceARP) + assert.True(t, ok) assert.Nil(t, err) - b, err = clients.AddHost("1.1.1.1", "host3", ClientSourceHostsFile) - assert.True(t, b) + ok, err = clients.AddHost("1.1.1.1", "host3", ClientSourceHostsFile) + assert.True(t, ok) assert.Nil(t, err) - assert.True(t, clients.Exists(net.IPv4(1, 1, 1, 1), ClientSourceHostsFile)) + assert.True(t, clients.Exists("1.1.1.1", ClientSourceHostsFile)) }) t.Run("addhost_fail", func(t *testing.T) { - b, err := clients.AddHost("1.1.1.1", "host1", ClientSourceRDNS) - assert.False(t, b) + ok, err := clients.AddHost("1.1.1.1", "host1", ClientSourceRDNS) + assert.False(t, ok) assert.Nil(t, err) }) } func TestClientsWhois(t *testing.T) { - var c Client + var c *Client clients := clientsContainer{} clients.testing = true clients.Init(nil, nil, nil) whois := [][]string{{"orgname", "orgname-val"}, {"country", "country-val"}} // set whois info on new client - clients.SetWhoisInfo(net.IPv4(1, 1, 1, 255), whois) - assert.Equal(t, "orgname-val", clients.ipHost["1.1.1.255"].WhoisInfo[0][1]) + clients.SetWhoisInfo("1.1.1.255", whois) + if assert.NotNil(t, clients.ipHost["1.1.1.255"]) { + h := clients.ipHost["1.1.1.255"] + if assert.Len(t, h.WhoisInfo, 2) && assert.Len(t, h.WhoisInfo[0], 2) { + assert.Equal(t, "orgname-val", h.WhoisInfo[0][1]) + } + } // set whois info on existing auto-client _, _ = clients.AddHost("1.1.1.1", "host", ClientSourceRDNS) - clients.SetWhoisInfo(net.IPv4(1, 1, 1, 1), whois) - assert.Equal(t, "orgname-val", clients.ipHost["1.1.1.1"].WhoisInfo[0][1]) + clients.SetWhoisInfo("1.1.1.1", whois) + if assert.NotNil(t, clients.ipHost["1.1.1.1"]) { + h := clients.ipHost["1.1.1.1"] + if assert.Len(t, h.WhoisInfo, 2) && assert.Len(t, h.WhoisInfo[0], 2) { + assert.Equal(t, "orgname-val", h.WhoisInfo[0][1]) + } + } // Check that we cannot set whois info on a manually-added client - c = Client{ + c = &Client{ IDs: []string{"1.1.1.2"}, Name: "client1", } _, _ = clients.Add(c) - clients.SetWhoisInfo(net.IPv4(1, 1, 1, 2), whois) + clients.SetWhoisInfo("1.1.1.2", whois) assert.Nil(t, clients.ipHost["1.1.1.2"]) _ = clients.Del("client1") } func TestClientsAddExisting(t *testing.T) { - var c Client + var c *Client clients := clientsContainer{} clients.testing = true clients.Init(nil, nil, nil) @@ -204,7 +216,7 @@ func TestClientsAddExisting(t *testing.T) { testIP := "1.2.3.4" // add a client - c = Client{ + c = &Client{ IDs: []string{"1.1.1.1", "1:2:3::4", "aa:aa:aa:aa:aa:aa", "2.2.2.0/24"}, Name: "client1", } @@ -233,7 +245,7 @@ func TestClientsAddExisting(t *testing.T) { assert.Nil(t, err) // add a new client with the same IP as for a client with MAC - c = Client{ + c = &Client{ IDs: []string{testIP}, Name: "client2", } @@ -242,7 +254,7 @@ func TestClientsAddExisting(t *testing.T) { assert.Nil(t, err) // add a new client with the IP from the client1's IP range - c = Client{ + c = &Client{ IDs: []string{"2.2.2.2"}, Name: "client3", } @@ -258,7 +270,7 @@ func TestClientsCustomUpstream(t *testing.T) { clients.Init(nil, nil, nil) // add client with upstreams - client := Client{ + c := &Client{ IDs: []string{"1.1.1.1", "1:2:3::4", "aa:aa:aa:aa:aa:aa"}, Name: "client1", Upstreams: []string{ @@ -266,7 +278,7 @@ func TestClientsCustomUpstream(t *testing.T) { "[/example.org/]8.8.8.8", }, } - ok, err := clients.Add(client) + ok, err := clients.Add(c) assert.Nil(t, err) assert.True(t, ok) @@ -275,6 +287,6 @@ func TestClientsCustomUpstream(t *testing.T) { config = clients.FindUpstreams("1.1.1.1") assert.NotNil(t, config) - assert.Len(t, config.Upstreams, 1) - assert.Len(t, config.DomainReservedUpstreams, 1) + assert.Equal(t, 1, len(config.Upstreams)) + assert.Equal(t, 1, len(config.DomainReservedUpstreams)) } diff --git a/internal/home/clientshttp.go b/internal/home/clientshttp.go index e39f4767..42d7fa20 100644 --- a/internal/home/clientshttp.go +++ b/internal/home/clientshttp.go @@ -158,7 +158,7 @@ func (clients *clientsContainer) handleAddClient(w http.ResponseWriter, r *http. } c := jsonToClient(cj) - ok, err := clients.Add(*c) + ok, err := clients.Add(c) if err != nil { httpError(w, http.StatusBadRequest, "%s", err) return @@ -216,7 +216,7 @@ func (clients *clientsContainer) handleUpdateClient(w http.ResponseWriter, r *ht } c := jsonToClient(dj.Data) - err = clients.Update(dj.Name, *c) + err = clients.Update(dj.Name, c) if err != nil { httpError(w, http.StatusBadRequest, "%s", err) return @@ -229,28 +229,28 @@ func (clients *clientsContainer) handleUpdateClient(w http.ResponseWriter, r *ht func (clients *clientsContainer) handleFindClient(w http.ResponseWriter, r *http.Request) { q := r.URL.Query() data := []map[string]clientJSON{} - for i := 0; ; i++ { - ipStr := q.Get(fmt.Sprintf("ip%d", i)) - ip := net.ParseIP(ipStr) - if ip == nil { + for i := 0; i < len(q); i++ { + idStr := q.Get(fmt.Sprintf("ip%d", i)) + if idStr == "" { break } - c, ok := clients.Find(ip) + ip := net.ParseIP(idStr) + c, ok := clients.Find(idStr) var cj clientJSON if !ok { var found bool - cj, found = clients.findTemporary(ip) + cj, found = clients.findTemporary(ip, idStr) if !found { continue } } else { - cj = clientToJSON(&c) + cj = clientToJSON(c) cj.Disallowed, cj.DisallowedRule = clients.dnsServer.IsBlockedIP(ip) } data = append(data, map[string]clientJSON{ - ipStr: cj, + idStr: cj, }) } @@ -263,10 +263,9 @@ func (clients *clientsContainer) handleFindClient(w http.ResponseWriter, r *http // findTemporary looks up the IP in temporary storages, like autohosts or // blocklists. -func (clients *clientsContainer) findTemporary(ip net.IP) (cj clientJSON, found bool) { - ipStr := ip.String() - ch, ok := clients.FindAutoClient(ip) - if !ok { +func (clients *clientsContainer) findTemporary(ip net.IP, idStr string) (cj clientJSON, found bool) { + ch, ok := clients.FindAutoClient(idStr) + if !ok && ip != nil { // It is still possible that the IP used to be in the runtime // clients list, but then the server was reloaded. So, check // the DNS server's blocked IP list. @@ -278,7 +277,7 @@ func (clients *clientsContainer) findTemporary(ip net.IP) (cj clientJSON, found } cj = clientJSON{ - IDs: []string{ipStr}, + IDs: []string{idStr}, Disallowed: disallowed, DisallowedRule: rule, } @@ -286,8 +285,10 @@ func (clients *clientsContainer) findTemporary(ip net.IP) (cj clientJSON, found return cj, true } - cj = clientHostToJSON(ipStr, ch) - cj.Disallowed, cj.DisallowedRule = clients.dnsServer.IsBlockedIP(ip) + cj = clientHostToJSON(idStr, ch) + if ip != nil { + cj.Disallowed, cj.DisallowedRule = clients.dnsServer.IsBlockedIP(ip) + } return cj, true } diff --git a/internal/home/config.go b/internal/home/config.go index afe96812..65a9401c 100644 --- a/internal/home/config.go +++ b/internal/home/config.go @@ -1,6 +1,7 @@ package home import ( + "errors" "io/ioutil" "net" "os" @@ -188,7 +189,7 @@ func initConfig() { func (c *configuration) getConfigFilename() string { configFile, err := filepath.EvalSymlinks(Context.configFilename) if err != nil { - if !os.IsNotExist(err) { + if !errors.Is(err, os.ErrNotExist) { log.Error("unexpected error while config file path evaluation: %s", err) } configFile = Context.configFilename diff --git a/internal/home/dns.go b/internal/home/dns.go index 82b844a4..3640fd03 100644 --- a/internal/home/dns.go +++ b/internal/home/dns.go @@ -3,8 +3,10 @@ package home import ( "fmt" "net" + "net/url" "os" "path/filepath" + "strconv" "github.com/AdguardTeam/AdGuardHome/internal/agherr" "github.com/AdguardTeam/AdGuardHome/internal/dnsfilter" @@ -58,7 +60,7 @@ func initDNSServer() error { if config.DNS.BindHost.IsUnspecified() { bindhost = net.IPv4(127, 0, 0, 1) } - filterConf.ResolverAddress = fmt.Sprintf("%s:%d", bindhost, config.DNS.Port) + filterConf.ResolverAddress = net.JoinHostPort(bindhost.String(), strconv.Itoa(config.DNS.Port)) filterConf.AutoHosts = &Context.autoHosts filterConf.ConfigModified = onConfigModified filterConf.HTTPRegister = httpRegister @@ -126,6 +128,7 @@ func generateServerConfig() (newconfig dnsforward.ServerConfig, err error) { Context.tls.WriteDiskConfig(&tlsConf) if tlsConf.Enabled { newconfig.TLSConfig = tlsConf.TLSConfig + newconfig.TLSConfig.ServerName = tlsConf.ServerName if tlsConf.PortDNSOverTLS != 0 { newconfig.TLSListenAddr = &net.TCPAddr{ @@ -207,36 +210,42 @@ type dnsEncryption struct { quic string } -func getDNSEncryption() dnsEncryption { - dnsEncryption := dnsEncryption{} - +func getDNSEncryption() (de dnsEncryption) { tlsConf := tlsConfigSettings{} Context.tls.WriteDiskConfig(&tlsConf) if tlsConf.Enabled && len(tlsConf.ServerName) != 0 { - + hostname := tlsConf.ServerName if tlsConf.PortHTTPS != 0 { - addr := tlsConf.ServerName + addr := hostname if tlsConf.PortHTTPS != 443 { - addr = fmt.Sprintf("%s:%d", addr, tlsConf.PortHTTPS) + addr = net.JoinHostPort(addr, strconv.Itoa(tlsConf.PortHTTPS)) } - addr = fmt.Sprintf("https://%s/dns-query", addr) - dnsEncryption.https = addr + + de.https = (&url.URL{ + Scheme: "https", + Host: addr, + Path: "/dns-query", + }).String() } if tlsConf.PortDNSOverTLS != 0 { - addr := fmt.Sprintf("tls://%s:%d", tlsConf.ServerName, tlsConf.PortDNSOverTLS) - dnsEncryption.tls = addr + de.tls = (&url.URL{ + Scheme: "tls", + Host: net.JoinHostPort(hostname, strconv.Itoa(tlsConf.PortDNSOverTLS)), + }).String() } if tlsConf.PortDNSOverQUIC != 0 { - addr := fmt.Sprintf("quic://%s:%d", tlsConf.ServerName, tlsConf.PortDNSOverQUIC) - dnsEncryption.quic = addr + de.quic = (&url.URL{ + Scheme: "quic", + Host: net.JoinHostPort(hostname, strconv.Itoa(int(tlsConf.PortDNSOverQUIC))), + }).String() } } - return dnsEncryption + return de } // Get the list of DNS addresses the server is listening on @@ -273,21 +282,26 @@ func getDNSAddresses() []string { return dnsAddresses } -// If a client has his own settings, apply them -func applyAdditionalFiltering(clientAddr net.IP, setts *dnsfilter.RequestFilteringSettings) { +// applyAdditionalFiltering adds additional client information and settings if +// the client has them. +func applyAdditionalFiltering(clientAddr net.IP, clientID string, setts *dnsfilter.RequestFilteringSettings) { Context.dnsFilter.ApplyBlockedServices(setts, nil, true) if clientAddr == nil { return } + setts.ClientIP = clientAddr - c, ok := Context.clients.Find(clientAddr) + c, ok := Context.clients.Find(clientID) if !ok { - return + c, ok = Context.clients.Find(clientAddr.String()) + if !ok { + return + } } - log.Debug("Using settings for client %s with IP %s", c.Name, clientAddr) + log.Debug("using settings for client %s with ip %s and id %q", c.Name, clientAddr, clientID) if c.UseOwnBlockedServices { Context.dnsFilter.ApplyBlockedServices(setts, c.BlockedServices, false) diff --git a/internal/home/home.go b/internal/home/home.go index 3c068912..1b6312c8 100644 --- a/internal/home/home.go +++ b/internal/home/home.go @@ -5,6 +5,7 @@ import ( "context" "crypto/tls" "crypto/x509" + "errors" "fmt" "io/ioutil" "net" @@ -434,6 +435,10 @@ func initWorkingDir(args options) { } else { Context.workDir = filepath.Dir(execPath) } + + if workDir, err := filepath.EvalSymlinks(Context.workDir); err == nil { + Context.workDir = workDir + } } // configureLogger configures logger level and output @@ -624,7 +629,7 @@ func detectFirstRun() bool { configfile = filepath.Join(Context.workDir, Context.configFilename) } _, err := os.Stat(configfile) - return os.IsNotExist(err) + return errors.Is(err, os.ErrNotExist) } // Connect to a remote server resolving hostname using our own DNS server diff --git a/internal/home/mobileconfig.go b/internal/home/mobileconfig.go index 2f06329e..3953e2e6 100644 --- a/internal/home/mobileconfig.go +++ b/internal/home/mobileconfig.go @@ -4,7 +4,10 @@ import ( "encoding/json" "fmt" "net/http" + "net/url" + "path" + "github.com/AdguardTeam/AdGuardHome/internal/dnsforward" "github.com/AdguardTeam/golibs/log" uuid "github.com/satori/go.uuid" "howett.net/plist" @@ -14,6 +17,7 @@ type dnsSettings struct { DNSProtocol string ServerURL string `plist:",omitempty"` ServerName string `plist:",omitempty"` + clientID string } type payloadContent struct { @@ -23,19 +27,19 @@ type payloadContent struct { PayloadIdentifier string PayloadType string PayloadUUID string - PayloadVersion int DNSSettings dnsSettings + PayloadVersion int } type mobileConfig struct { - PayloadContent []payloadContent PayloadDescription string PayloadDisplayName string PayloadIdentifier string - PayloadRemovalDisallowed bool PayloadType string PayloadUUID string + PayloadContent []payloadContent PayloadVersion int + PayloadRemovalDisallowed bool } func genUUIDv4() string { @@ -48,22 +52,35 @@ const ( ) func getMobileConfig(d dnsSettings) ([]byte, error) { - var name string + var dspName string switch d.DNSProtocol { case dnsProtoHTTPS: - name = fmt.Sprintf("%s DoH", d.ServerName) - d.ServerURL = fmt.Sprintf("https://%s/dns-query", d.ServerName) + dspName = fmt.Sprintf("%s DoH", d.ServerName) + + u := &url.URL{ + Scheme: "https", + Host: d.ServerName, + Path: "/dns-query", + } + if d.clientID != "" { + u.Path = path.Join(u.Path, d.clientID) + } + + d.ServerURL = u.String() case dnsProtoTLS: - name = fmt.Sprintf("%s DoT", d.ServerName) + dspName = fmt.Sprintf("%s DoT", d.ServerName) + if d.clientID != "" { + d.ServerName = d.clientID + "." + d.ServerName + } default: return nil, fmt.Errorf("bad dns protocol %q", d.DNSProtocol) } data := mobileConfig{ PayloadContent: []payloadContent{{ - Name: name, + Name: dspName, PayloadDescription: "Configures device to use AdGuard Home", - PayloadDisplayName: name, + PayloadDisplayName: dspName, PayloadIdentifier: fmt.Sprintf("com.apple.dnsSettings.managed.%s", genUUIDv4()), PayloadType: "com.apple.dnsSettings.managed", PayloadUUID: genUUIDv4(), @@ -71,7 +88,7 @@ func getMobileConfig(d dnsSettings) ([]byte, error) { DNSSettings: d, }}, PayloadDescription: "Adds AdGuard Home to Big Sur and iOS 14 or newer systems", - PayloadDisplayName: name, + PayloadDisplayName: dspName, PayloadIdentifier: genUUIDv4(), PayloadRemovalDisallowed: false, PayloadType: "Configuration", @@ -83,7 +100,10 @@ func getMobileConfig(d dnsSettings) ([]byte, error) { } func handleMobileConfig(w http.ResponseWriter, r *http.Request, dnsp string) { - host := r.URL.Query().Get("host") + var err error + + q := r.URL.Query() + host := q.Get("host") if host == "" { host = Context.tls.conf.ServerName } @@ -92,7 +112,7 @@ func handleMobileConfig(w http.ResponseWriter, r *http.Request, dnsp string) { w.WriteHeader(http.StatusInternalServerError) const msg = "no host in query parameters and no server_name" - err := json.NewEncoder(w).Encode(&jsonError{ + err = json.NewEncoder(w).Encode(&jsonError{ Message: msg, }) if err != nil { @@ -102,9 +122,25 @@ func handleMobileConfig(w http.ResponseWriter, r *http.Request, dnsp string) { return } + clientID := q.Get("client_id") + err = dnsforward.ValidateClientID(clientID) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + + err = json.NewEncoder(w).Encode(&jsonError{ + Message: err.Error(), + }) + if err != nil { + log.Debug("writing 400 json response: %s", err) + } + + return + } + d := dnsSettings{ DNSProtocol: dnsp, ServerName: host, + clientID: clientID, } mobileconfig, err := getMobileConfig(d) @@ -115,6 +151,7 @@ func handleMobileConfig(w http.ResponseWriter, r *http.Request, dnsp string) { } w.Header().Set("Content-Type", "application/xml") + _, _ = w.Write(mobileconfig) } diff --git a/internal/home/mobileconfig_test.go b/internal/home/mobileconfig_test.go index 1025fe93..9dcafc97 100644 --- a/internal/home/mobileconfig_test.go +++ b/internal/home/mobileconfig_test.go @@ -73,6 +73,27 @@ func TestHandleMobileConfigDOH(t *testing.T) { handleMobileConfigDOH(w, r) assert.Equal(t, http.StatusInternalServerError, w.Code) }) + + t.Run("client_id", func(t *testing.T) { + r, err := http.NewRequest(http.MethodGet, "https://example.com:12345/apple/doh.mobileconfig?host=example.org&client_id=cli42", nil) + assert.Nil(t, err) + + w := httptest.NewRecorder() + + handleMobileConfigDOH(w, r) + assert.Equal(t, http.StatusOK, w.Code) + + var mc mobileConfig + _, err = plist.Unmarshal(w.Body.Bytes(), &mc) + assert.Nil(t, err) + + if assert.Len(t, mc.PayloadContent, 1) { + assert.Equal(t, "example.org DoH", mc.PayloadContent[0].Name) + assert.Equal(t, "example.org DoH", mc.PayloadContent[0].PayloadDisplayName) + assert.Equal(t, "example.org", mc.PayloadContent[0].DNSSettings.ServerName) + assert.Equal(t, "https://example.org/dns-query/cli42", mc.PayloadContent[0].DNSSettings.ServerURL) + } + }) } func TestHandleMobileConfigDOT(t *testing.T) { @@ -137,4 +158,24 @@ func TestHandleMobileConfigDOT(t *testing.T) { handleMobileConfigDOT(w, r) assert.Equal(t, http.StatusInternalServerError, w.Code) }) + + t.Run("client_id", func(t *testing.T) { + r, err := http.NewRequest(http.MethodGet, "https://example.com:12345/apple/dot.mobileconfig?host=example.org&client_id=cli42", nil) + assert.Nil(t, err) + + w := httptest.NewRecorder() + + handleMobileConfigDOT(w, r) + assert.Equal(t, http.StatusOK, w.Code) + + var mc mobileConfig + _, err = plist.Unmarshal(w.Body.Bytes(), &mc) + assert.Nil(t, err) + + if assert.Len(t, mc.PayloadContent, 1) { + assert.Equal(t, "example.org DoT", mc.PayloadContent[0].Name) + assert.Equal(t, "example.org DoT", mc.PayloadContent[0].PayloadDisplayName) + assert.Equal(t, "cli42.example.org", mc.PayloadContent[0].DNSSettings.ServerName) + } + }) } diff --git a/internal/home/rdns.go b/internal/home/rdns.go index c71f3822..dad75e44 100644 --- a/internal/home/rdns.go +++ b/internal/home/rdns.go @@ -57,7 +57,8 @@ func (r *RDNS) Begin(ip net.IP) { binary.BigEndian.PutUint64(expire, now+ttl) _ = r.ipAddrs.Set(ip, expire) - if r.clients.Exists(ip, ClientSourceRDNS) { + id := ip.String() + if r.clients.Exists(id, ClientSourceRDNS) { return } diff --git a/internal/home/whois.go b/internal/home/whois.go index 1d849673..26c674dc 100644 --- a/internal/home/whois.go +++ b/internal/home/whois.go @@ -11,7 +11,6 @@ import ( "github.com/AdguardTeam/AdGuardHome/internal/aghio" "github.com/AdguardTeam/AdGuardHome/internal/util" - "github.com/AdguardTeam/golibs/cache" "github.com/AdguardTeam/golibs/log" ) @@ -25,14 +24,16 @@ const ( // Whois - module context type Whois struct { - clients *clientsContainer - ipChan chan net.IP - timeoutMsec uint + clients *clientsContainer + ipChan chan net.IP // Contains IP addresses of clients // An active IP address is resolved once again after it expires. // If IP address couldn't be resolved, it stays here for some time to prevent further attempts to resolve the same IP. ipAddrs cache.Cache + + // TODO(a.garipov): Rewrite to use time.Duration. Like, seriously, why? + timeoutMsec uint } // initWhois creates the Whois module context. @@ -244,6 +245,7 @@ func (w *Whois) workerLoop() { continue } - w.clients.SetWhoisInfo(ip, info) + id := ip.String() + w.clients.SetWhoisInfo(id, info) } } diff --git a/internal/querylog/decode.go b/internal/querylog/decode.go index ad0948ba..3e9a5f33 100644 --- a/internal/querylog/decode.go +++ b/internal/querylog/decode.go @@ -17,6 +17,16 @@ import ( type logEntryHandler (func(t json.Token, ent *logEntry) error) var logEntryHandlers = map[string]logEntryHandler{ + "CID": func(t json.Token, ent *logEntry) error { + v, ok := t.(string) + if !ok { + return nil + } + + ent.ClientID = v + + return nil + }, "IP": func(t json.Token, ent *logEntry) error { v, ok := t.(string) if !ok { diff --git a/internal/querylog/decode_test.go b/internal/querylog/decode_test.go index 40052fea..58ca5851 100644 --- a/internal/querylog/decode_test.go +++ b/internal/querylog/decode_test.go @@ -25,6 +25,7 @@ func TestDecodeLogEntry(t *testing.T) { t.Run("success", func(t *testing.T) { const ansStr = `Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==` const data = `{"IP":"127.0.0.1",` + + `"CID":"cli42",` + `"T":"2020-11-25T18:55:56.519796+03:00",` + `"QH":"an.yandex.ru",` + `"QT":"A",` + @@ -52,6 +53,7 @@ func TestDecodeLogEntry(t *testing.T) { QHost: "an.yandex.ru", QType: "A", QClass: "IN", + ClientID: "cli42", ClientProto: "", Answer: ans, Result: dnsfilter.Result{ diff --git a/internal/querylog/json.go b/internal/querylog/json.go index 152f6ce2..7bfd98c4 100644 --- a/internal/querylog/json.go +++ b/internal/querylog/json.go @@ -79,6 +79,10 @@ func (l *queryLog) logEntryToJSONEntry(entry *logEntry) (jsonEntry jobject) { }, } + if entry.ClientID != "" { + jsonEntry["client_id"] = entry.ClientID + } + if msg != nil { jsonEntry["status"] = dns.RcodeToString[msg.Rcode] diff --git a/internal/querylog/qlog.go b/internal/querylog/qlog.go index 30054a92..41ce9823 100644 --- a/internal/querylog/qlog.go +++ b/internal/querylog/qlog.go @@ -2,6 +2,7 @@ package querylog import ( + "errors" "fmt" "net" "os" @@ -37,10 +38,11 @@ type ClientProto string // Client protocol names. const ( - ClientProtoDOH ClientProto = "doh" - ClientProtoDOQ ClientProto = "doq" - ClientProtoDOT ClientProto = "dot" - ClientProtoPlain ClientProto = "" + ClientProtoDOH ClientProto = "doh" + ClientProtoDOQ ClientProto = "doq" + ClientProtoDOT ClientProto = "dot" + ClientProtoDNSCrypt ClientProto = "dnscrypt" + ClientProtoPlain ClientProto = "" ) // NewClientProto validates that the client protocol name is valid and returns @@ -68,6 +70,7 @@ type logEntry struct { QType string `json:"QT"` QClass string `json:"QC"` + ClientID string `json:"CID,omitempty"` ClientProto ClientProto `json:"CP"` Answer []byte `json:",omitempty"` // sometimes empty answers happen like binerdunt.top or rev2.globalrootservers.net @@ -119,14 +122,15 @@ func (l *queryLog) clear() { l.flushPending = false l.bufferLock.Unlock() - err := os.Remove(l.logFile + ".1") - if err != nil && !os.IsNotExist(err) { - log.Error("file remove: %s: %s", l.logFile+".1", err) + oldLogFile := l.logFile + ".1" + err := os.Remove(oldLogFile) + if err != nil && !errors.Is(err, os.ErrNotExist) { + log.Error("removing old log file %q: %s", oldLogFile, err) } err = os.Remove(l.logFile) - if err != nil && !os.IsNotExist(err) { - log.Error("file remove: %s: %s", l.logFile, err) + if err != nil && !errors.Is(err, os.ErrNotExist) { + log.Error("removing log file %q: %s", l.logFile, err) } log.Debug("Query log: cleared") @@ -154,6 +158,7 @@ func (l *queryLog) Add(params AddParams) { Result: *params.Result, Elapsed: params.Elapsed, Upstream: params.Upstream, + ClientID: params.ClientID, ClientProto: params.ClientProto, } q := params.Question.Question[0] diff --git a/internal/querylog/qlogfile.go b/internal/querylog/qlogfile.go index 69a42ed2..3aa56f6f 100644 --- a/internal/querylog/qlogfile.go +++ b/internal/querylog/qlogfile.go @@ -251,7 +251,7 @@ func (q *QLogFile) readNextLine(position int64) (string, int64, error) { // the goal is to read a chunk of file that includes the line with the specified position. func (q *QLogFile) initBuffer(position int64) error { q.bufferStart = int64(0) - if (position - bufferSize) > 0 { + if position > bufferSize { q.bufferStart = position - bufferSize } @@ -264,12 +264,10 @@ func (q *QLogFile) initBuffer(position int64) error { if q.buffer == nil { q.buffer = make([]byte, bufferSize) } - q.bufferLen, err = q.file.Read(q.buffer) - if err != nil { - return err - } - return nil + q.bufferLen, err = q.file.Read(q.buffer) + + return err } // readProbeLine reads a line that includes the specified position @@ -280,7 +278,7 @@ func (q *QLogFile) readProbeLine(position int64) (string, int64, int64, error) { // In order to do this, we'll define the boundaries seekPosition := int64(0) relativePos := position // position relative to the buffer we're going to read - if (position - maxEntrySize) > 0 { + if position > maxEntrySize { seekPosition = position - maxEntrySize relativePos = maxEntrySize } diff --git a/internal/querylog/querylog.go b/internal/querylog/querylog.go index 6a6e0a6c..98b8959d 100644 --- a/internal/querylog/querylog.go +++ b/internal/querylog/querylog.go @@ -46,6 +46,7 @@ type AddParams struct { OrigAnswer *dns.Msg // The response from an upstream server (optional) Result *dnsfilter.Result // Filtering result (optional) Elapsed time.Duration // Time spent for processing the request + ClientID string ClientIP net.IP Upstream string // Upstream server URL ClientProto ClientProto diff --git a/internal/querylog/querylogfile.go b/internal/querylog/querylogfile.go index c6d48235..a0fd165b 100644 --- a/internal/querylog/querylogfile.go +++ b/internal/querylog/querylogfile.go @@ -3,6 +3,7 @@ package querylog import ( "bytes" "encoding/json" + "errors" "os" "time" @@ -87,18 +88,19 @@ func (l *queryLog) rotate() error { from := l.logFile to := l.logFile + ".1" - if _, err := os.Stat(from); os.IsNotExist(err) { - // do nothing, file doesn't exist - return nil - } - err := os.Rename(from, to) if err != nil { + if errors.Is(err, os.ErrNotExist) { + return nil + } + log.Error("querylog: failed to rename file: %s", err) + return err } log.Debug("querylog: renamed %s -> %s", from, to) + return nil } diff --git a/internal/querylog/searchcriteria.go b/internal/querylog/searchcriteria.go index 1c2b26e3..4db39bf9 100644 --- a/internal/querylog/searchcriteria.go +++ b/internal/querylog/searchcriteria.go @@ -9,8 +9,13 @@ import ( type criteriaType int const ( - ctDomainOrClient criteriaType = iota // domain name or client IP address - ctFilteringStatus // filtering status + // ctDomainOrClient is for searching by the domain name, the client's IP + // address, or the clinet's ID. + ctDomainOrClient criteriaType = iota + // ctFilteringStatus is for searching by the filtering status. + // + // See (*searchCriteria).ctFilteringStatusCase for details. + ctFilteringStatus ) const ( @@ -38,9 +43,9 @@ var filteringStatusValues = []string{ // searchCriteria - every search request may contain a list of different search criteria // we use each of them to match the query type searchCriteria struct { + value string // search criteria value criteriaType criteriaType // type of the criteria strict bool // should we strictly match (equality) or not (indexOf) - value string // search criteria value } // quickMatch - quickly checks if the log entry matches this search criteria @@ -51,7 +56,8 @@ func (c *searchCriteria) quickMatch(line string) bool { switch c.criteriaType { case ctDomainOrClient: return c.quickMatchJSONValue(line, "QH") || - c.quickMatchJSONValue(line, "IP") + c.quickMatchJSONValue(line, "IP") || + c.quickMatchJSONValue(line, "ID") default: return true } @@ -89,13 +95,14 @@ func (c *searchCriteria) match(entry *logEntry) bool { } func (c *searchCriteria) ctDomainOrClientCase(entry *logEntry) bool { + clientID := strings.ToLower(entry.ClientID) qhost := strings.ToLower(entry.QHost) searchVal := strings.ToLower(c.value) - if c.strict && qhost == searchVal { + if c.strict && (qhost == searchVal || clientID == searchVal) { return true } - if !c.strict && strings.Contains(qhost, searchVal) { + if !c.strict && (strings.Contains(qhost, searchVal) || strings.Contains(clientID, searchVal)) { return true } diff --git a/internal/stats/stats.go b/internal/stats/stats.go index 1addbebd..7ed1d320 100644 --- a/internal/stats/stats.go +++ b/internal/stats/stats.go @@ -76,10 +76,14 @@ const ( rLast ) -// Entry - data to add +// Entry is a statistics data entry. type Entry struct { + // Clients is the client's primary ID. + // + // TODO(a.garipov): Make this a {net.IP, string} enum? + Client string + Domain string - Client net.IP Result Result Time uint32 // processing time (msec) } diff --git a/internal/stats/stats_test.go b/internal/stats/stats_test.go index b4be4db0..c4fbe191 100644 --- a/internal/stats/stats_test.go +++ b/internal/stats/stats_test.go @@ -39,13 +39,13 @@ func TestStats(t *testing.T) { e := Entry{} e.Domain = "domain" - e.Client = net.IP{127, 0, 0, 1} + e.Client = "127.0.0.1" e.Result = RFiltered e.Time = 123456 s.Update(e) e.Domain = "domain" - e.Client = net.IP{127, 0, 0, 1} + e.Client = "127.0.0.1" e.Result = RNotFiltered e.Time = 123456 s.Update(e) @@ -113,9 +113,10 @@ func TestLargeNumbers(t *testing.T) { } for i := 0; i != n; i++ { e.Domain = fmt.Sprintf("domain%d", i) - e.Client = net.IP{127, 0, 0, 1} - e.Client[2] = byte((i & 0xff00) >> 8) - e.Client[3] = byte(i & 0xff) + ip := net.IP{127, 0, 0, 1} + ip[2] = byte((i & 0xff00) >> 8) + ip[3] = byte(i & 0xff) + e.Client = ip.String() e.Result = RNotFiltered e.Time = 123456 s.Update(e) diff --git a/internal/stats/unit.go b/internal/stats/unit.go index 962fe85b..6f31cd5e 100644 --- a/internal/stats/unit.go +++ b/internal/stats/unit.go @@ -223,6 +223,7 @@ func (s *statsCtx) periodicFlush() { s.unitLock.Lock() ptr := s.unit s.unitLock.Unlock() + if ptr == nil { break } @@ -230,6 +231,7 @@ func (s *statsCtx) periodicFlush() { id := s.conf.UnitID() if ptr.id == id { time.Sleep(time.Second) + continue } @@ -243,6 +245,7 @@ func (s *statsCtx) periodicFlush() { if tx == nil { continue } + ok1 := s.flushUnitToDB(tx, u.id, udb) ok2 := s.deleteUnit(tx, id-s.conf.limit) if ok1 || ok2 { @@ -251,6 +254,7 @@ func (s *statsCtx) periodicFlush() { _ = tx.Rollback() } } + log.Tracef("periodicFlush() exited") } @@ -265,7 +269,7 @@ func (s *statsCtx) deleteUnit(tx *bolt.Tx, id uint32) bool { return true } -func convertMapToArray(m map[string]uint64, max int) []countPair { +func convertMapToSlice(m map[string]uint64, max int) []countPair { a := []countPair{} for k, v := range m { pair := countPair{} @@ -283,7 +287,7 @@ func convertMapToArray(m map[string]uint64, max int) []countPair { return a[:max] } -func convertArrayToMap(a []countPair) map[string]uint64 { +func convertSliceToMap(a []countPair) map[string]uint64 { m := map[string]uint64{} for _, it := range a { m[it.Name] = it.Count @@ -301,9 +305,9 @@ func serialize(u *unit) *unitDB { udb.TimeAvg = uint32(u.timeSum / u.nTotal) } - udb.Domains = convertMapToArray(u.domains, maxDomains) - udb.BlockedDomains = convertMapToArray(u.blockedDomains, maxDomains) - udb.Clients = convertMapToArray(u.clients, maxClients) + udb.Domains = convertMapToSlice(u.domains, maxDomains) + udb.BlockedDomains = convertMapToSlice(u.blockedDomains, maxDomains) + udb.Clients = convertMapToSlice(u.clients, maxClients) return &udb } @@ -319,9 +323,9 @@ func deserialize(u *unit, udb *unitDB) { u.nResult[i] = udb.NResult[i] } - u.domains = convertArrayToMap(udb.Domains) - u.blockedDomains = convertArrayToMap(udb.BlockedDomains) - u.clients = convertArrayToMap(udb.Clients) + u.domains = convertSliceToMap(udb.Domains) + u.blockedDomains = convertSliceToMap(udb.BlockedDomains) + u.clients = convertSliceToMap(udb.Clients) u.timeSum = uint64(udb.TimeAvg) * u.nTotal } @@ -372,7 +376,7 @@ func (s *statsCtx) loadUnitFromDB(tx *bolt.Tx, id uint32) *unitDB { return &udb } -func convertTopArray(a []countPair) []map[string]uint64 { +func convertTopSlice(a []countPair) []map[string]uint64 { m := []map[string]uint64{} for _, it := range a { ent := map[string]uint64{} @@ -461,13 +465,20 @@ func (s *statsCtx) getClientIP(ip net.IP) (clientIP net.IP) { func (s *statsCtx) Update(e Entry) { if e.Result == 0 || e.Result >= rLast || - len(e.Domain) == 0 || - !(len(e.Client) == 4 || len(e.Client) == 16) { + e.Domain == "" || + e.Client == "" { return } - client := s.getClientIP(e.Client) + + clientID := e.Client + if ip := net.ParseIP(clientID); ip != nil { + ip = s.getClientIP(ip) + clientID = ip.String() + } s.unitLock.Lock() + defer s.unitLock.Unlock() + u := s.unit u.nResult[e.Result]++ @@ -478,10 +489,9 @@ func (s *statsCtx) Update(e Entry) { u.blockedDomains[e.Domain]++ } - u.clients[client.String()]++ + u.clients[clientID]++ u.timeSum += uint64(e.Time) u.nTotal++ - s.unitLock.Unlock() } func (s *statsCtx) loadUnits(limit uint32) ([]*unitDB, uint32) { @@ -594,8 +604,8 @@ func (s *statsCtx) getData() (statsResponse, bool) { m[it.Name] += it.Count } } - a2 := convertMapToArray(m, max) - return convertTopArray(a2) + a2 := convertMapToSlice(m, max) + return convertTopSlice(a2) } dnsQueries := statsCollector(func(u *unitDB) (num uint64) { return u.NTotal }) @@ -661,7 +671,7 @@ func (s *statsCtx) GetTopClientsIP(maxCount uint) []net.IP { m[it.Name] += it.Count } } - a := convertMapToArray(m, int(maxCount)) + a := convertMapToSlice(m, int(maxCount)) d := []net.IP{} for _, it := range a { d = append(d, net.ParseIP(it.Name)) diff --git a/openapi/CHANGELOG.md b/openapi/CHANGELOG.md index 225d2ead..fab1099e 100644 --- a/openapi/CHANGELOG.md +++ b/openapi/CHANGELOG.md @@ -4,6 +4,11 @@ ## v0.105: API changes +### New `"dnscrypt"` `"client_proto"` value in `GET /querylog` response + +* The field `"client_proto"` can now have the value `"dnscrypt"` when the + request was sent over a DNSCrypt connection. + ### New `"reason"` in `GET /filtering/check_host` and `GET /querylog` * The new `RewriteRule` reason is added to `GET /filtering/check_host` and diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index 490fd65d..366222af 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -794,11 +794,17 @@ 'tags': - 'clients' 'operationId': 'clientsFind' - 'summary': 'Get information about selected clients by their IP address' + 'summary': > + Get information about clients by their IP addresses or client IDs. 'parameters': - 'name': 'ip0' 'in': 'query' - 'description': 'Filter by IP address' + 'description': > + Filter by IP address or client IDs. Parameters with names `ip1`, + `ip2`, and so on are also accepted and interpreted as "ip0 OR ip1 OR + ip2". + + TODO(a.garipov): Replace with a better query API. 'schema': 'type': 'string' 'responses': @@ -1109,6 +1115,13 @@ 'name': 'host' 'schema': 'type': 'string' + - 'description': > + Client ID. + 'example': 'client-1' + 'in': 'query' + 'name': 'client_id' + 'schema': + 'type': 'string' 'responses': '200': 'description': 'DNS over HTTPS plist file.' @@ -1136,6 +1149,13 @@ 'name': 'host' 'schema': 'type': 'string' + - 'description': > + Client ID. + 'example': 'client-1' + 'in': 'query' + 'name': 'client_id' + 'schema': + 'type': 'string' 'responses': '200': 'description': 'DNS over TLS plist file' @@ -1781,13 +1801,21 @@ 'answer_dnssec': 'type': 'boolean' 'client': - 'type': 'string' + 'description': > + The client's IP address. 'example': '192.168.0.1' + 'type': 'string' + 'client_id': + 'description': > + The client ID, if provided in DOH, DOQ, or DOT. + 'example': 'cli123' + 'type': 'string' 'client_proto': 'enum': - 'dot' - 'doh' - 'doq' + - 'dnscrypt' - '' 'elapsedMs': 'type': 'string' @@ -2094,7 +2122,7 @@ 'type': 'string' 'Client': 'type': 'object' - 'description': 'Client information' + 'description': 'Client information.' 'properties': 'name': 'type': 'string' @@ -2102,7 +2130,7 @@ 'example': 'localhost' 'ids': 'type': 'array' - 'description': 'IP, CIDR or MAC address' + 'description': 'IP, CIDR, MAC, or client ID.' 'items': 'type': 'string' 'use_global_settings': @@ -2157,9 +2185,38 @@ 'type': 'string' 'ClientsFindResponse': 'type': 'array' - 'description': 'Response to clients find operation' + 'description': 'Client search results.' 'items': '$ref': '#/components/schemas/ClientsFindEntry' + 'example': + - 'cli42': + 'name': 'Client 42' + 'ids': ['cli42'] + 'use_global_settings': true + 'filtering_enabled': true + 'parental_enabled': true + 'safebrowsing_enabled': true + 'safesearch_enabled': true + 'use_global_blocked_services': true + 'blocked_services': null + 'upstreams': null + 'whois_info': null + 'disallowed': false + 'disallowed_rule': '' + - '1.2.3.4': + 'name': 'Client 1-2-3-4' + 'ids': ['1.2.3.4'] + 'use_global_settings': true + 'filtering_enabled': true + 'parental_enabled': true + 'safebrowsing_enabled': true + 'safesearch_enabled': true + 'use_global_blocked_services': true + 'blocked_services': null + 'upstreams': null + 'whois_info': null + 'disallowed': false + 'disallowed_rule': '' 'AccessListResponse': '$ref': '#/components/schemas/AccessList' 'AccessSetRequest': @@ -2187,10 +2244,9 @@ 'type': 'object' 'additionalProperties': '$ref': '#/components/schemas/ClientFindSubEntry' - 'example': - '1.2.3.4': 'test' 'ClientFindSubEntry': 'type': 'object' + 'description': 'Client information.' 'properties': 'name': 'type': 'string' @@ -2198,7 +2254,7 @@ 'example': 'localhost' 'ids': 'type': 'array' - 'description': 'IP, CIDR or MAC address' + 'description': 'IP, CIDR, MAC, or client ID.' 'items': 'type': 'string' 'use_global_settings': diff --git a/scripts/make/go-build.sh b/scripts/make/go-build.sh index f9702c35..f8520f21 100644 --- a/scripts/make/go-build.sh +++ b/scripts/make/go-build.sh @@ -54,7 +54,7 @@ esac # TODO(a.garipov): Additional validation? version="$VERSION" -# Set the linker flags accordingly: set the realease channel and the +# Set the linker flags accordingly: set the release channel and the # current version as well as goarm and gomips variable values, if the # variables are set and are not empty. readonly version_pkg='github.com/AdguardTeam/AdGuardHome/internal/version' From a76fb2cd523528fb4a8215bdec8cc9484a0e75d2 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Wed, 27 Jan 2021 20:45:00 +0300 Subject: [PATCH 064/141] Pull request: scrips: make go-lint show output even when command fails Merge in DNS/adguard-home from imp-go-lint to master Squashed commit of the following: commit 4ac92d95071b747e01e30713030e72396f1c353f Author: Ainar Garipov Date: Wed Jan 27 20:35:35 2021 +0300 scrips: make go-lint show output even when command fails --- scripts/make/go-lint.sh | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/scripts/make/go-lint.sh b/scripts/make/go-lint.sh index 417b6b57..391e8c51 100644 --- a/scripts/make/go-lint.sh +++ b/scripts/make/go-lint.sh @@ -60,14 +60,24 @@ underscores() { # exit_on_output exits with a nonzero exit code if there is anything in # the command's combined output. -exit_on_output() { - test "$VERBOSE" -lt '2' && set +x +exit_on_output() ( + set +e + + if [ "$VERBOSE" -lt '2' ] + then + set +x + fi cmd="$1" shift - exitcode='0' output="$("$cmd" "$@" 2>&1)" + exitcode="$?" + if [ "$exitcode" != '0' ] + then + echo "'$cmd' failed with code $exitcode" + fi + if [ "$output" != '' ] then if [ "$*" != '' ] @@ -79,13 +89,14 @@ exit_on_output() { echo "$output" - exitcode='1' + if [ "$exitcode" = '0' ] + then + exitcode='1' + fi fi - test "$VERBOSE" -gt '0' && set -x - return "$exitcode" -} +) exit_on_output blocklist_imports From 154c9c1c262eb605d91baf2f1237837d4161e865 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Thu, 28 Jan 2021 14:02:38 +0300 Subject: [PATCH 065/141] Pull request: scripts: imp client linting Merge in DNS/adguard-home from imp-hook to master Squashed commit of the following: commit f09a3ea2c0299b3ecb39b62d83b65919e425e591 Author: Ainar Garipov Date: Thu Jan 28 13:49:03 2021 +0300 scripts: imp client linting --- Makefile | 12 +++++++----- scripts/hooks/pre-commit | 7 ++++++- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 3d8625e6..b119de72 100644 --- a/Makefile +++ b/Makefile @@ -68,11 +68,13 @@ js-build: js-deps: $(NPM) $(NPM_FLAGS) ci $(YARN) $(YARN_FLAGS) install -js-lint: - $(NPM) $(NPM_FLAGS) run lint - $(YARN) $(YARN_FLAGS) lint -js-test: - $(NPM) $(NPM_FLAGS) run test + +# TODO(a.garipov): Remove the legacy client tasks support once the new +# client is done and the old one is removed. +js-lint: ; $(NPM) $(NPM_FLAGS) run lint +js-test: ; $(NPM) $(NPM_FLAGS) run test +js-beta-lint: ; $(YARN) $(YARN_FLAGS) lint +js-beta-test: ; # TODO(v.abdulmyanov): Add tests for the new client. go-build: ; $(ENV) "$(SHELL)" ./scripts/make/go-build.sh go-deps: ; $(ENV) "$(SHELL)" ./scripts/make/go-deps.sh diff --git a/scripts/hooks/pre-commit b/scripts/hooks/pre-commit index 3aa9bd27..859f536f 100755 --- a/scripts/hooks/pre-commit +++ b/scripts/hooks/pre-commit @@ -2,11 +2,16 @@ set -e -f -u -if [ "$(git diff --cached --name-only -- '*.js')" ] +if [ "$(git diff --cached --name-only -- 'client/*.js')" ] then make js-lint js-test fi +if [ "$(git diff --cached --name-only -- 'client2/*.js' 'client2/*.ts' 'client2/*.tsx')" ] +then + make js-beta-lint js-beta-test +fi + if [ "$(git diff --cached --name-only -- '*.go' 'go.mod')" ] then make go-lint go-test From 3af079a81b3aa8ed8e701b8079d486f6344b0560 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Thu, 28 Jan 2021 16:00:29 +0300 Subject: [PATCH 066/141] Pull request: querylog: make answer actually a string Merge in DNS/adguard-home from 2609-dns-answer-string to master Updates #2609. Squashed commit of the following: commit 1922f90c50f47735d8025d862fbf60bcf707140f Author: Ainar Garipov Date: Thu Jan 28 15:19:11 2021 +0300 querylog: stricter typing commit 6ef73a45d822f77cfda44d8c093f272704d5afb6 Author: Ainar Garipov Date: Thu Jan 28 15:04:06 2021 +0300 querylog: make answer actually a string --- internal/querylog/json.go | 53 ++++++++++++++++++++++++--------------- openapi/openapi.yaml | 3 ++- 2 files changed, 35 insertions(+), 21 deletions(-) diff --git a/internal/querylog/json.go b/internal/querylog/json.go index 7bfd98c4..306a6bcb 100644 --- a/internal/querylog/json.go +++ b/internal/querylog/json.go @@ -4,6 +4,7 @@ import ( "fmt" "net" "strconv" + "strings" "time" "github.com/AdguardTeam/AdGuardHome/internal/dnsfilter" @@ -139,48 +140,60 @@ func resultRulesToJSONRules(rules []*dnsfilter.ResultRule) (jsonRules []jobject) return jsonRules } -func answerToMap(a *dns.Msg) (answers []jobject) { +type dnsAnswer struct { + Type string `json:"type"` + Value string `json:"value"` + TTL uint32 `json:"ttl"` +} + +func answerToMap(a *dns.Msg) (answers []*dnsAnswer) { if a == nil || len(a.Answer) == 0 { return nil } - answers = []jobject{} + answers = make([]*dnsAnswer, 0, len(a.Answer)) for _, k := range a.Answer { header := k.Header() - answer := jobject{ - "type": dns.TypeToString[header.Rrtype], - "ttl": header.Ttl, + answer := &dnsAnswer{ + Type: dns.TypeToString[header.Rrtype], + TTL: header.Ttl, } - // try most common record types + + // Some special treatment for some well-known types. + // + // TODO(a.garipov): Consider just calling String() for everyone + // instead. switch v := k.(type) { + case nil: + // Probably unlikely, but go on. case *dns.A: - answer["value"] = v.A + answer.Value = v.A.String() case *dns.AAAA: - answer["value"] = v.AAAA + answer.Value = v.AAAA.String() case *dns.MX: - answer["value"] = fmt.Sprintf("%v %v", v.Preference, v.Mx) + answer.Value = fmt.Sprintf("%v %v", v.Preference, v.Mx) case *dns.CNAME: - answer["value"] = v.Target + answer.Value = v.Target case *dns.NS: - answer["value"] = v.Ns + answer.Value = v.Ns case *dns.SPF: - answer["value"] = v.Txt + answer.Value = strings.Join(v.Txt, "\n") case *dns.TXT: - answer["value"] = v.Txt + answer.Value = strings.Join(v.Txt, "\n") case *dns.PTR: - answer["value"] = v.Ptr + answer.Value = v.Ptr case *dns.SOA: - answer["value"] = fmt.Sprintf("%v %v %v %v %v %v %v", v.Ns, v.Mbox, v.Serial, v.Refresh, v.Retry, v.Expire, v.Minttl) + answer.Value = fmt.Sprintf("%v %v %v %v %v %v %v", v.Ns, v.Mbox, v.Serial, v.Refresh, v.Retry, v.Expire, v.Minttl) case *dns.CAA: - answer["value"] = fmt.Sprintf("%v %v \"%v\"", v.Flag, v.Tag, v.Value) + answer.Value = fmt.Sprintf("%v %v \"%v\"", v.Flag, v.Tag, v.Value) case *dns.HINFO: - answer["value"] = fmt.Sprintf("\"%v\" \"%v\"", v.Cpu, v.Os) + answer.Value = fmt.Sprintf("\"%v\" \"%v\"", v.Cpu, v.Os) case *dns.RRSIG: - answer["value"] = fmt.Sprintf("%v %v %v %v %v %v %v %v %v", dns.TypeToString[v.TypeCovered], v.Algorithm, v.Labels, v.OrigTtl, v.Expiration, v.Inception, v.KeyTag, v.SignerName, v.Signature) + answer.Value = fmt.Sprintf("%v %v %v %v %v %v %v %v %v", dns.TypeToString[v.TypeCovered], v.Algorithm, v.Labels, v.OrigTtl, v.Expiration, v.Inception, v.KeyTag, v.SignerName, v.Signature) default: - // type unknown, marshall it as-is - answer["value"] = v + answer.Value = v.String() } + answers = append(answers, answer) } diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index 366222af..8183faaa 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -1738,8 +1738,9 @@ 'description': 'DNS answer section' 'properties': 'ttl': - 'type': 'integer' 'example': 55 + 'format': 'uint32' + 'type': 'integer' 'type': 'type': 'string' 'example': 'A' From eeeb03839a14029d47685666f7869d908ffadbd2 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Thu, 28 Jan 2021 19:39:33 +0300 Subject: [PATCH 067/141] Pull request: all: fix client id handling in querylog Merge in DNS/adguard-home from 2607-querylog-client-id to master Closes #2607. Squashed commit of the following: commit 95367a82469af3b042489fb650b962b48cb73236 Author: Ainar Garipov Date: Thu Jan 28 19:02:02 2021 +0300 all: fix client, imp docs commit b652a7ef2373a75f7e897d29f5c9e36b0e076f8e Author: Ainar Garipov Date: Thu Jan 28 18:36:17 2021 +0300 all: fix client id handling in querylog --- client/src/actions/queryLogs.js | 2 +- client/src/helpers/helpers.js | 17 +++++++++++++---- internal/home/clientshttp.go | 10 ++++++---- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/client/src/actions/queryLogs.js b/client/src/actions/queryLogs.js index de8af4a3..076a9008 100644 --- a/client/src/actions/queryLogs.js +++ b/client/src/actions/queryLogs.js @@ -12,7 +12,7 @@ const enrichWithClientInfo = async (logs) => { if (Object.keys(clientsParams).length > 0) { const clients = await apiClient.findClients(clientsParams); - return addClientInfo(logs, clients, 'client'); + return addClientInfo(logs, clients, 'client_id', 'client'); } return logs; diff --git a/client/src/helpers/helpers.js b/client/src/helpers/helpers.js index 2acf5315..f8f276c8 100644 --- a/client/src/helpers/helpers.js +++ b/client/src/helpers/helpers.js @@ -128,12 +128,21 @@ export const normalizeTopStats = (stats) => ( })) ); -export const addClientInfo = (data, clients, param) => data.map((row) => { - const clientIp = row[param]; - const info = clients.find((item) => item[clientIp]) || ''; +export const addClientInfo = (data, clients, ...params) => data.map((row) => { + let info = ''; + params.find((param) => { + const id = row[param]; + if (id) { + const client = clients.find((item) => item[id]) || ''; + info = client?.[id] ?? ''; + } + + return info; + }); + return { ...row, - info: info?.[clientIp] ?? '', + info, }; }); diff --git a/internal/home/clientshttp.go b/internal/home/clientshttp.go index 42d7fa20..edeaf585 100644 --- a/internal/home/clientshttp.go +++ b/internal/home/clientshttp.go @@ -264,8 +264,12 @@ func (clients *clientsContainer) handleFindClient(w http.ResponseWriter, r *http // findTemporary looks up the IP in temporary storages, like autohosts or // blocklists. func (clients *clientsContainer) findTemporary(ip net.IP, idStr string) (cj clientJSON, found bool) { + if ip == nil { + return cj, false + } + ch, ok := clients.FindAutoClient(idStr) - if !ok && ip != nil { + if !ok { // It is still possible that the IP used to be in the runtime // clients list, but then the server was reloaded. So, check // the DNS server's blocked IP list. @@ -286,9 +290,7 @@ func (clients *clientsContainer) findTemporary(ip net.IP, idStr string) (cj clie } cj = clientHostToJSON(idStr, ch) - if ip != nil { - cj.Disallowed, cj.DisallowedRule = clients.dnsServer.IsBlockedIP(ip) - } + cj.Disallowed, cj.DisallowedRule = clients.dnsServer.IsBlockedIP(ip) return cj, true } From 0d0a419bd34dd8fe612f790ace698aebd930bfcc Mon Sep 17 00:00:00 2001 From: Eugene Burkov Date: Fri, 29 Jan 2021 16:09:31 +0300 Subject: [PATCH 068/141] Pull request: 2574 external tests vol.1 Merge in DNS/adguard-home from 2574-external-tests to master Updates #2574. Squashed commit of the following: commit cb7017aa7fc1c81c014c1fa7e0972b06748aff29 Merge: 2a073431 eeeb0383 Author: Eugene Burkov Date: Fri Jan 29 15:43:41 2021 +0300 Merge branch 'master' into 2574-external-tests commit 2a0734311c5a6ddd2d9c3f56a4494a20b4197955 Author: Eugene Burkov Date: Fri Jan 29 15:16:40 2021 +0300 dnsfilter: imp docs commit 49aff871282e2739d27fcbceefdf1bd005c21174 Author: Eugene Burkov Date: Fri Jan 29 14:10:56 2021 +0300 dnsfilter: imp docs commit 11be89a5378c0e451f1b88dc48a2e8827ba38358 Author: Eugene Burkov Date: Thu Jan 28 21:20:55 2021 +0300 dnsfilter: fix go version trouble commit 1ba4afd95bd739c18818ab0bea0ae70d59b880dc Author: Eugene Burkov Date: Thu Jan 28 17:42:29 2021 +0300 dnsfilter: imp tests --- internal/dnsfilter/dnsfilter.go | 16 +- internal/dnsfilter/dnsfilter_test.go | 831 +++++++++++++++--------- internal/dnsfilter/dnsrewrite_test.go | 2 +- internal/dnsfilter/rewrites_test.go | 15 +- internal/dnsfilter/safebrowsing_test.go | 16 +- internal/dnsfilter/safesearch.go | 8 +- 6 files changed, 568 insertions(+), 320 deletions(-) diff --git a/internal/dnsfilter/dnsfilter.go b/internal/dnsfilter/dnsfilter.go index 45ce5b8a..6ade9701 100644 --- a/internal/dnsfilter/dnsfilter.go +++ b/internal/dnsfilter/dnsfilter.go @@ -2,6 +2,7 @@ package dnsfilter import ( + "context" "fmt" "io/ioutil" "net" @@ -91,6 +92,12 @@ type filtersInitializerParams struct { blockFilters []Filter } +// Resolver is the interface for net.Resolver to simplify testing. +type Resolver interface { + // TODO(e.burkov): Replace with LookupIP after upgrading go to v1.15. + LookupIPAddr(ctx context.Context, host string) (ips []net.IPAddr, err error) +} + // DNSFilter matches hostnames and DNS requests against filtering rules. type DNSFilter struct { rulesStorage *filterlist.RuleStorage @@ -110,6 +117,11 @@ type DNSFilter struct { // Channel for passing data to filters-initializer goroutine filtersInitializerChan chan filtersInitializerParams filtersInitializerLock sync.Mutex + + // resolver only looks up the IP address of the host while safe search. + // + // TODO(e.burkov): Use upstream that configured in dnsforward instead. + resolver Resolver } // Filter represents a filter list @@ -805,7 +817,9 @@ func New(c *Config, blockFilters []Filter) *DNSFilter { } } - d := new(DNSFilter) + d := &DNSFilter{ + resolver: net.DefaultResolver, + } err := d.initSecurityServices() if err != nil { diff --git a/internal/dnsfilter/dnsfilter_test.go b/internal/dnsfilter/dnsfilter_test.go index abac1fb9..257953ad 100644 --- a/internal/dnsfilter/dnsfilter_test.go +++ b/internal/dnsfilter/dnsfilter_test.go @@ -2,11 +2,14 @@ package dnsfilter import ( "bytes" + "context" + "crypto/sha256" "fmt" "net" "testing" "github.com/AdguardTeam/AdGuardHome/internal/testutil" + "github.com/AdguardTeam/golibs/cache" "github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/urlfilter/rules" "github.com/miekg/dns" @@ -19,28 +22,21 @@ func TestMain(m *testing.M) { var setts RequestFilteringSettings -// HELPERS -// SAFE BROWSING -// SAFE SEARCH -// PARENTAL -// FILTERING -// BENCHMARKS - -// HELPERS +// Helpers. func purgeCaches() { - if gctx.safebrowsingCache != nil { - gctx.safebrowsingCache.Clear() - } - if gctx.parentalCache != nil { - gctx.parentalCache.Clear() - } - if gctx.safeSearchCache != nil { - gctx.safeSearchCache.Clear() + for _, c := range []cache.Cache{ + gctx.safebrowsingCache, + gctx.parentalCache, + gctx.safeSearchCache, + } { + if c != nil { + c.Clear() + } } } -func NewForTest(c *Config, filters []Filter) *DNSFilter { +func newForTest(c *Config, filters []Filter) *DNSFilter { setts = RequestFilteringSettings{} setts.FilteringEnabled = true if c != nil { @@ -59,48 +55,31 @@ func NewForTest(c *Config, filters []Filter) *DNSFilter { func (d *DNSFilter) checkMatch(t *testing.T, hostname string) { t.Helper() + res, err := d.CheckHost(hostname, dns.TypeA, &setts) - if err != nil { - t.Errorf("Error while matching host %s: %s", hostname, err) - } - if !res.IsFiltered { - t.Errorf("Expected hostname %s to match", hostname) - } + assert.Nilf(t, err, "Error while matching host %s: %s", hostname, err) + assert.Truef(t, res.IsFiltered, "Expected hostname %s to match", hostname) } func (d *DNSFilter) checkMatchIP(t *testing.T, hostname, ip string, qtype uint16) { t.Helper() res, err := d.CheckHost(hostname, qtype, &setts) - if err != nil { - t.Errorf("Error while matching host %s: %s", hostname, err) - } - - if !res.IsFiltered { - t.Errorf("Expected hostname %s to match", hostname) - } - - if len(res.Rules) == 0 { - t.Errorf("Expected result to have rules") - - return - } - - r := res.Rules[0] - if r.IP == nil || r.IP.String() != ip { - t.Errorf("Expected ip %s to match, actual: %v", ip, r.IP) + assert.Nilf(t, err, "Error while matching host %s: %s", hostname, err) + assert.Truef(t, res.IsFiltered, "Expected hostname %s to match", hostname) + if assert.NotEmpty(t, res.Rules, "Expected result to have rules") { + r := res.Rules[0] + assert.NotNilf(t, r.IP, "Expected ip %s to match, actual: %v", ip, r.IP) + assert.Equalf(t, ip, r.IP.String(), "Expected ip %s to match, actual: %v", ip, r.IP) } } func (d *DNSFilter) checkMatchEmpty(t *testing.T, hostname string) { t.Helper() + res, err := d.CheckHost(hostname, dns.TypeA, &setts) - if err != nil { - t.Errorf("Error while matching host %s: %s", hostname, err) - } - if res.IsFiltered { - t.Errorf("Expected hostname %s to not match", hostname) - } + assert.Nilf(t, err, "Error while matching host %s: %s", hostname, err) + assert.Falsef(t, res.IsFiltered, "Expected hostname %s to not match", hostname) } func TestEtcHostsMatching(t *testing.T) { @@ -117,18 +96,18 @@ func TestEtcHostsMatching(t *testing.T) { filters := []Filter{{ ID: 0, Data: []byte(text), }} - d := NewForTest(nil, filters) - defer d.Close() + d := newForTest(nil, filters) + t.Cleanup(d.Close) d.checkMatchIP(t, "google.com", addr, dns.TypeA) d.checkMatchIP(t, "www.google.com", addr, dns.TypeA) d.checkMatchEmpty(t, "subdomain.google.com") d.checkMatchEmpty(t, "example.org") - // IPv4 + // IPv4 match. d.checkMatchIP(t, "block.com", "0.0.0.0", dns.TypeA) - // ...but empty IPv6 + // Empty IPv6. res, err := d.CheckHost("block.com", dns.TypeAAAA, &setts) assert.Nil(t, err) assert.True(t, res.IsFiltered) @@ -137,10 +116,10 @@ func TestEtcHostsMatching(t *testing.T) { assert.Empty(t, res.Rules[0].IP) } - // IPv6 + // IPv6 match. d.checkMatchIP(t, "ipv6.com", addr6, dns.TypeAAAA) - // ...but empty IPv4 + // Empty IPv4. res, err = d.CheckHost("ipv6.com", dns.TypeA, &setts) assert.Nil(t, err) assert.True(t, res.IsFiltered) @@ -149,58 +128,67 @@ func TestEtcHostsMatching(t *testing.T) { assert.Empty(t, res.Rules[0].IP) } - // 2 IPv4 (return only the first one) + // Two IPv4, the first one returned. res, err = d.CheckHost("host2", dns.TypeA, &setts) assert.Nil(t, err) assert.True(t, res.IsFiltered) if assert.Len(t, res.Rules, 1) { - loopback4 := net.IP{0, 0, 0, 1} - assert.Equal(t, res.Rules[0].IP, loopback4) + assert.Equal(t, res.Rules[0].IP, net.IP{0, 0, 0, 1}) } - // ...and 1 IPv6 address + // One IPv6 address. res, err = d.CheckHost("host2", dns.TypeAAAA, &setts) assert.Nil(t, err) assert.True(t, res.IsFiltered) if assert.Len(t, res.Rules, 1) { - loopback6 := net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1} - assert.Equal(t, res.Rules[0].IP, loopback6) + assert.Equal(t, res.Rules[0].IP, net.IPv6loopback) } } -// SAFE BROWSING +// Safe Browsing. func TestSafeBrowsing(t *testing.T) { logOutput := &bytes.Buffer{} testutil.ReplaceLogWriter(t, logOutput) testutil.ReplaceLogLevel(t, log.DEBUG) - d := NewForTest(&Config{SafeBrowsingEnabled: true}, nil) - defer d.Close() - d.checkMatch(t, "wmconvirus.narod.ru") + d := newForTest(&Config{SafeBrowsingEnabled: true}, nil) + t.Cleanup(d.Close) + matching := "wmconvirus.narod.ru" + d.safeBrowsingUpstream = &testSbUpstream{ + hostname: matching, + block: true, + } + d.checkMatch(t, matching) - assert.Contains(t, logOutput.String(), "SafeBrowsing lookup for wmconvirus.narod.ru") + assert.Contains(t, logOutput.String(), "SafeBrowsing lookup for "+matching) - d.checkMatch(t, "test.wmconvirus.narod.ru") + d.checkMatch(t, "test."+matching) d.checkMatchEmpty(t, "yandex.ru") d.checkMatchEmpty(t, "pornhub.com") - // test cached result + // Cached result. d.safeBrowsingServer = "127.0.0.1" - d.checkMatch(t, "wmconvirus.narod.ru") + d.checkMatch(t, matching) d.checkMatchEmpty(t, "pornhub.com") d.safeBrowsingServer = defaultSafebrowsingServer } func TestParallelSB(t *testing.T) { - d := NewForTest(&Config{SafeBrowsingEnabled: true}, nil) - defer d.Close() + d := newForTest(&Config{SafeBrowsingEnabled: true}, nil) + t.Cleanup(d.Close) + matching := "wmconvirus.narod.ru" + d.safeBrowsingUpstream = &testSbUpstream{ + hostname: matching, + block: true, + } + t.Run("group", func(t *testing.T) { for i := 0; i < 100; i++ { t.Run(fmt.Sprintf("aaa%d", i), func(t *testing.T) { t.Parallel() - d.checkMatch(t, "wmconvirus.narod.ru") - d.checkMatch(t, "test.wmconvirus.narod.ru") + d.checkMatch(t, matching) + d.checkMatch(t, "test."+matching) d.checkMatchEmpty(t, "yandex.ru") d.checkMatchEmpty(t, "pornhub.com") }) @@ -208,59 +196,81 @@ func TestParallelSB(t *testing.T) { }) } -// SAFE SEARCH +// Safe Search. func TestSafeSearch(t *testing.T) { - d := NewForTest(&Config{SafeSearchEnabled: true}, nil) - defer d.Close() + d := newForTest(&Config{SafeSearchEnabled: true}, nil) + t.Cleanup(d.Close) val, ok := d.SafeSearchDomain("www.google.com") - if !ok { - t.Errorf("Expected safesearch to find result for www.google.com") - } - if val != "forcesafesearch.google.com" { - t.Errorf("Expected safesearch for google.com to be forcesafesearch.google.com") - } + assert.True(t, ok, "Expected safesearch to find result for www.google.com") + assert.Equal(t, "forcesafesearch.google.com", val, "Expected safesearch for google.com to be forcesafesearch.google.com") } func TestCheckHostSafeSearchYandex(t *testing.T) { - d := NewForTest(&Config{SafeSearchEnabled: true}, nil) - defer d.Close() + d := newForTest(&Config{SafeSearchEnabled: true}, nil) + t.Cleanup(d.Close) - // Slice of yandex domains - yandex := []string{"yAndeX.ru", "YANdex.COM", "yandex.ua", "yandex.by", "yandex.kz", "www.yandex.com"} - - // Check host for each domain - for _, host := range yandex { + // Check host for each domain. + for _, host := range []string{ + "yAndeX.ru", + "YANdex.COM", + "yandex.ua", + "yandex.by", + "yandex.kz", + "www.yandex.com", + } { res, err := d.CheckHost(host, dns.TypeA, &setts) assert.Nil(t, err) assert.True(t, res.IsFiltered) if assert.Len(t, res.Rules, 1) { - assert.Equal(t, res.Rules[0].IP.String(), "213.180.193.56") + assert.Equal(t, res.Rules[0].IP, net.IPv4(213, 180, 193, 56)) } } } +// testResolver is a Resolver for tests. +type testResolver struct{} + +// LookupIP implements Resolver interface for *testResolver. +func (r *testResolver) LookupIPAddr(_ context.Context, host string) (ips []net.IPAddr, err error) { + hash := sha256.Sum256([]byte(host)) + addrs := []net.IPAddr{{ + IP: net.IP(hash[:4]), + Zone: "somezone", + }, { + IP: net.IP(hash[4:20]), + Zone: "somezone", + }} + return addrs, nil +} + func TestCheckHostSafeSearchGoogle(t *testing.T) { - d := NewForTest(&Config{SafeSearchEnabled: true}, nil) - defer d.Close() + d := newForTest(&Config{SafeSearchEnabled: true}, nil) + t.Cleanup(d.Close) + d.resolver = &testResolver{} - // Slice of google domains - googleDomains := []string{"www.google.com", "www.google.im", "www.google.co.in", "www.google.iq", "www.google.is", "www.google.it", "www.google.je"} - - // Check host for each domain - for _, host := range googleDomains { - res, err := d.CheckHost(host, dns.TypeA, &setts) - assert.Nil(t, err) - assert.True(t, res.IsFiltered) - if assert.Len(t, res.Rules, 1) { - assert.NotEqual(t, res.Rules[0].IP.String(), "0.0.0.0") - } + // Check host for each domain. + for _, host := range []string{ + "www.google.com", + "www.google.im", + "www.google.co.in", + "www.google.iq", + "www.google.is", + "www.google.it", + "www.google.je", + } { + t.Run(host, func(t *testing.T) { + res, err := d.CheckHost(host, dns.TypeA, &setts) + assert.Nil(t, err) + assert.True(t, res.IsFiltered) + assert.Len(t, res.Rules, 1) + }) } } func TestSafeSearchCacheYandex(t *testing.T) { - d := NewForTest(nil, nil) - defer d.Close() + d := newForTest(nil, nil) + t.Cleanup(d.Close) domain := "yandex.ru" // Check host with disabled safesearch. @@ -269,54 +279,55 @@ func TestSafeSearchCacheYandex(t *testing.T) { assert.False(t, res.IsFiltered) assert.Empty(t, res.Rules) - d = NewForTest(&Config{SafeSearchEnabled: true}, nil) - defer d.Close() + d = newForTest(&Config{SafeSearchEnabled: true}, nil) + t.Cleanup(d.Close) res, err = d.CheckHost(domain, dns.TypeA, &setts) - if err != nil { - t.Fatalf("CheckHost for safesearh domain %s failed cause %s", domain, err) - } + assert.Nilf(t, err, "CheckHost for safesearh domain %s failed cause %s", domain, err) - // For yandex we already know valid ip. + // For yandex we already know valid IP. if assert.Len(t, res.Rules, 1) { - assert.Equal(t, res.Rules[0].IP.String(), "213.180.193.56") + assert.Equal(t, res.Rules[0].IP, net.IPv4(213, 180, 193, 56)) } // Check cache. cachedValue, isFound := getCachedResult(gctx.safeSearchCache, domain) assert.True(t, isFound) if assert.Len(t, cachedValue.Rules, 1) { - assert.Equal(t, cachedValue.Rules[0].IP.String(), "213.180.193.56") + assert.Equal(t, cachedValue.Rules[0].IP, net.IPv4(213, 180, 193, 56)) } } func TestSafeSearchCacheGoogle(t *testing.T) { - d := NewForTest(nil, nil) - defer d.Close() + d := newForTest(nil, nil) + t.Cleanup(d.Close) + + resolver := &testResolver{} + d.resolver = resolver + domain := "www.google.ru" res, err := d.CheckHost(domain, dns.TypeA, &setts) assert.Nil(t, err) assert.False(t, res.IsFiltered) assert.Empty(t, res.Rules) - d = NewForTest(&Config{SafeSearchEnabled: true}, nil) - defer d.Close() + d = newForTest(&Config{SafeSearchEnabled: true}, nil) + t.Cleanup(d.Close) + d.resolver = resolver - // Let's lookup for safesearch domain + // Lookup for safesearch domain. safeDomain, ok := d.SafeSearchDomain(domain) - if !ok { - t.Fatalf("Failed to get safesearch domain for %s", domain) - } + assert.Truef(t, ok, "Failed to get safesearch domain for %s", domain) - ips, err := net.LookupIP(safeDomain) + ipAddrs, err := resolver.LookupIPAddr(context.Background(), safeDomain) if err != nil { t.Fatalf("Failed to lookup for %s", safeDomain) } - ip := ips[0] - for _, i := range ips { - if i.To4() != nil { - ip = i + ip := ipAddrs[0].IP + for _, ipAddr := range ipAddrs { + if ipAddr.IP.To4() != nil { + ip = ipAddr.IP break } } @@ -335,114 +346,324 @@ func TestSafeSearchCacheGoogle(t *testing.T) { } } -// PARENTAL +// Parental. func TestParentalControl(t *testing.T) { logOutput := &bytes.Buffer{} testutil.ReplaceLogWriter(t, logOutput) testutil.ReplaceLogLevel(t, log.DEBUG) - d := NewForTest(&Config{ParentalEnabled: true}, nil) - defer d.Close() - d.checkMatch(t, "pornhub.com") - assert.Contains(t, logOutput.String(), "Parental lookup for pornhub.com") - d.checkMatch(t, "www.pornhub.com") + d := newForTest(&Config{ParentalEnabled: true}, nil) + t.Cleanup(d.Close) + matching := "pornhub.com" + d.parentalUpstream = &testSbUpstream{ + hostname: matching, + block: true, + } + + d.checkMatch(t, matching) + assert.Contains(t, logOutput.String(), "Parental lookup for "+matching) + d.checkMatch(t, "www."+matching) d.checkMatchEmpty(t, "www.yandex.ru") d.checkMatchEmpty(t, "yandex.ru") d.checkMatchEmpty(t, "api.jquery.com") // test cached result d.parentalServer = "127.0.0.1" - d.checkMatch(t, "pornhub.com") + d.checkMatch(t, matching) d.checkMatchEmpty(t, "yandex.ru") d.parentalServer = defaultParentalServer } -// FILTERING - -const nl = "\n" - -const ( - blockingRules = `||example.org^` + nl - allowlistRules = `||example.org^` + nl + `@@||test.example.org` + nl - importantRules = `@@||example.org^` + nl + `||test.example.org^$important` + nl - regexRules = `/example\.org/` + nl + `@@||test.example.org^` + nl - maskRules = `test*.example.org^` + nl + `exam*.com` + nl - dnstypeRules = `||example.org^$dnstype=AAAA` + nl + `@@||test.example.org^` + nl -) - -var tests = []struct { - testname string - rules string - hostname string - isFiltered bool - reason Reason - dnsType uint16 -}{ - {"sanity", "||doubleclick.net^", "www.doubleclick.net", true, FilteredBlockList, dns.TypeA}, - {"sanity", "||doubleclick.net^", "nodoubleclick.net", false, NotFilteredNotFound, dns.TypeA}, - {"sanity", "||doubleclick.net^", "doubleclick.net.ru", false, NotFilteredNotFound, dns.TypeA}, - {"sanity", "||doubleclick.net^", "wmconvirus.narod.ru", false, NotFilteredNotFound, dns.TypeA}, - - {"blocking", blockingRules, "example.org", true, FilteredBlockList, dns.TypeA}, - {"blocking", blockingRules, "test.example.org", true, FilteredBlockList, dns.TypeA}, - {"blocking", blockingRules, "test.test.example.org", true, FilteredBlockList, dns.TypeA}, - {"blocking", blockingRules, "testexample.org", false, NotFilteredNotFound, dns.TypeA}, - {"blocking", blockingRules, "onemoreexample.org", false, NotFilteredNotFound, dns.TypeA}, - - {"allowlist", allowlistRules, "example.org", true, FilteredBlockList, dns.TypeA}, - {"allowlist", allowlistRules, "test.example.org", false, NotFilteredAllowList, dns.TypeA}, - {"allowlist", allowlistRules, "test.test.example.org", false, NotFilteredAllowList, dns.TypeA}, - {"allowlist", allowlistRules, "testexample.org", false, NotFilteredNotFound, dns.TypeA}, - {"allowlist", allowlistRules, "onemoreexample.org", false, NotFilteredNotFound, dns.TypeA}, - - {"important", importantRules, "example.org", false, NotFilteredAllowList, dns.TypeA}, - {"important", importantRules, "test.example.org", true, FilteredBlockList, dns.TypeA}, - {"important", importantRules, "test.test.example.org", true, FilteredBlockList, dns.TypeA}, - {"important", importantRules, "testexample.org", false, NotFilteredNotFound, dns.TypeA}, - {"important", importantRules, "onemoreexample.org", false, NotFilteredNotFound, dns.TypeA}, - - {"regex", regexRules, "example.org", true, FilteredBlockList, dns.TypeA}, - {"regex", regexRules, "test.example.org", false, NotFilteredAllowList, dns.TypeA}, - {"regex", regexRules, "test.test.example.org", false, NotFilteredAllowList, dns.TypeA}, - {"regex", regexRules, "testexample.org", true, FilteredBlockList, dns.TypeA}, - {"regex", regexRules, "onemoreexample.org", true, FilteredBlockList, dns.TypeA}, - - {"mask", maskRules, "test.example.org", true, FilteredBlockList, dns.TypeA}, - {"mask", maskRules, "test2.example.org", true, FilteredBlockList, dns.TypeA}, - {"mask", maskRules, "example.com", true, FilteredBlockList, dns.TypeA}, - {"mask", maskRules, "exampleeee.com", true, FilteredBlockList, dns.TypeA}, - {"mask", maskRules, "onemoreexamsite.com", true, FilteredBlockList, dns.TypeA}, - {"mask", maskRules, "example.org", false, NotFilteredNotFound, dns.TypeA}, - {"mask", maskRules, "testexample.org", false, NotFilteredNotFound, dns.TypeA}, - {"mask", maskRules, "example.co.uk", false, NotFilteredNotFound, dns.TypeA}, - - {"dnstype", dnstypeRules, "onemoreexample.org", false, NotFilteredNotFound, dns.TypeA}, - {"dnstype", dnstypeRules, "example.org", false, NotFilteredNotFound, dns.TypeA}, - {"dnstype", dnstypeRules, "example.org", true, FilteredBlockList, dns.TypeAAAA}, - {"dnstype", dnstypeRules, "test.example.org", false, NotFilteredAllowList, dns.TypeA}, - {"dnstype", dnstypeRules, "test.example.org", false, NotFilteredAllowList, dns.TypeAAAA}, -} +// Filtering. func TestMatching(t *testing.T) { - for _, test := range tests { - t.Run(fmt.Sprintf("%s-%s", test.testname, test.hostname), func(t *testing.T) { - filters := []Filter{{ - ID: 0, Data: []byte(test.rules), - }} - d := NewForTest(nil, filters) - defer d.Close() + const nl = "\n" + const ( + blockingRules = `||example.org^` + nl + allowlistRules = `||example.org^` + nl + `@@||test.example.org` + nl + importantRules = `@@||example.org^` + nl + `||test.example.org^$important` + nl + regexRules = `/example\.org/` + nl + `@@||test.example.org^` + nl + maskRules = `test*.example.org^` + nl + `exam*.com` + nl + dnstypeRules = `||example.org^$dnstype=AAAA` + nl + `@@||test.example.org^` + nl + ) + testCases := []struct { + name string + rules string + host string + wantIsFiltered bool + wantReason Reason + wantDNSType uint16 + }{{ + name: "sanity", + rules: "||doubleclick.net^", + host: "www.doubleclick.net", + wantIsFiltered: true, + wantReason: FilteredBlockList, + wantDNSType: dns.TypeA, + }, { + name: "sanity", + rules: "||doubleclick.net^", + host: "nodoubleclick.net", + wantIsFiltered: false, + wantReason: NotFilteredNotFound, + wantDNSType: dns.TypeA, + }, { + name: "sanity", + rules: "||doubleclick.net^", + host: "doubleclick.net.ru", + wantIsFiltered: false, + wantReason: NotFilteredNotFound, + wantDNSType: dns.TypeA, + }, { + name: "sanity", + rules: "||doubleclick.net^", + host: "wmconvirus.narod.ru", + wantIsFiltered: false, + wantReason: NotFilteredNotFound, + wantDNSType: dns.TypeA, + }, { + name: "blocking", + rules: blockingRules, + host: "example.org", + wantIsFiltered: true, + wantReason: FilteredBlockList, + wantDNSType: dns.TypeA, + }, { + name: "blocking", + rules: blockingRules, + host: "test.example.org", + wantIsFiltered: true, + wantReason: FilteredBlockList, + wantDNSType: dns.TypeA, + }, { + name: "blocking", + rules: blockingRules, + host: "test.test.example.org", + wantIsFiltered: true, + wantReason: FilteredBlockList, + wantDNSType: dns.TypeA, + }, { + name: "blocking", + rules: blockingRules, + host: "testexample.org", + wantIsFiltered: false, + wantReason: NotFilteredNotFound, + wantDNSType: dns.TypeA, + }, { + name: "blocking", + rules: blockingRules, + host: "onemoreexample.org", + wantIsFiltered: false, + wantReason: NotFilteredNotFound, + wantDNSType: dns.TypeA, + }, { + name: "allowlist", + rules: allowlistRules, + host: "example.org", + wantIsFiltered: true, + wantReason: FilteredBlockList, + wantDNSType: dns.TypeA, + }, { + name: "allowlist", + rules: allowlistRules, + host: "test.example.org", + wantIsFiltered: false, + wantReason: NotFilteredAllowList, + wantDNSType: dns.TypeA, + }, { + name: "allowlist", + rules: allowlistRules, + host: "test.test.example.org", + wantIsFiltered: false, + wantReason: NotFilteredAllowList, + wantDNSType: dns.TypeA, + }, { + name: "allowlist", + rules: allowlistRules, + host: "testexample.org", + wantIsFiltered: false, + wantReason: NotFilteredNotFound, + wantDNSType: dns.TypeA, + }, { + name: "allowlist", + rules: allowlistRules, + host: "onemoreexample.org", + wantIsFiltered: false, + wantReason: NotFilteredNotFound, + wantDNSType: dns.TypeA, + }, { + name: "important", + rules: importantRules, + host: "example.org", + wantIsFiltered: false, + wantReason: NotFilteredAllowList, + wantDNSType: dns.TypeA, + }, { + name: "important", + rules: importantRules, + host: "test.example.org", + wantIsFiltered: true, + wantReason: FilteredBlockList, + wantDNSType: dns.TypeA, + }, { + name: "important", + rules: importantRules, + host: "test.test.example.org", + wantIsFiltered: true, + wantReason: FilteredBlockList, + wantDNSType: dns.TypeA, + }, { + name: "important", + rules: importantRules, + host: "testexample.org", + wantIsFiltered: false, + wantReason: NotFilteredNotFound, + wantDNSType: dns.TypeA, + }, { + name: "important", + rules: importantRules, + host: "onemoreexample.org", + wantIsFiltered: false, + wantReason: NotFilteredNotFound, + wantDNSType: dns.TypeA, + }, { + name: "regex", + rules: regexRules, + host: "example.org", + wantIsFiltered: true, + wantReason: FilteredBlockList, + wantDNSType: dns.TypeA, + }, { + name: "regex", + rules: regexRules, + host: "test.example.org", + wantIsFiltered: false, + wantReason: NotFilteredAllowList, + wantDNSType: dns.TypeA, + }, { + name: "regex", + rules: regexRules, + host: "test.test.example.org", + wantIsFiltered: false, + wantReason: NotFilteredAllowList, + wantDNSType: dns.TypeA, + }, { + name: "regex", + rules: regexRules, + host: "testexample.org", + wantIsFiltered: true, + wantReason: FilteredBlockList, + wantDNSType: dns.TypeA, + }, { + name: "regex", + rules: regexRules, + host: "onemoreexample.org", + wantIsFiltered: true, + wantReason: FilteredBlockList, + wantDNSType: dns.TypeA, + }, { + name: "mask", + rules: maskRules, + host: "test.example.org", + wantIsFiltered: true, + wantReason: FilteredBlockList, + wantDNSType: dns.TypeA, + }, { + name: "mask", + rules: maskRules, + host: "test2.example.org", + wantIsFiltered: true, + wantReason: FilteredBlockList, + wantDNSType: dns.TypeA, + }, { + name: "mask", + rules: maskRules, + host: "example.com", + wantIsFiltered: true, + wantReason: FilteredBlockList, + wantDNSType: dns.TypeA, + }, { + name: "mask", + rules: maskRules, + host: "exampleeee.com", + wantIsFiltered: true, + wantReason: FilteredBlockList, + wantDNSType: dns.TypeA, + }, { + name: "mask", + rules: maskRules, + host: "onemoreexamsite.com", + wantIsFiltered: true, + wantReason: FilteredBlockList, + wantDNSType: dns.TypeA, + }, { + name: "mask", + rules: maskRules, + host: "example.org", + wantIsFiltered: false, + wantReason: NotFilteredNotFound, + wantDNSType: dns.TypeA, + }, { + name: "mask", + rules: maskRules, + host: "testexample.org", + wantIsFiltered: false, + wantReason: NotFilteredNotFound, + wantDNSType: dns.TypeA, + }, { + name: "mask", + rules: maskRules, + host: "example.co.uk", + wantIsFiltered: false, + wantReason: NotFilteredNotFound, + wantDNSType: dns.TypeA, + }, { + name: "dnstype", + rules: dnstypeRules, + host: "onemoreexample.org", + wantIsFiltered: false, + wantReason: NotFilteredNotFound, + wantDNSType: dns.TypeA, + }, { + name: "dnstype", + rules: dnstypeRules, + host: "example.org", + wantIsFiltered: false, + wantReason: NotFilteredNotFound, + wantDNSType: dns.TypeA, + }, { + name: "dnstype", + rules: dnstypeRules, + host: "example.org", + wantIsFiltered: true, + wantReason: FilteredBlockList, + wantDNSType: dns.TypeAAAA, + }, { + name: "dnstype", + rules: dnstypeRules, + host: "test.example.org", + wantIsFiltered: false, + wantReason: NotFilteredAllowList, + wantDNSType: dns.TypeA, + }, { + name: "dnstype", + rules: dnstypeRules, + host: "test.example.org", + wantIsFiltered: false, + wantReason: NotFilteredAllowList, + wantDNSType: dns.TypeAAAA, + }} + for _, tc := range testCases { + t.Run(fmt.Sprintf("%s-%s", tc.name, tc.host), func(t *testing.T) { + filters := []Filter{{ID: 0, Data: []byte(tc.rules)}} + d := newForTest(nil, filters) + t.Cleanup(d.Close) - res, err := d.CheckHost(test.hostname, test.dnsType, &setts) - if err != nil { - t.Errorf("Error while matching host %s: %s", test.hostname, err) - } - if res.IsFiltered != test.isFiltered { - t.Errorf("Hostname %s has wrong result (%v must be %v)", test.hostname, res.IsFiltered, test.isFiltered) - } - if res.Reason != test.reason { - t.Errorf("Hostname %s has wrong reason (%v must be %v)", test.hostname, res.Reason.String(), test.reason.String()) - } + res, err := d.CheckHost(tc.host, tc.wantDNSType, &setts) + assert.Nilf(t, err, "Error while matching host %s: %s", tc.host, err) + assert.Equalf(t, tc.wantIsFiltered, res.IsFiltered, "Hostname %s has wrong result (%v must be %v)", tc.host, res.IsFiltered, tc.wantIsFiltered) + assert.Equalf(t, tc.wantReason, res.Reason, "Hostname %s has wrong reason (%v must be %v)", tc.host, res.Reason, tc.wantReason) }) } } @@ -461,11 +682,11 @@ func TestWhitelist(t *testing.T) { whiteFilters := []Filter{{ ID: 0, Data: []byte(whiteRules), }} - d := NewForTest(nil, filters) + d := newForTest(nil, filters) d.SetFilters(filters, whiteFilters, false) - defer d.Close() + t.Cleanup(d.Close) - // matched by white filter + // Matched by white filter. res, err := d.CheckHost("host1", dns.TypeA, &setts) assert.Nil(t, err) assert.False(t, res.IsFiltered) @@ -474,7 +695,7 @@ func TestWhitelist(t *testing.T) { assert.Equal(t, "||host1^", res.Rules[0].Text) } - // not matched by white filter, but matched by block filter + // Not matched by white filter, but matched by block filter. res, err = d.CheckHost("host2", dns.TypeA, &setts) assert.Nil(t, err) assert.True(t, res.IsFiltered) @@ -484,7 +705,7 @@ func TestWhitelist(t *testing.T) { } } -// CLIENT SETTINGS +// Client Settings. func applyClientSettings(setts *RequestFilteringSettings) { setts.FilteringEnabled = false @@ -498,127 +719,131 @@ func applyClientSettings(setts *RequestFilteringSettings) { setts.ServicesRules = append(setts.ServicesRules, s) } -// Check behaviour without any per-client settings, -// then apply per-client settings and check behaviour once again func TestClientSettings(t *testing.T) { - var r Result - filters := []Filter{{ - ID: 0, Data: []byte("||example.org^\n"), + d := newForTest( + &Config{ + ParentalEnabled: true, + SafeBrowsingEnabled: false, + }, + []Filter{{ + ID: 0, Data: []byte("||example.org^\n"), + }}, + ) + t.Cleanup(d.Close) + d.parentalUpstream = &testSbUpstream{ + hostname: "pornhub.com", + block: true, + } + d.safeBrowsingUpstream = &testSbUpstream{ + hostname: "wmconvirus.narod.ru", + block: true, + } + + type testCase struct { + name string + host string + before bool + wantReason Reason + } + testCases := []testCase{{ + name: "filters", + host: "example.org", + before: true, + wantReason: FilteredBlockList, + }, { + name: "parental", + host: "pornhub.com", + before: true, + wantReason: FilteredParental, + }, { + name: "safebrowsing", + host: "wmconvirus.narod.ru", + before: false, + wantReason: FilteredSafeBrowsing, + }, { + name: "additional_rules", + host: "facebook.com", + before: false, + wantReason: FilteredBlockedService, }} - d := NewForTest(&Config{ParentalEnabled: true, SafeBrowsingEnabled: false}, filters) - defer d.Close() - // no client settings: - - // blocked by filters - r, _ = d.CheckHost("example.org", dns.TypeA, &setts) - if !r.IsFiltered || r.Reason != FilteredBlockList { - t.Fatalf("CheckHost FilteredBlockList") + makeTester := func(tc testCase, before bool) func(t *testing.T) { + return func(t *testing.T) { + r, _ := d.CheckHost(tc.host, dns.TypeA, &setts) + if before { + assert.True(t, r.IsFiltered) + assert.Equal(t, tc.wantReason, r.Reason) + } else { + assert.False(t, r.IsFiltered) + } + } } - // blocked by parental - r, _ = d.CheckHost("pornhub.com", dns.TypeA, &setts) - if !r.IsFiltered || r.Reason != FilteredParental { - t.Fatalf("CheckHost FilteredParental") + // Check behaviour without any per-client settings, then apply per-client + // settings and check behaviour once again. + for _, tc := range testCases { + t.Run(tc.name, makeTester(tc, tc.before)) } - // safesearch is disabled - r, _ = d.CheckHost("wmconvirus.narod.ru", dns.TypeA, &setts) - if r.IsFiltered { - t.Fatalf("CheckHost safesearch") - } - - // not blocked - r, _ = d.CheckHost("facebook.com", dns.TypeA, &setts) - assert.False(t, r.IsFiltered) - - // override client settings: applyClientSettings(&setts) - // override filtering settings - r, _ = d.CheckHost("example.org", dns.TypeA, &setts) - if r.IsFiltered { - t.Fatalf("CheckHost") + for _, tc := range testCases { + t.Run(tc.name, makeTester(tc, !tc.before)) } - - // override parental settings (force disable parental) - r, _ = d.CheckHost("pornhub.com", dns.TypeA, &setts) - if r.IsFiltered { - t.Fatalf("CheckHost") - } - - // override safesearch settings (force enable safesearch) - r, _ = d.CheckHost("wmconvirus.narod.ru", dns.TypeA, &setts) - if !r.IsFiltered || r.Reason != FilteredSafeBrowsing { - t.Fatalf("CheckHost FilteredSafeBrowsing") - } - - // blocked by additional rules - r, _ = d.CheckHost("facebook.com", dns.TypeA, &setts) - assert.True(t, r.IsFiltered) - assert.Equal(t, r.Reason, FilteredBlockedService) } -// BENCHMARKS +// Benchmarks. func BenchmarkSafeBrowsing(b *testing.B) { - d := NewForTest(&Config{SafeBrowsingEnabled: true}, nil) - defer d.Close() + d := newForTest(&Config{SafeBrowsingEnabled: true}, nil) + b.Cleanup(d.Close) + blocked := "wmconvirus.narod.ru" + d.safeBrowsingUpstream = &testSbUpstream{ + hostname: blocked, + block: true, + } for n := 0; n < b.N; n++ { - hostname := "wmconvirus.narod.ru" - res, err := d.CheckHost(hostname, dns.TypeA, &setts) - if err != nil { - b.Errorf("Error while matching host %s: %s", hostname, err) - } - if !res.IsFiltered { - b.Errorf("Expected hostname %s to match", hostname) - } + res, err := d.CheckHost(blocked, dns.TypeA, &setts) + assert.Nilf(b, err, "Error while matching host %s: %s", blocked, err) + assert.True(b, res.IsFiltered, "Expected hostname %s to match", blocked) } } func BenchmarkSafeBrowsingParallel(b *testing.B) { - d := NewForTest(&Config{SafeBrowsingEnabled: true}, nil) - defer d.Close() + d := newForTest(&Config{SafeBrowsingEnabled: true}, nil) + b.Cleanup(d.Close) + blocked := "wmconvirus.narod.ru" + d.safeBrowsingUpstream = &testSbUpstream{ + hostname: blocked, + block: true, + } b.RunParallel(func(pb *testing.PB) { for pb.Next() { - hostname := "wmconvirus.narod.ru" - res, err := d.CheckHost(hostname, dns.TypeA, &setts) - if err != nil { - b.Errorf("Error while matching host %s: %s", hostname, err) - } - if !res.IsFiltered { - b.Errorf("Expected hostname %s to match", hostname) - } + res, err := d.CheckHost(blocked, dns.TypeA, &setts) + assert.Nilf(b, err, "Error while matching host %s: %s", blocked, err) + assert.True(b, res.IsFiltered, "Expected hostname %s to match", blocked) } }) } func BenchmarkSafeSearch(b *testing.B) { - d := NewForTest(&Config{SafeSearchEnabled: true}, nil) - defer d.Close() + d := newForTest(&Config{SafeSearchEnabled: true}, nil) + b.Cleanup(d.Close) for n := 0; n < b.N; n++ { val, ok := d.SafeSearchDomain("www.google.com") - if !ok { - b.Errorf("Expected safesearch to find result for www.google.com") - } - if val != "forcesafesearch.google.com" { - b.Errorf("Expected safesearch for google.com to be forcesafesearch.google.com") - } + assert.True(b, ok, "Expected safesearch to find result for www.google.com") + assert.Equal(b, "forcesafesearch.google.com", val, "Expected safesearch for google.com to be forcesafesearch.google.com") } } func BenchmarkSafeSearchParallel(b *testing.B) { - d := NewForTest(&Config{SafeSearchEnabled: true}, nil) - defer d.Close() + d := newForTest(&Config{SafeSearchEnabled: true}, nil) + b.Cleanup(d.Close) b.RunParallel(func(pb *testing.PB) { for pb.Next() { val, ok := d.SafeSearchDomain("www.google.com") - if !ok { - b.Errorf("Expected safesearch to find result for www.google.com") - } - if val != "forcesafesearch.google.com" { - b.Errorf("Expected safesearch for google.com to be forcesafesearch.google.com") - } + assert.True(b, ok, "Expected safesearch to find result for www.google.com") + assert.Equal(b, "forcesafesearch.google.com", val, "Expected safesearch for google.com to be forcesafesearch.google.com") } }) } diff --git a/internal/dnsfilter/dnsrewrite_test.go b/internal/dnsfilter/dnsrewrite_test.go index 201de44f..c915d920 100644 --- a/internal/dnsfilter/dnsrewrite_test.go +++ b/internal/dnsfilter/dnsrewrite_test.go @@ -45,7 +45,7 @@ func TestDNSFilter_CheckHostRules_dnsrewrite(t *testing.T) { @@||disable-all^$dnsrewrite ` - f := NewForTest(nil, []Filter{{ID: 0, Data: []byte(text)}}) + f := newForTest(nil, []Filter{{ID: 0, Data: []byte(text)}}) setts := &RequestFilteringSettings{ FilteringEnabled: true, } diff --git a/internal/dnsfilter/rewrites_test.go b/internal/dnsfilter/rewrites_test.go index 12a59409..a56d2a48 100644 --- a/internal/dnsfilter/rewrites_test.go +++ b/internal/dnsfilter/rewrites_test.go @@ -9,7 +9,8 @@ import ( ) func TestRewrites(t *testing.T) { - d := DNSFilter{} + d := newForTest(nil, nil) + t.Cleanup(d.Close) // CNAME, A, AAAA d.Rewrites = []RewriteEntry{ {"somecname", "somehost.com", 0, nil}, @@ -104,7 +105,8 @@ func TestRewrites(t *testing.T) { } func TestRewritesLevels(t *testing.T) { - d := DNSFilter{} + d := newForTest(nil, nil) + t.Cleanup(d.Close) // exact host, wildcard L2, wildcard L3 d.Rewrites = []RewriteEntry{ {"host.com", "1.1.1.1", 0, nil}, @@ -133,7 +135,8 @@ func TestRewritesLevels(t *testing.T) { } func TestRewritesExceptionCNAME(t *testing.T) { - d := DNSFilter{} + d := newForTest(nil, nil) + t.Cleanup(d.Close) // wildcard; exception for a sub-domain d.Rewrites = []RewriteEntry{ {"*.host.com", "2.2.2.2", 0, nil}, @@ -153,7 +156,8 @@ func TestRewritesExceptionCNAME(t *testing.T) { } func TestRewritesExceptionWC(t *testing.T) { - d := DNSFilter{} + d := newForTest(nil, nil) + t.Cleanup(d.Close) // wildcard; exception for a sub-wildcard d.Rewrites = []RewriteEntry{ {"*.host.com", "2.2.2.2", 0, nil}, @@ -173,7 +177,8 @@ func TestRewritesExceptionWC(t *testing.T) { } func TestRewritesExceptionIP(t *testing.T) { - d := DNSFilter{} + d := newForTest(nil, nil) + t.Cleanup(d.Close) // exception for AAAA record d.Rewrites = []RewriteEntry{ {"host.com", "1.2.3.4", 0, nil}, diff --git a/internal/dnsfilter/safebrowsing_test.go b/internal/dnsfilter/safebrowsing_test.go index 21ea2277..a1f627c4 100644 --- a/internal/dnsfilter/safebrowsing_test.go +++ b/internal/dnsfilter/safebrowsing_test.go @@ -4,6 +4,7 @@ import ( "crypto/sha256" "encoding/hex" "strings" + "sync" "testing" "github.com/AdguardTeam/AdGuardHome/internal/agherr" @@ -121,8 +122,8 @@ func (teu *testErrUpstream) Address() string { } func TestSBPC_checkErrorUpstream(t *testing.T) { - d := NewForTest(&Config{SafeBrowsingEnabled: true}, nil) - defer d.Close() + d := newForTest(&Config{SafeBrowsingEnabled: true}, nil) + t.Cleanup(d.Close) ups := &testErrUpstream{} @@ -142,11 +143,14 @@ type testSbUpstream struct { hostname string block bool requestsCount int + counterLock sync.RWMutex } // Exchange returns a message depending on the upstream settings (hostname, block) func (u *testSbUpstream) Exchange(r *dns.Msg) (*dns.Msg, error) { + u.counterLock.Lock() u.requestsCount++ + u.counterLock.Unlock() hash := sha256.Sum256([]byte(u.hostname)) prefix := hash[0:2] @@ -175,8 +179,8 @@ func (u *testSbUpstream) Address() string { } func TestSBPC_sbValidResponse(t *testing.T) { - d := NewForTest(&Config{SafeBrowsingEnabled: true}, nil) - defer d.Close() + d := newForTest(&Config{SafeBrowsingEnabled: true}, nil) + t.Cleanup(d.Close) ups := &testSbUpstream{} d.safeBrowsingUpstream = ups @@ -213,8 +217,8 @@ func TestSBPC_sbValidResponse(t *testing.T) { } func TestSBPC_pcBlockedResponse(t *testing.T) { - d := NewForTest(&Config{SafeBrowsingEnabled: true}, nil) - defer d.Close() + d := newForTest(&Config{SafeBrowsingEnabled: true}, nil) + t.Cleanup(d.Close) ups := &testSbUpstream{} d.safeBrowsingUpstream = ups diff --git a/internal/dnsfilter/safesearch.go b/internal/dnsfilter/safesearch.go index 958962db..bc72cc9e 100644 --- a/internal/dnsfilter/safesearch.go +++ b/internal/dnsfilter/safesearch.go @@ -2,6 +2,7 @@ package dnsfilter import ( "bytes" + "context" "encoding/binary" "encoding/gob" "encoding/json" @@ -101,15 +102,14 @@ func (d *DNSFilter) checkSafeSearch(host string) (Result, error) { return res, nil } - // TODO this address should be resolved with upstream that was configured in dnsforward - ips, err := net.LookupIP(safeHost) + ipAddrs, err := d.resolver.LookupIPAddr(context.Background(), safeHost) if err != nil { log.Tracef("SafeSearchDomain for %s was found but failed to lookup for %s cause %s", host, safeHost, err) return Result{}, err } - for _, ip := range ips { - if ipv4 := ip.To4(); ipv4 != nil { + for _, ipAddr := range ipAddrs { + if ipv4 := ipAddr.IP.To4(); ipv4 != nil { res.Rules[0].IP = ipv4 l := d.setCacheResult(gctx.safeSearchCache, host, res) From 510573a904e0fd184d6a2ccedf9ebe7b7ed81465 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Fri, 29 Jan 2021 18:53:39 +0300 Subject: [PATCH 069/141] Pull request: 2179 ipset subdomains Merge in DNS/adguard-home from 2179-ipset-subdomains to master Closes #2179. Squashed commit of the following: commit de17caac4c2ea2bc7931f162c6dfa7822a71554f Author: Ainar Garipov Date: Fri Jan 29 18:34:46 2021 +0300 dnsforward: imp code, docs commit e5ab957560bcfba80feac4b72f9b22535ecd4c7d Author: Ainar Garipov Date: Tue Jan 26 20:43:31 2021 +0300 dnsforward: imp code commit 2b84d27b752832885e4896d0e75de2576e2b965b Author: David Sheets Date: Tue Oct 6 16:34:06 2020 +0100 dnsforward: support subdomain matching in ipset This is a squash of all commits in #2179. --- CHANGELOG.md | 4 +- go.mod | 3 + go.sum | 13 + internal/dnsforward/dnsforward.go | 13 +- internal/dnsforward/ipset.go | 142 ----------- internal/dnsforward/ipset_linux.go | 369 ++++++++++++++++++++++++++++ internal/dnsforward/ipset_others.go | 26 ++ internal/dnsforward/ipset_test.go | 41 ---- 8 files changed, 425 insertions(+), 186 deletions(-) delete mode 100644 internal/dnsforward/ipset.go create mode 100644 internal/dnsforward/ipset_linux.go create mode 100644 internal/dnsforward/ipset_others.go delete mode 100644 internal/dnsforward/ipset_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 367a9940..2328d259 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,11 +10,12 @@ and this project adheres to ## [Unreleased] ### Added +- `ipset` subdomain matching, just like `dnsmasq` does ([#2179]). - Client ID support for DNS-over-HTTPS, DNS-over-QUIC, and DNS-over-TLS ([#1383]). - `$dnsrewrite` modifier for filters ([#2102]). @@ -31,6 +32,7 @@ and this project adheres to [#1361]: https://github.com/AdguardTeam/AdGuardHome/issues/1361 [#1383]: https://github.com/AdguardTeam/AdGuardHome/issues/1383 [#2102]: https://github.com/AdguardTeam/AdGuardHome/issues/2102 +[#2179]: https://github.com/AdguardTeam/AdGuardHome/issues/2179 [#2302]: https://github.com/AdguardTeam/AdGuardHome/issues/2302 [#2304]: https://github.com/AdguardTeam/AdGuardHome/issues/2304 [#2305]: https://github.com/AdguardTeam/AdGuardHome/issues/2305 diff --git a/go.mod b/go.mod index a4f86658..aea6a6ee 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/AdguardTeam/urlfilter v0.14.2 github.com/NYTimes/gziphandler v1.1.1 github.com/ameshkov/dnscrypt/v2 v2.0.1 + github.com/digineo/go-ipset/v2 v2.2.1 github.com/fsnotify/fsnotify v1.4.9 github.com/go-ping/ping v0.0.0-20201115131931-3300c582a663 github.com/gobuffalo/envy v1.9.0 // indirect @@ -19,6 +20,7 @@ require ( github.com/karrick/godirwalk v1.16.1 // indirect github.com/lucas-clemente/quic-go v0.19.3 github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7 + github.com/mdlayher/netlink v1.1.2-0.20201013204415-ded538f7f4be github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065 github.com/miekg/dns v1.1.35 github.com/rogpeppe/go-internal v1.6.2 // indirect @@ -26,6 +28,7 @@ require ( github.com/sirupsen/logrus v1.7.0 // indirect github.com/spf13/cobra v1.1.1 // indirect github.com/stretchr/testify v1.6.1 + github.com/ti-mo/netfilter v0.4.0 github.com/u-root/u-root v7.0.0+incompatible go.etcd.io/bbolt v1.3.5 golang.org/x/crypto v0.0.0-20201217014255-9d1352758620 diff --git a/go.sum b/go.sum index b298768b..995400c9 100644 --- a/go.sum +++ b/go.sum @@ -83,6 +83,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/digineo/go-ipset/v2 v2.2.1 h1:k6skY+0fMqeUjjeWO/m5OuWPSZUAn7AucHMnQ1MX77g= +github.com/digineo/go-ipset/v2 v2.2.1/go.mod h1:wBsNzJlZlABHUITkesrggFnZQtgW5wkqw1uo8Qxe0VU= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= @@ -223,6 +225,7 @@ github.com/joomcode/errorx v1.0.3/go.mod h1:eQzdtdlNyN7etw6YCS4W4+lu442waxZYw5yv github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw= github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ= github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok= +github.com/jsimonetti/rtnetlink v0.0.0-20201110080708-d2c240429e6c h1:7cpGGTQO6+OuYQWkueqeXuErSjs1NZtpALpv1x7Mq4g= github.com/jsimonetti/rtnetlink v0.0.0-20201110080708-d2c240429e6c/go.mod h1:huN4d1phzjhlOsNIjFsw2SVRbwIHj3fJDMEU2SDPTmg= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= @@ -269,10 +272,14 @@ github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNx github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7 h1:lez6TS6aAau+8wXUP3G9I3TGlmPFEq2CTxBaRqY6AGE= github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7/go.mod h1:U6ZQobyTjI/tJyq2HG+i/dfSoFUt8/aZCM+GKtmFk/Y= +github.com/mdlayher/netlink v0.0.0-20190313131330-258ea9dff42c/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA= github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA= github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M= github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY= +github.com/mdlayher/netlink v1.1.1 h1:VqG+Voq9V4uZ+04vjIrcSCWDpf91B1xxbP4QBUmUJE8= github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o= +github.com/mdlayher/netlink v1.1.2-0.20201013204415-ded538f7f4be h1:7JeFwhE5SIdgKRd0qnqjOYJxY8AML8x/j+/qvFZ8R+c= +github.com/mdlayher/netlink v1.1.2-0.20201013204415-ded538f7f4be/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o= github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg= github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065 h1:aFkJ6lx4FPip+S+Uw4aTegFMct9shDvP+79PsSxpm3w= github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg= @@ -411,6 +418,9 @@ github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= +github.com/ti-mo/netfilter v0.2.0/go.mod h1:8GbBGsY/8fxtyIdfwy29JiluNcPK4K7wIT+x42ipqUU= +github.com/ti-mo/netfilter v0.4.0 h1:rTN1nBYULDmMfDeBHZpKuNKX/bWEXQUhe02a/10orzg= +github.com/ti-mo/netfilter v0.4.0/go.mod h1:V54q75mUx8CNA2JnFl+wv9iZ5+JP9nCcRlaFS5OZSRM= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/u-root/u-root v7.0.0+incompatible h1:u+KSS04pSxJGI5E7WE4Bs9+Zd75QjFv+REkjy/aoAc8= github.com/u-root/u-root v7.0.0+incompatible/go.mod h1:RYkpo8pTHrNjW08opNd/U6p/RJE7K0D8fXO0d47+3YY= @@ -506,6 +516,7 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgN golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201016165138-7b1cca2348c0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11 h1:lwlPPsmjDKK0J6eG6xDWd5XPehI0R024zxjDnw3esPA= @@ -540,6 +551,7 @@ golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190418153312-f0ce4c0180be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -567,6 +579,7 @@ golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201017003518-b09fb700fbb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201112073958-5cba982894dd h1:5CtCZbICpIOFdgO940moixOPjc0178IU44m4EjOO5IY= golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/internal/dnsforward/dnsforward.go b/internal/dnsforward/dnsforward.go index 0ada0640..b3449439 100644 --- a/internal/dnsforward/dnsforward.go +++ b/internal/dnsforward/dnsforward.go @@ -108,6 +108,12 @@ func (s *Server) Close() { s.stats = nil s.queryLog = nil s.dnsProxy = nil + + err := s.ipset.Close() + if err != nil { + log.Error("closing ipset: %s", err) + } + s.Unlock() } @@ -190,11 +196,14 @@ func (s *Server) Prepare(config *ServerConfig) error { // Initialize IPSET configuration // -- - s.ipset.init(s.conf.IPSETList) + err := s.ipset.init(s.conf.IPSETList) + if err != nil { + return err + } // Prepare DNS servers settings // -- - err := s.prepareUpstreamSettings() + err = s.prepareUpstreamSettings() if err != nil { return err } diff --git a/internal/dnsforward/ipset.go b/internal/dnsforward/ipset.go deleted file mode 100644 index 7421edda..00000000 --- a/internal/dnsforward/ipset.go +++ /dev/null @@ -1,142 +0,0 @@ -package dnsforward - -import ( - "net" - "strings" - "sync" - - "github.com/AdguardTeam/AdGuardHome/internal/util" - "github.com/AdguardTeam/golibs/log" - "github.com/miekg/dns" -) - -type ipsetCtx struct { - ipsetList map[string][]string // domain -> []ipset_name - ipsetCache map[[4]byte]bool // cache for IP[] to prevent duplicate calls to ipset program - ipsetMutex *sync.Mutex - ipset6Cache map[[16]byte]bool // cache for IP[] to prevent duplicate calls to ipset program - ipset6Mutex *sync.Mutex -} - -// Convert configuration settings to an internal map -// DOMAIN[,DOMAIN].../IPSET1_NAME[,IPSET2_NAME]... -func (c *ipsetCtx) init(ipsetConfig []string) { - c.ipsetList = make(map[string][]string) - c.ipsetCache = make(map[[4]byte]bool) - c.ipsetMutex = &sync.Mutex{} - c.ipset6Cache = make(map[[16]byte]bool) - c.ipset6Mutex = &sync.Mutex{} - - for _, it := range ipsetConfig { - it = strings.TrimSpace(it) - hostsAndNames := strings.Split(it, "/") - if len(hostsAndNames) != 2 { - log.Debug("IPSET: invalid value %q", it) - continue - } - - ipsetNames := strings.Split(hostsAndNames[1], ",") - if len(ipsetNames) == 0 { - log.Debug("IPSET: invalid value %q", it) - continue - } - bad := false - for i := range ipsetNames { - ipsetNames[i] = strings.TrimSpace(ipsetNames[i]) - if len(ipsetNames[i]) == 0 { - bad = true - break - } - } - if bad { - log.Debug("IPSET: invalid value %q", it) - continue - } - - hosts := strings.Split(hostsAndNames[0], ",") - for _, host := range hosts { - host = strings.TrimSpace(host) - host = strings.ToLower(host) - if len(host) == 0 { - log.Debug("IPSET: invalid value %q", it) - continue - } - c.ipsetList[host] = ipsetNames - } - } - log.Debug("IPSET: added %d hosts", len(c.ipsetList)) -} - -func (c *ipsetCtx) getIP(rr dns.RR) net.IP { - switch a := rr.(type) { - case *dns.A: - var ip4 [4]byte - copy(ip4[:], a.A.To4()) - c.ipsetMutex.Lock() - defer c.ipsetMutex.Unlock() - _, found := c.ipsetCache[ip4] - if found { - return nil // this IP was added before - } - c.ipsetCache[ip4] = false - return a.A - - case *dns.AAAA: - var ip6 [16]byte - copy(ip6[:], a.AAAA) - c.ipset6Mutex.Lock() - defer c.ipset6Mutex.Unlock() - _, found := c.ipset6Cache[ip6] - if found { - return nil // this IP was added before - } - c.ipset6Cache[ip6] = false - return a.AAAA - - default: - return nil - } -} - -// Add IP addresses of the specified in configuration domain names to an ipset list -func (c *ipsetCtx) process(ctx *dnsContext) (rc resultCode) { - req := ctx.proxyCtx.Req - if !(req.Question[0].Qtype == dns.TypeA || - req.Question[0].Qtype == dns.TypeAAAA) || - !ctx.responseFromUpstream { - return resultCodeSuccess - } - - host := req.Question[0].Name - host = strings.TrimSuffix(host, ".") - host = strings.ToLower(host) - ipsetNames, found := c.ipsetList[host] - if !found { - return resultCodeSuccess - } - - log.Debug("IPSET: found ipsets %v for host %s", ipsetNames, host) - - for _, it := range ctx.proxyCtx.Res.Answer { - ip := c.getIP(it) - if ip == nil { - continue - } - - ipStr := ip.String() - for _, name := range ipsetNames { - code, out, err := util.RunCommand("ipset", "add", name, ipStr) - if err != nil { - log.Info("IPSET: %s(%s) -> %s: %s", host, ipStr, name, err) - continue - } - if code != 0 { - log.Info("IPSET: ipset add: code:%d output:%q", code, out) - continue - } - log.Debug("IPSET: added %s(%s) -> %s", host, ipStr, name) - } - } - - return resultCodeSuccess -} diff --git a/internal/dnsforward/ipset_linux.go b/internal/dnsforward/ipset_linux.go new file mode 100644 index 00000000..b62744f7 --- /dev/null +++ b/internal/dnsforward/ipset_linux.go @@ -0,0 +1,369 @@ +// +build linux + +package dnsforward + +import ( + "fmt" + "net" + "strings" + "sync" + + "github.com/AdguardTeam/AdGuardHome/internal/agherr" + "github.com/AdguardTeam/golibs/log" + "github.com/digineo/go-ipset/v2" + "github.com/mdlayher/netlink" + "github.com/miekg/dns" + "github.com/ti-mo/netfilter" +) + +// TODO(a.garipov): Cover with unit tests as well as document how to test it +// manually. The original PR by @dsheets on Github contained an integration +// test, but unfortunately I didn't have the time to properly refactor it and +// check it in. +// +// See https://github.com/AdguardTeam/AdGuardHome/issues/2611. + +// ipsetProps contains one Linux Netfilter ipset properties. +type ipsetProps struct { + name string + family netfilter.ProtoFamily +} + +// ipsetCtx is the Linux Netfilter ipset context. +type ipsetCtx struct { + // mu protects all properties below. + mu *sync.Mutex + + nameToIpset map[string]ipsetProps + domainToIpsets map[string][]ipsetProps + + addedIPs map[[16]byte]struct{} + + ipv4Conn *ipset.Conn + ipv6Conn *ipset.Conn +} + +// dialNetfilter establishes connections to Linux's netfilter module. +func (c *ipsetCtx) dialNetfilter(config *netlink.Config) (err error) { + // The kernel API does not actually require two sockets but package + // github.com/digineo/go-ipset does. + // + // TODO(a.garipov): Perhaps we can ditch package ipset altogether and + // just use packages netfilter and netlink. + c.ipv4Conn, err = ipset.Dial(netfilter.ProtoIPv4, config) + if err != nil { + return fmt.Errorf("dialing v4: %w", err) + } + + c.ipv6Conn, err = ipset.Dial(netfilter.ProtoIPv6, config) + if err != nil { + return fmt.Errorf("dialing v6: %w", err) + } + + return nil +} + +// ipsetProps returns the properties of an ipset with the given name. +func (c *ipsetCtx) ipsetProps(name string) (set ipsetProps, err error) { + // The family doesn't seem to matter when we use a header query, so + // query only the IPv4 one. + // + // TODO(a.garipov): Find out if this is a bug or a feature. + res, err := c.ipv4Conn.Header(name) + if err != nil { + return set, err + } + + if res == nil || res.Family == nil { + return set, agherr.Error("empty response or no family data") + } + + family := netfilter.ProtoFamily(res.Family.Value) + if family != netfilter.ProtoIPv4 && family != netfilter.ProtoIPv6 { + return set, fmt.Errorf("unexpected ipset family %s", family) + } + + return ipsetProps{ + name: name, + family: family, + }, nil +} + +// ipsets returns currently known ipsets. +func (c *ipsetCtx) ipsets(names []string) (sets []ipsetProps, err error) { + for _, name := range names { + set, ok := c.nameToIpset[name] + if ok { + sets = append(sets, set) + + continue + } + + var err error + set, err = c.ipsetProps(name) + if err != nil { + return nil, fmt.Errorf("querying ipset %q: %w", name, err) + } + + c.nameToIpset[name] = set + sets = append(sets, set) + } + + return sets, nil +} + +// parseIpsetConfig parses one ipset configuration string. +func parseIpsetConfig(cfgStr string) (hosts, ipsetNames []string, err error) { + cfgStr = strings.TrimSpace(cfgStr) + hostsAndNames := strings.Split(cfgStr, "/") + if len(hostsAndNames) != 2 { + return nil, nil, fmt.Errorf("invalid value %q: expected one slash", cfgStr) + } + + hosts = strings.Split(hostsAndNames[0], ",") + ipsetNames = strings.Split(hostsAndNames[1], ",") + + if len(ipsetNames) == 0 { + log.Info("ipset: resolutions for %q will not be stored", hosts) + + return nil, nil, nil + } + + for i := range ipsetNames { + ipsetNames[i] = strings.TrimSpace(ipsetNames[i]) + if len(ipsetNames[i]) == 0 { + return nil, nil, fmt.Errorf("invalid value %q: empty ipset name", cfgStr) + } + } + + for i := range hosts { + hosts[i] = strings.TrimSpace(hosts[i]) + hosts[i] = strings.ToLower(hosts[i]) + if len(hosts[i]) == 0 { + log.Info("ipset: root catchall in %q", ipsetNames) + } + } + + return hosts, ipsetNames, nil +} + +// init initializes the ipset context. It is not safe for concurrent use. +// +// TODO(a.garipov): Rewrite into a simple constructor? +func (c *ipsetCtx) init(ipsetConfig []string) (err error) { + c.mu = &sync.Mutex{} + c.nameToIpset = make(map[string]ipsetProps) + c.domainToIpsets = make(map[string][]ipsetProps) + c.addedIPs = make(map[[16]byte]struct{}) + + err = c.dialNetfilter(&netlink.Config{}) + if err != nil { + return fmt.Errorf("ipset: dialing netfilter: %w", err) + } + + for i, cfgStr := range ipsetConfig { + var hosts, ipsetNames []string + hosts, ipsetNames, err = parseIpsetConfig(cfgStr) + if err != nil { + return fmt.Errorf("ipset: config line at index %d: %w", i, err) + } + + var ipsets []ipsetProps + ipsets, err = c.ipsets(ipsetNames) + if err != nil { + return fmt.Errorf("ipset: getting ipsets config line at index %d: %w", i, err) + } + + for _, host := range hosts { + c.domainToIpsets[host] = append(c.domainToIpsets[host], ipsets...) + } + } + + log.Debug("ipset: added %d domains for %d ipsets", len(c.domainToIpsets), len(c.nameToIpset)) + + return nil +} + +// Close closes the Linux Netfilter connections. +func (c *ipsetCtx) Close() (err error) { + var errors []error + err = c.ipv4Conn.Close() + if err != nil { + errors = append(errors, err) + } + + err = c.ipv6Conn.Close() + if err != nil { + errors = append(errors, err) + } + + if len(errors) != 0 { + return agherr.Many("closing ipsets", errors...) + } + + return nil +} + +// ipFromRR returns an IP address from a DNS resource record. +func ipFromRR(rr dns.RR) (ip net.IP) { + switch a := rr.(type) { + case *dns.A: + return a.A + case *dns.AAAA: + return a.AAAA + default: + return nil + } +} + +// lookupHost find the ipsets for the host, taking subdomain wildcards into +// account. +func (c *ipsetCtx) lookupHost(host string) (sets []ipsetProps) { + // Search for matching ipset hosts starting with most specific + // subdomain. We could use a trie here but the simple, inefficient + // solution isn't that expensive. ~75 % for 10 subdomains vs 0, but + // still sub-microsecond on a Core i7. + // + // TODO(a.garipov): Re-add benchmarks from the original PR. + for i := 0; i != -1; i++ { + host = host[i:] + sets = c.domainToIpsets[host] + if sets != nil { + return sets + } + + i = strings.Index(host, ".") + } + + // Check the root catch-all one. + return c.domainToIpsets[""] +} + +// addIPs adds the IP addresses for the host to the ipset. set must be same +// family as set's family. +func (c *ipsetCtx) addIPs(host string, set ipsetProps, ips []net.IP) (err error) { + if len(ips) == 0 { + return + } + + entries := make([]*ipset.Entry, 0, len(ips)) + for _, ip := range ips { + entries = append(entries, ipset.NewEntry(ipset.EntryIP(ip))) + } + + var conn *ipset.Conn + switch set.family { + case netfilter.ProtoIPv4: + conn = c.ipv4Conn + case netfilter.ProtoIPv6: + conn = c.ipv6Conn + default: + return fmt.Errorf("unexpected family %s for ipset %q", set.family, set.name) + } + + err = conn.Add(set.name, entries...) + if err != nil { + return fmt.Errorf("adding %q%s to ipset %q: %w", host, ips, set.name, err) + } + + log.Debug("ipset: added %s%s to ipset %s", host, ips, set.name) + + return nil +} + +// skipIpsetProcessing returns true when the ipset processing can be skipped for +// this request. +func (c *ipsetCtx) skipIpsetProcessing(ctx *dnsContext) (ok bool) { + if len(c.domainToIpsets) == 0 || ctx == nil || !ctx.responseFromUpstream { + return true + } + + req := ctx.proxyCtx.Req + if req == nil || len(req.Question) == 0 { + return true + } + + qt := req.Question[0].Qtype + return qt != dns.TypeA && qt != dns.TypeAAAA && qt != dns.TypeANY +} + +// process adds the resolved IP addresses to the domain's ipsets, if any. +func (c *ipsetCtx) process(ctx *dnsContext) (rc resultCode) { + if c == nil { + return resultCodeSuccess + } + + c.mu.Lock() + defer c.mu.Unlock() + + if c.skipIpsetProcessing(ctx) { + log.Debug("ipset: skipped processing for request") + + return resultCodeSuccess + } + + req := ctx.proxyCtx.Req + host := req.Question[0].Name + host = strings.TrimSuffix(host, ".") + host = strings.ToLower(host) + sets := c.lookupHost(host) + if len(sets) == 0 { + return resultCodeSuccess + } + + log.Debug("ipset: found ipsets %+v for host %s", sets, host) + + if ctx.proxyCtx.Res == nil { + return resultCodeSuccess + } + + ans := ctx.proxyCtx.Res.Answer + l := len(ans) + v4s := make([]net.IP, 0, l) + v6s := make([]net.IP, 0, l) + for _, rr := range ans { + ip := ipFromRR(rr) + if ip == nil { + continue + } + + var iparr [16]byte + copy(iparr[:], ip.To16()) + if _, added := c.addedIPs[iparr]; added { + continue + } + + if ip.To4() == nil { + v6s = append(v6s, ip) + + continue + } + + v4s = append(v4s, ip) + } + + var err error +setLoop: + for _, set := range sets { + switch set.family { + case netfilter.ProtoIPv4: + err = c.addIPs(host, set, v4s) + if err != nil { + break setLoop + } + case netfilter.ProtoIPv6: + err = c.addIPs(host, set, v6s) + if err != nil { + break setLoop + } + default: + err = fmt.Errorf("unexpected family %s for ipset %q", set.family, set.name) + break setLoop + } + } + if err != nil { + log.Error("ipset: adding host ips: %s", err) + } + + return resultCodeSuccess +} diff --git a/internal/dnsforward/ipset_others.go b/internal/dnsforward/ipset_others.go new file mode 100644 index 00000000..1b1c2e1b --- /dev/null +++ b/internal/dnsforward/ipset_others.go @@ -0,0 +1,26 @@ +// +build !linux + +package dnsforward + +import ( + "github.com/AdguardTeam/golibs/log" +) + +type ipsetCtx struct{} + +// init initializes the ipset context. +func (c *ipsetCtx) init(ipsetConfig []string) (err error) { + if len(ipsetConfig) != 0 { + log.Info("ipset: only available on linux") + } + + return nil +} + +// process adds the resolved IP addresses to the domain's ipsets, if any. +func (c *ipsetCtx) process(_ *dnsContext) (rc resultCode) { + return resultCodeSuccess +} + +// Close closes the Linux Netfilter connections. +func (c *ipsetCtx) Close() (_ error) { return nil } diff --git a/internal/dnsforward/ipset_test.go b/internal/dnsforward/ipset_test.go deleted file mode 100644 index f08e4d08..00000000 --- a/internal/dnsforward/ipset_test.go +++ /dev/null @@ -1,41 +0,0 @@ -package dnsforward - -import ( - "testing" - - "github.com/AdguardTeam/dnsproxy/proxy" - "github.com/miekg/dns" - "github.com/stretchr/testify/assert" -) - -func TestIPSET(t *testing.T) { - s := Server{} - s.conf.IPSETList = append(s.conf.IPSETList, "HOST.com/name") - s.conf.IPSETList = append(s.conf.IPSETList, "host2.com,host3.com/name23") - s.conf.IPSETList = append(s.conf.IPSETList, "host4.com/name4,name41") - c := ipsetCtx{} - c.init(s.conf.IPSETList) - - assert.Equal(t, "name", c.ipsetList["host.com"][0]) - assert.Equal(t, "name23", c.ipsetList["host2.com"][0]) - assert.Equal(t, "name23", c.ipsetList["host3.com"][0]) - assert.Equal(t, "name4", c.ipsetList["host4.com"][0]) - assert.Equal(t, "name41", c.ipsetList["host4.com"][1]) - - _, ok := c.ipsetList["host0.com"] - assert.False(t, ok) - - ctx := &dnsContext{ - srv: &s, - } - ctx.proxyCtx = &proxy.DNSContext{} - ctx.proxyCtx.Req = &dns.Msg{ - Question: []dns.Question{ - { - Name: "host.com.", - Qtype: dns.TypeA, - }, - }, - } - assert.Equal(t, resultCodeSuccess, c.process(ctx)) -} From 4aa6f77a611731980d21157393c66b78349bffb1 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Fri, 29 Jan 2021 20:00:11 +0300 Subject: [PATCH 070/141] Pull request: all: imp tests, docs Merge in DNS/adguard-home from imp-tests to master Squashed commit of the following: commit 15e1bd4ac38e95aa7dce716679d9a6bea43c5964 Author: Ainar Garipov Date: Fri Jan 29 19:41:03 2021 +0300 scripts: imp docs commit bc54ce4e703dd4b2956636e0bd554073c9aa12c6 Author: Ainar Garipov Date: Fri Jan 29 19:39:26 2021 +0300 all: imp tests, docs --- HACKING.md | 3 +++ scripts/README.md | 2 ++ scripts/make/go-test.sh | 10 ++++++---- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/HACKING.md b/HACKING.md index 0a4d102e..5e9e4e76 100644 --- a/HACKING.md +++ b/HACKING.md @@ -98,6 +98,9 @@ The rules are mostly sorted in the alphabetical order. * Prefer constants to variables where possible. Reduce global variables. Use [constant errors] instead of `errors.New`. + * Program code lines should not be longer than one hundred (**100**) columns. + For comments, see the text section below. + * Unused arguments in anonymous functions must be called `_`: ```go diff --git a/scripts/README.md b/scripts/README.md index 1971f5cd..020a8444 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -108,6 +108,8 @@ Optional environment: * `GO`: set an alternarive name for the Go compiler. * `RACE`: set to `0` to not use the Go race detector. The default value is `1`, use the race detector. + * `TIMEOUT_FLAGS`: set timeout flags for tests. The default value is + `--timeout 30s`. * `VERBOSE`: verbosity level. `1` shows every command that is run and every Go package that is processed. `2` also shows subcommands. The default value is `0`, don't be verbose. diff --git a/scripts/make/go-test.sh b/scripts/make/go-test.sh index 098b56d8..0654157e 100644 --- a/scripts/make/go-test.sh +++ b/scripts/make/go-test.sh @@ -32,10 +32,12 @@ else race_flags='--race' fi -go="${GO:-go}" -cover_flags='--coverprofile ./coverage.txt' -count_flags='--count 1' +readonly go="${GO:-go}" +readonly timeout_flags="${TIMEOUT_FLAGS:---timeout 30s}" +readonly cover_flags='--coverprofile ./coverage.txt' +readonly count_flags='--count 1' # Don't use quotes with flag variables because we want an empty space if # those aren't set. -"$go" test $race_flags $count_flags $cover_flags $x_flags $v_flags ./... +"$go" test $count_flags $cover_flags $race_flags $timeout_flags\ + $x_flags $v_flags ./... From cd88137333419e954ef929a918b81d2a36da577d Mon Sep 17 00:00:00 2001 From: Andrey Meshkov Date: Sun, 31 Jan 2021 00:07:09 +0300 Subject: [PATCH 071/141] Don't fail init when ipset is not initialized --- internal/dnsforward/dnsforward.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/internal/dnsforward/dnsforward.go b/internal/dnsforward/dnsforward.go index b3449439..2f8b8c50 100644 --- a/internal/dnsforward/dnsforward.go +++ b/internal/dnsforward/dnsforward.go @@ -198,7 +198,10 @@ func (s *Server) Prepare(config *ServerConfig) error { // -- err := s.ipset.init(s.conf.IPSETList) if err != nil { - return err + // ipset cannot be initialized in a Snap version (and maybe - without root) + // this needs to be handled properly + // TODO: Handle this properly + log.Info("Cannot initialize ipset module due to %v", err) } // Prepare DNS servers settings From 7042811ee6411c0278130f03d60a1eb7b92c738e Mon Sep 17 00:00:00 2001 From: Andrey Meshkov Date: Mon, 1 Feb 2021 00:14:47 +0300 Subject: [PATCH 072/141] Install AGH to /Applications on MacOS --- scripts/install.sh | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/scripts/install.sh b/scripts/install.sh index 1114c641..6ac8fa90 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -189,7 +189,13 @@ main() { SCRIPT_URL="https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/scripts/install.sh" URL="https://static.adguard.com/adguardhome/${CHANNEL}/${PKG_NAME}" - OUT_DIR=/opt + OUT_DIR="/opt" + if [ "${OS}" = "darwin" ]; then + # It may be important to install AdGuard Home to /Applications on MacOS + # Otherwise, it may not grant enough privileges to it + OUT_DIR="/Applications" + fi + AGH_DIR="${OUT_DIR}/AdGuardHome" # Root check From a32f8118b1eecc354bc2e87684af45c14aa9f88f Mon Sep 17 00:00:00 2001 From: Andrey Meshkov Date: Mon, 1 Feb 2021 00:33:17 +0300 Subject: [PATCH 073/141] Added arm64 support to the install script --- scripts/install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/install.sh b/scripts/install.sh index 6ac8fa90..cfa02759 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -97,7 +97,7 @@ detect_cpu() CPU=armv7 ;; - aarch64) + aarch64 | arm64) CPU=arm64 ;; From 3f050a750da108377b3eecf20ed748159b84e968 Mon Sep 17 00:00:00 2001 From: Andrey Meshkov Date: Mon, 1 Feb 2021 00:49:04 +0300 Subject: [PATCH 074/141] Added check for Mac M1 --- scripts/install.sh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/scripts/install.sh b/scripts/install.sh index cfa02759..ecc2303b 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -184,6 +184,13 @@ main() { OS=$(detect_os) || error_exit "Cannot detect your OS" CPU=$(detect_cpu) || error_exit "Cannot detect your CPU" + + # TODO: Remove when Mac M1 native support is added + if [ "${OS}" = "darwin" ] && [ "${CPU}" = "arm64" ]; then + CPU="amd64" + log_info "Use ${CPU} build on Mac M1 until the native ARM support is added" + fi + PKG_EXT=$(package_extension) PKG_NAME=AdGuardHome_${OS}_${CPU}.${PKG_EXT} From a83c3cf2ea7444787d4f4b93e856d97c4abf8ae0 Mon Sep 17 00:00:00 2001 From: Ildar Kamalov Date: Mon, 1 Feb 2021 12:53:19 +0300 Subject: [PATCH 075/141] Merge: - client: fix server name validation Closes #2617 Squashed commit of the following: commit be7fa5a37ab86dd0855dbb456415642d70817cbb Author: Ildar Kamalov Date: Mon Feb 1 11:19:08 2021 +0300 - client: fix server name validation --- client/src/helpers/constants.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/helpers/constants.js b/client/src/helpers/constants.js index e05ee9dc..b50ed007 100644 --- a/client/src/helpers/constants.js +++ b/client/src/helpers/constants.js @@ -13,7 +13,7 @@ export const R_MAC = /^((([a-fA-F0-9][a-fA-F0-9]+[-]){5}|([a-fA-F0-9][a-fA-F0-9] export const R_CIDR_IPV6 = /^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*(\/(12[0-8]|1[0-1][0-9]|[1-9][0-9]|[0-9]))$/; -export const R_DOMAIN = /^[a-zA-Z0-9][a-zA-Z0-9-]{1,61}[a-zA-Z0-9]\.[a-zA-Z]{2,}$/; +export const R_DOMAIN = /^([a-zA-Z0-9][a-zA-Z0-9-_]*\.)*[a-zA-Z0-9]*[a-zA-Z0-9-_]*[[a-zA-Z0-9]+$/; export const R_PATH_LAST_PART = /\/[^/]*$/; From 6f155f78b6ea7811417221b67c4f2fa6704a68ff Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Mon, 1 Feb 2021 14:12:57 +0300 Subject: [PATCH 076/141] Pull request: scripts: make sure wd is set when inistalling Merge in DNS/adguard-home from 2614-install-wd to master Updates #2614. Squashed commit of the following: commit 8e59fa29138654326a66dbf5d165fa49a5aec46e Author: Ainar Garipov Date: Mon Feb 1 13:24:22 2021 +0300 scripts: fix removing commit d199998e4bc0c2d8f1facac5fed30011cae1166e Author: Ainar Garipov Date: Mon Feb 1 13:15:51 2021 +0300 scripts: make sure wd is set when inistalling --- internal/home/service.go | 9 ++++++++- scripts/install.sh | 4 ++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/internal/home/service.go b/internal/home/service.go index aa243634..3d817446 100644 --- a/internal/home/service.go +++ b/internal/home/service.go @@ -15,6 +15,9 @@ import ( "github.com/kardianos/service" ) +// TODO(a.garipov): Move shell templates into actual files. Either during the +// v0.106.0 cycle using packr or during the following cycle using go:embed. + const ( launchdStdoutPath = "/var/log/AdGuardHome.stdout.log" launchdStderrPath = "/var/log/AdGuardHome.stderr.log" @@ -504,6 +507,10 @@ status() { } ` +// TODO(a.garipov): Don't use .WorkingDirectory here. There are currently no +// guarantees that it will actually be the required directory. +// +// See https://github.com/AdguardTeam/AdGuardHome/issues/2614. const freeBSDScript = `#!/bin/sh # PROVIDE: {{.Name}} # REQUIRE: networking @@ -514,6 +521,6 @@ name="{{.Name}}" {{.Name}}_user="root" pidfile="/var/run/${name}.pid" command="/usr/sbin/daemon" -command_args="-P ${pidfile} -r -f {{.WorkingDirectory}}/{{.Name}}" +command_args="-P ${pidfile} -f -r {{.WorkingDirectory}}/{{.Name}}" run_rc_command "$1" ` diff --git a/scripts/install.sh b/scripts/install.sh index ecc2303b..551f84fc 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -230,8 +230,8 @@ main() { unpack "${PKG_NAME}" "${OUT_DIR}" "${PKG_EXT}" || error_exit "Cannot unpack the package" - # Install AdGuard Home service and run it - ${AGH_DIR}/AdGuardHome -s install || error_exit "Cannot install AdGuardHome as a service" + # Install AdGuard Home service and run it. + ( cd "${AGH_DIR}" && ./AdGuardHome -s install || error_exit "Cannot install AdGuardHome as a service" ) rm "${PKG_NAME}" From adb76aa9b88c2383f2c80cb1934b664439cccb8b Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Mon, 1 Feb 2021 16:01:33 +0300 Subject: [PATCH 077/141] Pull request: openapi: fix naming in filter object Merge in DNS/adguard-home from fix-openapi-filter to master Squashed commit of the following: commit 12c431b656804a27d00c9e016d290a1e90bf48ef Author: Ainar Garipov Date: Mon Feb 1 15:43:33 2021 +0300 openapi: fix naming in filter object --- openapi/openapi.yaml | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index 8183faaa..5de5a607 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -1330,26 +1330,28 @@ 'required': - 'enabled' - 'id' - - 'lastUpdated' + - 'last_updated' - 'name' - - 'rulesCount' + - 'rules_count' - 'url' 'properties': 'enabled': 'type': 'boolean' 'id': - 'type': 'integer' 'example': 1234 - 'lastUpdated': - 'type': 'string' - 'format': 'date-time' - 'example': '2018-10-30T12:18:57+03:00' - 'name': - 'type': 'string' - 'example': 'AdGuard Simplified Domain Names filter' - 'rulesCount': + 'format': 'int64' 'type': 'integer' + 'last_updated': + 'example': '2018-10-30T12:18:57+03:00' + 'format': 'date-time' + 'type': 'string' + 'name': + 'example': 'AdGuard Simplified Domain Names filter' + 'type': 'string' + 'rules_count': 'example': 5912 + 'format': 'uint32' + 'type': 'integer' 'url': 'type': 'string' 'example': > From 3e0238aa99c18fe5eae1a4a122f43daf8918bd75 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Mon, 1 Feb 2021 17:06:09 +0300 Subject: [PATCH 078/141] Pull request: dnsforward: imp ipset caching, logging, and eperm handling Merge in DNS/adguard-home from ipset-fix to master Updates #2619. Squashed commit of the following: commit 6939c823598b1e74cb3d991aad1b928547fd26a9 Author: Ainar Garipov Date: Mon Feb 1 16:55:14 2021 +0300 dnsforward: imp code commit 99e3a7c30b79d7929ddd9b700d7dd3d2683ec6d2 Author: Ainar Garipov Date: Mon Feb 1 15:52:12 2021 +0300 dnsforward: imp ipset caching, logging, and eperm handling --- internal/dnsforward/dnsforward.go | 16 ++++++++++++---- internal/dnsforward/ipset_linux.go | 26 +++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/internal/dnsforward/dnsforward.go b/internal/dnsforward/dnsforward.go index 2f8b8c50..78777002 100644 --- a/internal/dnsforward/dnsforward.go +++ b/internal/dnsforward/dnsforward.go @@ -2,9 +2,11 @@ package dnsforward import ( + "errors" "fmt" "net" "net/http" + "os" "runtime" "sync" "time" @@ -198,10 +200,16 @@ func (s *Server) Prepare(config *ServerConfig) error { // -- err := s.ipset.init(s.conf.IPSETList) if err != nil { - // ipset cannot be initialized in a Snap version (and maybe - without root) - // this needs to be handled properly - // TODO: Handle this properly - log.Info("Cannot initialize ipset module due to %v", err) + if !errors.Is(err, os.ErrPermission) { + return fmt.Errorf("cannot initialize ipset: %w", err) + } + + // ipset cannot currently be initialized if the server was + // installed from Snap or when the user or the binary doesn't + // have the required permissions. + // + // Log and go on. + log.Error("cannot initialize ipset: %s", err) } // Prepare DNS servers settings diff --git a/internal/dnsforward/ipset_linux.go b/internal/dnsforward/ipset_linux.go index b62744f7..cf6a570f 100644 --- a/internal/dnsforward/ipset_linux.go +++ b/internal/dnsforward/ipset_linux.go @@ -37,6 +37,11 @@ type ipsetCtx struct { nameToIpset map[string]ipsetProps domainToIpsets map[string][]ipsetProps + // TODO(a.garipov): Currently, the ipset list is static, and we don't + // read the IPs already in sets, so we can assume that all incoming IPs + // are either added to all corresponding ipsets or not. When that stops + // being the case, for example if we add dynamic reconfiguration of + // ipsets, this map will need to become a per-ipset-name one. addedIPs map[[16]byte]struct{} ipv4Conn *ipset.Conn @@ -289,10 +294,14 @@ func (c *ipsetCtx) skipIpsetProcessing(ctx *dnsContext) (ok bool) { // process adds the resolved IP addresses to the domain's ipsets, if any. func (c *ipsetCtx) process(ctx *dnsContext) (rc resultCode) { + var err error + if c == nil { return resultCodeSuccess } + log.Debug("ipset: starting processing") + c.mu.Lock() defer c.mu.Unlock() @@ -308,6 +317,8 @@ func (c *ipsetCtx) process(ctx *dnsContext) (rc resultCode) { host = strings.ToLower(host) sets := c.lookupHost(host) if len(sets) == 0 { + log.Debug("ipset: no ipsets for host %s", host) + return resultCodeSuccess } @@ -342,7 +353,6 @@ func (c *ipsetCtx) process(ctx *dnsContext) (rc resultCode) { v4s = append(v4s, ip) } - var err error setLoop: for _, set := range sets { switch set.family { @@ -363,6 +373,20 @@ setLoop: } if err != nil { log.Error("ipset: adding host ips: %s", err) + } else { + log.Debug("ipset: processed %d new ips", len(v4s)+len(v6s)) + } + + for _, ip := range v4s { + var iparr [16]byte + copy(iparr[:], ip.To16()) + c.addedIPs[iparr] = struct{}{} + } + + for _, ip := range v6s { + var iparr [16]byte + copy(iparr[:], ip.To16()) + c.addedIPs[iparr] = struct{}{} } return resultCodeSuccess From 8fdd021ed7904a8150b3940765d4dfffff3a00d7 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Mon, 1 Feb 2021 19:33:45 +0300 Subject: [PATCH 079/141] Pull request: dnsforward: fix closing of uninitialized ipset ctx Merge in DNS/adguard-home from fix-close to master Squashed commit of the following: commit 43b55105cc669e2740ef178808c81a22c8f53cc1 Author: Ainar Garipov Date: Mon Feb 1 19:19:12 2021 +0300 dnsforward: fix closing of uninitialized ipset ctx --- internal/dnsforward/ipset_linux.go | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/internal/dnsforward/ipset_linux.go b/internal/dnsforward/ipset_linux.go index cf6a570f..e88c3986 100644 --- a/internal/dnsforward/ipset_linux.go +++ b/internal/dnsforward/ipset_linux.go @@ -192,14 +192,18 @@ func (c *ipsetCtx) init(ipsetConfig []string) (err error) { // Close closes the Linux Netfilter connections. func (c *ipsetCtx) Close() (err error) { var errors []error - err = c.ipv4Conn.Close() - if err != nil { - errors = append(errors, err) + if c.ipv4Conn != nil { + err = c.ipv4Conn.Close() + if err != nil { + errors = append(errors, err) + } } - err = c.ipv6Conn.Close() - if err != nil { - errors = append(errors, err) + if c.ipv6Conn != nil { + err = c.ipv6Conn.Close() + if err != nil { + errors = append(errors, err) + } } if len(errors) != 0 { From 39f6689b39c4f4600b8373a130b67d228b97b1e1 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Mon, 1 Feb 2021 20:44:15 +0300 Subject: [PATCH 080/141] Pull request: dnsforward: add a crutch against einval for ipset Merge in DNS/adguard-home from fix-ipset to master Squashed commit of the following: commit 18538b0276a0896ad7d99bd0edd1604af80e790f Author: Ainar Garipov Date: Mon Feb 1 20:30:13 2021 +0300 dnsforward: add a crutch against einval for ipset --- internal/dnsforward/dnsforward.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/internal/dnsforward/dnsforward.go b/internal/dnsforward/dnsforward.go index 78777002..3384f6cb 100644 --- a/internal/dnsforward/dnsforward.go +++ b/internal/dnsforward/dnsforward.go @@ -200,15 +200,19 @@ func (s *Server) Prepare(config *ServerConfig) error { // -- err := s.ipset.init(s.conf.IPSETList) if err != nil { - if !errors.Is(err, os.ErrPermission) { + if !errors.Is(err, os.ErrInvalid) && !errors.Is(err, os.ErrPermission) { return fmt.Errorf("cannot initialize ipset: %w", err) } // ipset cannot currently be initialized if the server was // installed from Snap or when the user or the binary doesn't - // have the required permissions. + // have the required permissions, or when the kernel doesn't + // support netfilter. // // Log and go on. + // + // TODO(a.garipov): The Snap problem can probably be solved if + // we add the netlink-coinnector interface plug. log.Error("cannot initialize ipset: %s", err) } From f5c47b6193bfb5c8d288ce04098435a927673a30 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Tue, 2 Feb 2021 13:35:23 +0300 Subject: [PATCH 081/141] Pull request: dnsforward: fix infinite loop Merge in DNS/adguard-home from 2619-infinite-loop to master Updates #2619. Squashed commit of the following: commit 425fde3f476349571036b9b95c9d0a33703270ee Author: Ainar Garipov Date: Tue Feb 2 13:08:52 2021 +0300 dnsforward: fix infinite loop --- internal/dnsforward/dnsforward.go | 2 +- internal/dnsforward/ipset_linux.go | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/internal/dnsforward/dnsforward.go b/internal/dnsforward/dnsforward.go index 3384f6cb..58ea3b72 100644 --- a/internal/dnsforward/dnsforward.go +++ b/internal/dnsforward/dnsforward.go @@ -212,7 +212,7 @@ func (s *Server) Prepare(config *ServerConfig) error { // Log and go on. // // TODO(a.garipov): The Snap problem can probably be solved if - // we add the netlink-coinnector interface plug. + // we add the netlink-connector interface plug. log.Error("cannot initialize ipset: %s", err) } diff --git a/internal/dnsforward/ipset_linux.go b/internal/dnsforward/ipset_linux.go index e88c3986..f74f5503 100644 --- a/internal/dnsforward/ipset_linux.go +++ b/internal/dnsforward/ipset_linux.go @@ -242,6 +242,9 @@ func (c *ipsetCtx) lookupHost(host string) (sets []ipsetProps) { } i = strings.Index(host, ".") + if i == -1 { + break + } } // Check the root catch-all one. From 28b9abf1dd9da62e540eb7a0672a7ebedc308fbd Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Tue, 2 Feb 2021 14:15:13 +0300 Subject: [PATCH 082/141] Pull request: dnsforward: don't redirect doh queries Merge in DNS/adguard-home from 2628-no-redirect to master Updates #2628. Squashed commit of the following: commit 686f3254bed1c84fe79aa203c1da4db243f301e3 Author: Ainar Garipov Date: Tue Feb 2 13:51:16 2021 +0300 dnsforward: don't redirect doh queries --- internal/dnsforward/http.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/internal/dnsforward/http.go b/internal/dnsforward/http.go index e96c6c3f..e7508bc8 100644 --- a/internal/dnsforward/http.go +++ b/internal/dnsforward/http.go @@ -529,5 +529,13 @@ func (s *Server) registerHandlers() { s.conf.HTTPRegister(http.MethodGet, "/control/access/list", s.handleAccessList) s.conf.HTTPRegister(http.MethodPost, "/control/access/set", s.handleAccessSet) + // Register both versions, with and without the trailing slash, to + // prevent a 301 Moved Permanently redirect when clients request the + // path without the trailing slash. Those redirects break some clients. + // + // See go doc net/http.ServeMux. + // + // See also https://github.com/AdguardTeam/AdGuardHome/issues/2628. + s.conf.HTTPRegister("", "/dns-query", s.handleDOH) s.conf.HTTPRegister("", "/dns-query/", s.handleDOH) } From 6fe227646b9c154059274321ca15294fe8a06a4e Mon Sep 17 00:00:00 2001 From: Eugene Burkov Date: Tue, 2 Feb 2021 15:13:12 +0300 Subject: [PATCH 083/141] Pull request: 2574 external tests vol.2 Merge in DNS/adguard-home from 2574-external-tests-2 to master Updates #2574. Squashed commit of the following: commit 8a514facc33cb727ef842fc3048f72cc12efbbf1 Merge: 41c7374f 28b9abf1 Author: Eugene Burkov Date: Tue Feb 2 14:43:55 2021 +0300 Merge branch 'master' into 2574-external-tests-2 commit 41c7374f88a292961c0217a6702e65c693691bf5 Author: Eugene Burkov Date: Tue Feb 2 14:42:33 2021 +0300 dnsfilter: imp error messages commit 1402a987ccec3f84d2e863d9dea6e47ac1c0249f Author: Eugene Burkov Date: Tue Feb 2 14:07:11 2021 +0300 dnsfilter: imp tests --- internal/dhcpd/v6.go | 1 - internal/dnsforward/dnsforward.go | 20 +- internal/dnsforward/dnsforward_test.go | 1241 ++++++++++++------------ 3 files changed, 655 insertions(+), 607 deletions(-) diff --git a/internal/dhcpd/v6.go b/internal/dhcpd/v6.go index f9dab113..51788551 100644 --- a/internal/dhcpd/v6.go +++ b/internal/dhcpd/v6.go @@ -42,7 +42,6 @@ func (s *v6Server) WriteDiskConfig6(c *V6ServerConf) { } // Return TRUE if IP address is within range [start..0xff] -// nolint(staticcheck) func ip6InRange(start, ip net.IP) bool { if len(start) != 16 { return false diff --git a/internal/dnsforward/dnsforward.go b/internal/dnsforward/dnsforward.go index 58ea3b72..c24ba62b 100644 --- a/internal/dnsforward/dnsforward.go +++ b/internal/dnsforward/dnsforward.go @@ -163,15 +163,15 @@ func (s *Server) Exchange(req *dns.Msg) (*dns.Msg, error) { return ctx.Res, nil } -// Start starts the DNS server +// Start starts the DNS server. func (s *Server) Start() error { s.Lock() defer s.Unlock() - return s.startInternal() + return s.startLocked() } -// startInternal starts without locking -func (s *Server) startInternal() error { +// startLocked starts the DNS server without locking. For internal use only. +func (s *Server) startLocked() error { err := s.dnsProxy.Start() if err == nil { s.isRunning = true @@ -256,15 +256,15 @@ func (s *Server) Prepare(config *ServerConfig) error { return nil } -// Stop stops the DNS server +// Stop stops the DNS server. func (s *Server) Stop() error { s.Lock() defer s.Unlock() - return s.stopInternal() + return s.stopLocked() } -// stopInternal stops without locking -func (s *Server) stopInternal() error { +// stopLocked stops the DNS server without locking. For internal use only. +func (s *Server) stopLocked() error { if s.dnsProxy != nil { err := s.dnsProxy.Stop() if err != nil { @@ -289,7 +289,7 @@ func (s *Server) Reconfigure(config *ServerConfig) error { defer s.Unlock() log.Print("Start reconfiguring the server") - err := s.stopInternal() + err := s.stopLocked() if err != nil { return fmt.Errorf("could not reconfigure the server: %w", err) } @@ -303,7 +303,7 @@ func (s *Server) Reconfigure(config *ServerConfig) error { return fmt.Errorf("could not reconfigure the server: %w", err) } - err = s.startInternal() + err = s.startLocked() if err != nil { return fmt.Errorf("could not reconfigure the server: %w", err) } diff --git a/internal/dnsforward/dnsforward_test.go b/internal/dnsforward/dnsforward_test.go index 17f4b872..86356c6b 100644 --- a/internal/dnsforward/dnsforward_test.go +++ b/internal/dnsforward/dnsforward_test.go @@ -1,9 +1,11 @@ package dnsforward import ( + "context" "crypto/ecdsa" "crypto/rand" "crypto/rsa" + "crypto/sha256" "crypto/tls" "crypto/x509" "crypto/x509/pkix" @@ -38,82 +40,104 @@ const ( testMessagesCount = 10 ) +func startDeferStop(t *testing.T, s *Server) { + t.Helper() + + err := s.Start() + assert.Nilf(t, err, "failed to start server: %s", err) + + t.Cleanup(func() { + err := s.Stop() + assert.Nilf(t, err, "dns server failed to stop: %s", err) + }) +} + func TestServer(t *testing.T) { s := createTestServer(t) - err := s.Start() - if err != nil { - t.Fatalf("Failed to start server: %s", err) + s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{ + &testUpstream{ + ipv4: map[string][]net.IP{ + "google-public-dns-a.google.com.": {{8, 8, 8, 8}}, + }, + }, } + startDeferStop(t, s) - // message over UDP - req := createGoogleATestMessage() - addr := s.dnsProxy.Addr(proxy.ProtoUDP) - client := dns.Client{Net: "udp"} - reply, _, err := client.Exchange(req, addr.String()) - if err != nil { - t.Fatalf("Couldn't talk to server %s: %s", addr, err) - } - assertGoogleAResponse(t, reply) + testCases := []struct { + name string + proto string + }{{ + name: "message_over_udp", + proto: proxy.ProtoUDP, + }, { + name: "message_over_tcp", + proto: proxy.ProtoTCP, + }} - // message over TCP - req = createGoogleATestMessage() - addr = s.dnsProxy.Addr("tcp") - client = dns.Client{Net: "tcp"} - reply, _, err = client.Exchange(req, addr.String()) - if err != nil { - t.Fatalf("Couldn't talk to server %s: %s", addr, err) - } - assertGoogleAResponse(t, reply) + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + addr := s.dnsProxy.Addr(tc.proto) + client := dns.Client{Net: tc.proto} - err = s.Stop() - if err != nil { - t.Fatalf("DNS server failed to stop: %s", err) + reply, _, err := client.Exchange(createGoogleATestMessage(), addr.String()) + assert.Nilf(t, err, "сouldn't talk to server %s: %s", addr, err) + + assertGoogleAResponse(t, reply) + }) } } func TestServerWithProtectionDisabled(t *testing.T) { s := createTestServer(t) s.conf.ProtectionEnabled = false - err := s.Start() - if err != nil { - t.Fatalf("Failed to start server: %s", err) + s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{ + &testUpstream{ + ipv4: map[string][]net.IP{ + "google-public-dns-a.google.com.": {{8, 8, 8, 8}}, + }, + }, } + startDeferStop(t, s) - // message over UDP + // Message over UDP. req := createGoogleATestMessage() addr := s.dnsProxy.Addr(proxy.ProtoUDP) - client := dns.Client{Net: "udp"} + client := dns.Client{Net: proxy.ProtoUDP} reply, _, err := client.Exchange(req, addr.String()) - if err != nil { - t.Fatalf("Couldn't talk to server %s: %s", addr, err) - } + assert.Nilf(t, err, "сouldn't talk to server %s: %s", addr, err) assertGoogleAResponse(t, reply) - - err = s.Stop() - if err != nil { - t.Fatalf("DNS server failed to stop: %s", err) - } } -func TestDotServer(t *testing.T) { - // Prepare the proxy server - _, certPem, keyPem := createServerTLSConfig(t) - s := createTestServer(t) +func createTestTLS(t *testing.T, tlsConf TLSConfig) (s *Server, certPem []byte) { + t.Helper() - s.conf.TLSConfig = TLSConfig{ - TLSListenAddr: &net.TCPAddr{Port: 0}, - CertificateChainData: certPem, - PrivateKeyData: keyPem, + var keyPem []byte + _, certPem, keyPem = createServerTLSConfig(t) + s = createTestServer(t) + + tlsConf.CertificateChainData, tlsConf.PrivateKeyData = certPem, keyPem + s.conf.TLSConfig = tlsConf + + err := s.Prepare(nil) + assert.Nilf(t, err, "failed to prepare server: %s", err) + + return s, certPem +} + +func TestDoTServer(t *testing.T) { + s, certPem := createTestTLS(t, TLSConfig{ + TLSListenAddr: &net.TCPAddr{Port: 0}, + }) + s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{ + &testUpstream{ + ipv4: map[string][]net.IP{ + "google-public-dns-a.google.com.": {{8, 8, 8, 8}}, + }, + }, } + startDeferStop(t, s) - _ = s.Prepare(nil) - // Starting the server - err := s.Start() - if err != nil { - t.Fatalf("Failed to start server: %s", err) - } - - // Add our self-signed generated config to roots + // Add our self-signed generated config to roots. roots := x509.NewCertPool() roots.AppendCertsFromPEM(certPem) tlsConfig := &tls.Config{ @@ -122,275 +146,259 @@ func TestDotServer(t *testing.T) { MinVersion: tls.VersionTLS12, } - // Create a DNS-over-TLS client connection + // Create a DNS-over-TLS client connection. addr := s.dnsProxy.Addr(proxy.ProtoTLS) conn, err := dns.DialWithTLS("tcp-tls", addr.String(), tlsConfig) - if err != nil { - t.Fatalf("cannot connect to the proxy: %s", err) - } + assert.Nilf(t, err, "cannot connect to the proxy: %s", err) sendTestMessages(t, conn) - - // Stop the proxy - err = s.Stop() - if err != nil { - t.Fatalf("DNS server failed to stop: %s", err) - } } -func TestDoqServer(t *testing.T) { - // Prepare the proxy server - _, certPem, keyPem := createServerTLSConfig(t) - s := createTestServer(t) - - s.conf.TLSConfig = TLSConfig{ - QUICListenAddr: &net.UDPAddr{Port: 0}, - CertificateChainData: certPem, - PrivateKeyData: keyPem, +func TestDoQServer(t *testing.T) { + s, _ := createTestTLS(t, TLSConfig{ + QUICListenAddr: &net.UDPAddr{Port: 0}, + }) + s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{ + &testUpstream{ + ipv4: map[string][]net.IP{ + "google-public-dns-a.google.com.": {{8, 8, 8, 8}}, + }, + }, } + startDeferStop(t, s) - _ = s.Prepare(nil) - // Starting the server - err := s.Start() - assert.Nil(t, err) - - // Create a DNS-over-QUIC upstream + // Create a DNS-over-QUIC upstream. addr := s.dnsProxy.Addr(proxy.ProtoQUIC) opts := upstream.Options{InsecureSkipVerify: true} - u, err := upstream.AddressToUpstream(fmt.Sprintf("quic://%s", addr), opts) + u, err := upstream.AddressToUpstream(fmt.Sprintf("%s://%s", proxy.ProtoQUIC, addr), opts) assert.Nil(t, err) - // Send the test message + // Send the test message. req := createGoogleATestMessage() res, err := u.Exchange(req) assert.Nil(t, err) - assertGoogleAResponse(t, res) - // Stop the proxy - err = s.Stop() - if err != nil { - t.Fatalf("DNS server failed to stop: %s", err) - } + assertGoogleAResponse(t, res) } func TestServerRace(t *testing.T) { - s := createTestServer(t) - err := s.Start() - if err != nil { - t.Fatalf("Failed to start server: %s", err) - } + t.Skip("TODO(e.burkov): inspect the golibs/cache package for locks") - // message over UDP - addr := s.dnsProxy.Addr(proxy.ProtoUDP) - conn, err := dns.Dial("udp", addr.String()) - if err != nil { - t.Fatalf("cannot connect to the proxy: %s", err) + s := createTestServer(t) + s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{ + &testUpstream{ + ipv4: map[string][]net.IP{ + "google-public-dns-a.google.com.": {{8, 8, 8, 8}}, + }, + }, } + startDeferStop(t, s) + + // Message over UDP. + addr := s.dnsProxy.Addr(proxy.ProtoUDP) + conn, err := dns.Dial(proxy.ProtoUDP, addr.String()) + assert.Nilf(t, err, "cannot connect to the proxy: %s", err) sendTestMessagesAsync(t, conn) +} - // Stop the proxy - err = s.Stop() - if err != nil { - t.Fatalf("DNS server failed to stop: %s", err) +// testResolver is a Resolver for tests. +// +//lint:ignore U1000 TODO(e.burkov): move into aghtest package. +type testResolver struct{} + +// LookupIPAddr implements Resolver interface for *testResolver. +// +//lint:ignore U1000 TODO(e.burkov): move into aghtest package. +func (r *testResolver) LookupIPAddr(_ context.Context, host string) (ips []net.IPAddr, err error) { + hash := sha256.Sum256([]byte(host)) + addrs := []net.IPAddr{{ + IP: net.IP(hash[:4]), + Zone: "somezone", + }, { + IP: net.IP(hash[4:20]), + Zone: "somezone", + }} + return addrs, nil +} + +// LookupHost implements Resolver interface for *testResolver. +// +//lint:ignore U1000 TODO(e.burkov): move into aghtest package. +func (r *testResolver) LookupHost(host string) (addrs []string, err error) { + hash := sha256.Sum256([]byte(host)) + addrs = []string{ + net.IP(hash[:4]).String(), + net.IP(hash[4:20]).String(), } + return addrs, nil } func TestSafeSearch(t *testing.T) { - s := createTestServer(t) - err := s.Start() - if err != nil { - t.Fatalf("Failed to start server: %s", err) + t.Skip("TODO(e.burkov): substitute the dnsfilter by one with custom resolver from aghtest") + + testUpstreamIP := net.IP{213, 180, 193, 56} + testCases := []string{ + "yandex.com.", + "yandex.by.", + "yandex.kz.", + "yandex.ru.", + "www.google.com.", + "www.google.com.af.", + "www.google.be.", + "www.google.by.", } - // Test safe search for yandex. We already know safe search ip - addr := s.dnsProxy.Addr(proxy.ProtoUDP) - client := dns.Client{Net: "udp"} - yandexDomains := []string{"yandex.com.", "yandex.by.", "yandex.kz.", "yandex.ru.", "yandex.com."} - for _, host := range yandexDomains { - exchangeAndAssertResponse(t, &client, addr, host, "213.180.193.56") - } + for _, tc := range testCases { + t.Run("safe_search_"+tc, func(t *testing.T) { + s := createTestServer(t) + startDeferStop(t, s) - // Let's lookup for google safesearch ip - ips, err := net.LookupIP("forcesafesearch.google.com") - if err != nil { - t.Fatalf("Failed to lookup for forcesafesearch.google.com: %s", err) - } + addr := s.dnsProxy.Addr(proxy.ProtoUDP) + client := dns.Client{Net: proxy.ProtoUDP} - ip := ips[0] - for _, i := range ips { - if i.To4() != nil { - ip = i - break - } - } - - // Test safe search for google. - googleDomains := []string{"www.google.com.", "www.google.com.af.", "www.google.be.", "www.google.by."} - for _, host := range googleDomains { - exchangeAndAssertResponse(t, &client, addr, host, ip.String()) - } - - err = s.Stop() - if err != nil { - t.Fatalf("Can not stopd server cause: %s", err) + exchangeAndAssertResponse(t, &client, addr, tc, testUpstreamIP) + }) } } func TestInvalidRequest(t *testing.T) { s := createTestServer(t) - err := s.Start() - if err != nil { - t.Fatalf("Failed to start server: %s", err) + startDeferStop(t, s) + + addr := s.dnsProxy.Addr(proxy.ProtoUDP).String() + req := dns.Msg{ + MsgHdr: dns.MsgHdr{ + Id: dns.Id(), + RecursionDesired: true, + }, } - // server is running, send a message - addr := s.dnsProxy.Addr(proxy.ProtoUDP) - req := dns.Msg{} - req.Id = dns.Id() - req.RecursionDesired = true + // Send a DNS request without question. + _, _, err := (&dns.Client{ + Net: proxy.ProtoUDP, + Timeout: 500 * time.Millisecond, + }).Exchange(&req, addr) - // send a DNS request without question - client := dns.Client{Net: "udp", Timeout: 500 * time.Millisecond} - _, _, err = client.Exchange(&req, addr.String()) - if err != nil { - t.Fatalf("got a response to an invalid query") - } - - err = s.Stop() - if err != nil { - t.Fatalf("DNS server failed to stop: %s", err) - } + assert.Nil(t, err, "got a response to an invalid query") } func TestBlockedRequest(t *testing.T) { s := createTestServer(t) - err := s.Start() - if err != nil { - t.Fatalf("Failed to start server: %s", err) - } + startDeferStop(t, s) + addr := s.dnsProxy.Addr(proxy.ProtoUDP) - // - // Default blocking - NULL IP - // - req := dns.Msg{} - req.Id = dns.Id() - req.RecursionDesired = true - req.Question = []dns.Question{ - {Name: "nxdomain.example.org.", Qtype: dns.TypeA, Qclass: dns.ClassINET}, - } + // Default blocking. + req := createTestMessage("nxdomain.example.org.") - reply, err := dns.Exchange(&req, addr.String()) - if err != nil { - t.Fatalf("Couldn't talk to server %s: %s", addr, err) - } + reply, err := dns.Exchange(req, addr.String()) + assert.Nilf(t, err, "couldn't talk to server %s: %s", addr, err) assert.Equal(t, dns.RcodeSuccess, reply.Rcode) - assert.True(t, reply.Answer[0].(*dns.A).A.Equal(net.IP{0, 0, 0, 0})) - - err = s.Stop() - if err != nil { - t.Fatalf("DNS server failed to stop: %s", err) - } + assert.True(t, reply.Answer[0].(*dns.A).A.IsUnspecified()) } func TestServerCustomClientUpstream(t *testing.T) { s := createTestServer(t) s.conf.GetCustomUpstreamByClient = func(_ string) *proxy.UpstreamConfig { - uc := &proxy.UpstreamConfig{} - u := &testUpstream{} - u.ipv4 = map[string][]net.IP{} - u.ipv4["host."] = []net.IP{{192, 168, 0, 1}} - uc.Upstreams = append(uc.Upstreams, u) - return uc + return &proxy.UpstreamConfig{ + Upstreams: []upstream.Upstream{ + &testUpstream{ + ipv4: map[string][]net.IP{ + "host.": {{192, 168, 0, 1}}, + }, + }, + }, + } } - - assert.Nil(t, s.Start()) + startDeferStop(t, s) addr := s.dnsProxy.Addr(proxy.ProtoUDP) - // Send test request - req := dns.Msg{} - req.Id = dns.Id() - req.RecursionDesired = true - req.Question = []dns.Question{ - {Name: "host.", Qtype: dns.TypeA, Qclass: dns.ClassINET}, - } + // Send test request. + req := createTestMessage("host.") - reply, err := dns.Exchange(&req, addr.String()) + reply, err := dns.Exchange(req, addr.String()) assert.Nil(t, err) assert.Equal(t, dns.RcodeSuccess, reply.Rcode) - assert.NotNil(t, reply.Answer) - assert.True(t, net.IP{192, 168, 0, 1}.Equal(reply.Answer[0].(*dns.A).A)) - assert.Nil(t, s.Stop()) + assert.NotEmpty(t, reply.Answer) + + assert.Equal(t, net.IP{192, 168, 0, 1}, reply.Answer[0].(*dns.A).A) } -// testUpstream is a mock of real upstream. -// specify fields with necessary values to simulate real upstream behaviour +// testUpstream is a mock of real upstream. specify fields with necessary values +// to simulate real upstream behaviour. +// +// TODO(e.burkov): move into aghtest package. type testUpstream struct { - cn map[string]string // Map of [name]canonical_name - ipv4 map[string][]net.IP // Map of [name]IPv4 - ipv6 map[string][]net.IP // Map of [name]IPv6 + // cn is a map of hostname to canonical name. + cn map[string]string + // ipv4 is a map of hostname to IPv4. + ipv4 map[string][]net.IP + // ipv6 is a map of hostname to IPv6. + ipv6 map[string][]net.IP } +// Exchange implements upstream.Upstream interface for *testUpstream. func (u *testUpstream) Exchange(m *dns.Msg) (*dns.Msg, error) { - resp := dns.Msg{} + resp := &dns.Msg{} resp.SetReply(m) - hasARecord := false - hasAAAARecord := false + hasRec := false - reqType := m.Question[0].Qtype name := m.Question[0].Name - // Let's check if we have any CNAME for given name if cname, ok := u.cn[name]; ok { - cn := dns.CNAME{} - cn.Hdr.Name = name - cn.Hdr.Rrtype = dns.TypeCNAME - cn.Target = cname - resp.Answer = append(resp.Answer, &cn) + resp.Answer = append(resp.Answer, &dns.CNAME{ + Hdr: dns.RR_Header{ + Name: name, + Rrtype: dns.TypeCNAME, + }, + Target: cname, + }) } - // Let's check if we can add some A records to the answer - if ipv4addr, ok := u.ipv4[name]; ok && reqType == dns.TypeA { - hasARecord = true - for _, ipv4 := range ipv4addr { - respA := dns.A{} - respA.Hdr.Rrtype = dns.TypeA - respA.Hdr.Name = name - respA.A = ipv4 - resp.Answer = append(resp.Answer, &respA) + var rrtype uint16 + var a []net.IP + switch m.Question[0].Qtype { + case dns.TypeA: + rrtype = dns.TypeA + if ipv4addr, ok := u.ipv4[name]; ok { + hasRec = true + a = ipv4addr + } + case dns.TypeAAAA: + rrtype = dns.TypeAAAA + if ipv6addr, ok := u.ipv6[name]; ok { + hasRec = true + a = ipv6addr } } - - // Let's check if we can add some AAAA records to the answer - if u.ipv6 != nil { - if ipv6addr, ok := u.ipv6[name]; ok && reqType == dns.TypeAAAA { - hasAAAARecord = true - for _, ipv6 := range ipv6addr { - respAAAA := dns.A{} - respAAAA.Hdr.Rrtype = dns.TypeAAAA - respAAAA.Hdr.Name = name - respAAAA.A = ipv6 - resp.Answer = append(resp.Answer, &respAAAA) - } - } + for _, ip := range a { + resp.Answer = append(resp.Answer, &dns.A{ + Hdr: dns.RR_Header{ + Name: name, + Rrtype: rrtype, + }, + A: ip, + }) } if len(resp.Answer) == 0 { - if hasARecord || hasAAAARecord { - // Set No Error RCode if there are some records for given Qname but we didn't apply them + if hasRec { + // Set no error RCode if there are some records for + // given Qname but we didn't apply them. resp.SetRcode(m, dns.RcodeSuccess) - } else { - // Set NXDomain RCode otherwise - resp.SetRcode(m, dns.RcodeNameError) + return resp, nil } + // Set NXDomain RCode otherwise. + resp.SetRcode(m, dns.RcodeNameError) } - return &resp, nil + return resp, nil } +// Address implements upstream.Upstream interface for *testUpstream. func (u *testUpstream) Address() string { return "test" } @@ -422,14 +430,18 @@ var testIPv4 = map[string][]net.IP{ func TestBlockCNAMEProtectionEnabled(t *testing.T) { s := createTestServer(t) - testUpstm := &testUpstream{testCNAMEs, testIPv4, nil} + testUpstm := &testUpstream{ + cn: testCNAMEs, + ipv4: testIPv4, + ipv6: nil, + } s.conf.ProtectionEnabled = false err := s.startWithUpstream(testUpstm) assert.Nil(t, err) addr := s.dnsProxy.Addr(proxy.ProtoUDP) - // 'badhost' has a canonical name 'null.example.org' which is blocked by filters: - // but protection is disabled - response is NOT blocked + // 'badhost' has a canonical name 'null.example.org' which is blocked by + // filters: but protection is disabled so response is _not_ blocked. req := createTestMessage("badhost.") reply, err := dns.Exchange(req, addr.String()) assert.Nil(t, err) @@ -438,57 +450,81 @@ func TestBlockCNAMEProtectionEnabled(t *testing.T) { func TestBlockCNAME(t *testing.T) { s := createTestServer(t) - testUpstm := &testUpstream{testCNAMEs, testIPv4, nil} - err := s.startWithUpstream(testUpstm) - assert.Nil(t, err) - addr := s.dnsProxy.Addr(proxy.ProtoUDP) + s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{ + &testUpstream{ + cn: testCNAMEs, + ipv4: testIPv4, + }, + } + startDeferStop(t, s) - // 'badhost' has a canonical name 'null.example.org' which is blocked by filters: - // response is blocked - req := createTestMessage("badhost.") - reply, err := dns.Exchange(req, addr.String()) - assert.Nil(t, err) - assert.Equal(t, dns.RcodeSuccess, reply.Rcode) - assert.True(t, reply.Answer[0].(*dns.A).A.Equal(net.IP{0, 0, 0, 0})) + addr := s.dnsProxy.Addr(proxy.ProtoUDP).String() - // 'whitelist.example.org' has a canonical name 'null.example.org' which is blocked by filters - // but 'whitelist.example.org' is in a whitelist: - // response isn't blocked - req = createTestMessage("whitelist.example.org.") - reply, err = dns.Exchange(req, addr.String()) - assert.Nil(t, err) - assert.Equal(t, dns.RcodeSuccess, reply.Rcode) + testCases := []struct { + host string + want bool + }{{ + host: "badhost.", + // 'badhost' has a canonical name 'null.example.org' which is + // blocked by filters: response is blocked. + want: true, + }, { + host: "whitelist.example.org.", + // 'whitelist.example.org' has a canonical name + // 'null.example.org' which is blocked by filters + // but 'whitelist.example.org' is in a whitelist: + // response isn't blocked. + want: false, + }, { + host: "example.org.", + // 'example.org' has a canonical name 'cname1' with IP + // 127.0.0.255 which is blocked by filters: response is blocked. + want: true, + }} - // 'example.org' has a canonical name 'cname1' with IP 127.0.0.255 which is blocked by filters: - // response is blocked - req = createTestMessage("example.org.") - reply, err = dns.Exchange(req, addr.String()) - assert.Nil(t, err) - assert.Equal(t, dns.RcodeSuccess, reply.Rcode) - assert.True(t, reply.Answer[0].(*dns.A).A.Equal(net.IP{0, 0, 0, 0})) - - _ = s.Stop() + for _, tc := range testCases { + t.Run("block_cname_"+tc.host, func(t *testing.T) { + req := createTestMessage(tc.host) + reply, err := dns.Exchange(req, addr) + assert.Nil(t, err) + assert.Equal(t, dns.RcodeSuccess, reply.Rcode) + if tc.want { + assert.True(t, reply.Answer[0].(*dns.A).A.IsUnspecified()) + } + }) + } } func TestClientRulesForCNAMEMatching(t *testing.T) { s := createTestServer(t) - testUpstm := &testUpstream{testCNAMEs, testIPv4, nil} s.conf.FilterHandler = func(_ net.IP, _ string, settings *dnsfilter.RequestFilteringSettings) { settings.FilteringEnabled = false } - err := s.startWithUpstream(testUpstm) - assert.Nil(t, err) + s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{ + &testUpstream{ + cn: testCNAMEs, + ipv4: testIPv4, + }, + } + startDeferStop(t, s) + addr := s.dnsProxy.Addr(proxy.ProtoUDP) - // 'badhost' has a canonical name 'null.example.org' which is blocked by filters: - // response is blocked - req := dns.Msg{} - req.Id = dns.Id() - req.Question = []dns.Question{ - {Name: "badhost.", Qtype: dns.TypeA, Qclass: dns.ClassINET}, + // 'badhost' has a canonical name 'null.example.org' which is blocked by + // filters: response is blocked. + req := dns.Msg{ + MsgHdr: dns.MsgHdr{ + Id: dns.Id(), + }, + Question: []dns.Question{{ + Name: "badhost.", + Qtype: dns.TypeA, + Qclass: dns.ClassINET, + }}, } - // However, in our case it should not be blocked - // as filtering is disabled on the client level + + // However, in our case it should not be blocked as filtering is + // disabled on the client level. reply, err := dns.Exchange(&req, addr.String()) assert.Nil(t, err) assert.Equal(t, dns.RcodeSuccess, reply.Rcode) @@ -497,68 +533,58 @@ func TestClientRulesForCNAMEMatching(t *testing.T) { func TestNullBlockedRequest(t *testing.T) { s := createTestServer(t) s.conf.FilteringConfig.BlockingMode = "null_ip" - err := s.Start() - if err != nil { - t.Fatalf("Failed to start server: %s", err) - } + startDeferStop(t, s) addr := s.dnsProxy.Addr(proxy.ProtoUDP) - // - // Null filter blocking - // - req := dns.Msg{} - req.Id = dns.Id() - req.RecursionDesired = true - req.Question = []dns.Question{ - {Name: "null.example.org.", Qtype: dns.TypeA, Qclass: dns.ClassINET}, + // Nil filter blocking. + req := dns.Msg{ + MsgHdr: dns.MsgHdr{ + Id: dns.Id(), + RecursionDesired: true, + }, + Question: []dns.Question{{ + Name: "null.example.org.", + Qtype: dns.TypeA, + Qclass: dns.ClassINET, + }}, } reply, err := dns.Exchange(&req, addr.String()) - if err != nil { - t.Fatalf("Couldn't talk to server %s: %s", addr, err) - } - if len(reply.Answer) != 1 { - t.Fatalf("DNS server %s returned reply with wrong number of answers - %d", addr, len(reply.Answer)) - } - if a, ok := reply.Answer[0].(*dns.A); ok { - if !net.IPv4zero.Equal(a.A) { - t.Fatalf("DNS server %s returned wrong answer instead of 0.0.0.0: %v", addr, a.A) - } - } else { - t.Fatalf("DNS server %s returned wrong answer type instead of A: %v", addr, reply.Answer[0]) - } - - err = s.Stop() - if err != nil { - t.Fatalf("DNS server failed to stop: %s", err) - } + assert.Nilf(t, err, "couldn't talk to server %s: %s", addr, err) + assert.Lenf(t, reply.Answer, 1, "dns server %s returned reply with wrong number of answers - %d", addr, len(reply.Answer)) + a, ok := reply.Answer[0].(*dns.A) + assert.Truef(t, ok, "dns server %s returned wrong answer type instead of A: %v", addr, reply.Answer[0]) + assert.Truef(t, a.A.IsUnspecified(), "dns server %s returned wrong answer instead of 0.0.0.0: %v", addr, a.A) } func TestBlockedCustomIP(t *testing.T) { rules := "||nxdomain.example.org^\n||null.example.org^\n127.0.0.1 host.example.org\n@@||whitelist.example.org^\n||127.0.0.255\n" filters := []dnsfilter.Filter{{ - ID: 0, Data: []byte(rules), + ID: 0, + Data: []byte(rules), }} - c := dnsfilter.Config{} - f := dnsfilter.New(&c, filters) - s := NewServer(DNSCreateParams{DNSFilter: f}) - conf := ServerConfig{} - conf.UDPListenAddr = &net.UDPAddr{Port: 0} - conf.TCPListenAddr = &net.TCPAddr{Port: 0} - conf.ProtectionEnabled = true - conf.BlockingMode = "custom_ip" - conf.BlockingIPv4 = nil - conf.UpstreamDNS = []string{"8.8.8.8:53", "8.8.4.4:53"} - err := s.Prepare(&conf) - assert.NotNil(t, err) // invalid BlockingIPv4 + s := NewServer(DNSCreateParams{ + DNSFilter: dnsfilter.New(&dnsfilter.Config{}, filters), + }) + conf := ServerConfig{ + UDPListenAddr: &net.UDPAddr{Port: 0}, + TCPListenAddr: &net.TCPAddr{Port: 0}, + FilteringConfig: FilteringConfig{ + ProtectionEnabled: true, + BlockingMode: "custom_ip", + BlockingIPv4: nil, + UpstreamDNS: []string{"8.8.8.8:53", "8.8.4.4:53"}, + }, + } + // Invalid BlockingIPv4. + assert.NotNil(t, s.Prepare(&conf)) conf.BlockingIPv4 = net.IP{0, 0, 0, 1} conf.BlockingIPv6 = net.ParseIP("::1") - err = s.Prepare(&conf) - assert.Nil(t, err) - err = s.Start() - assert.Nil(t, err) + assert.Nil(t, s.Prepare(&conf)) + + startDeferStop(t, s) addr := s.dnsProxy.Addr(proxy.ProtoUDP) @@ -577,134 +603,102 @@ func TestBlockedCustomIP(t *testing.T) { a6, ok := reply.Answer[0].(*dns.AAAA) assert.True(t, ok) assert.Equal(t, "::1", a6.AAAA.String()) - - err = s.Stop() - if err != nil { - t.Fatalf("DNS server failed to stop: %s", err) - } } func TestBlockedByHosts(t *testing.T) { s := createTestServer(t) - err := s.Start() - if err != nil { - t.Fatalf("Failed to start server: %s", err) - } + startDeferStop(t, s) addr := s.dnsProxy.Addr(proxy.ProtoUDP) - // - // Hosts blocking - // - req := dns.Msg{} - req.Id = dns.Id() - req.RecursionDesired = true - req.Question = []dns.Question{ - {Name: "host.example.org.", Qtype: dns.TypeA, Qclass: dns.ClassINET}, - } + // Hosts blocking. + req := createTestMessage("host.example.org.") - reply, err := dns.Exchange(&req, addr.String()) - if err != nil { - t.Fatalf("Couldn't talk to server %s: %s", addr, err) - } - if len(reply.Answer) != 1 { - t.Fatalf("DNS server %s returned reply with wrong number of answers - %d", addr, len(reply.Answer)) - } - if a, ok := reply.Answer[0].(*dns.A); ok { - if !net.IPv4(127, 0, 0, 1).Equal(a.A) { - t.Fatalf("DNS server %s returned wrong answer instead of 8.8.8.8: %v", addr, a.A) - } - } else { - t.Fatalf("DNS server %s returned wrong answer type instead of A: %v", addr, reply.Answer[0]) - } + reply, err := dns.Exchange(req, addr.String()) + assert.Nilf(t, err, "couldn't talk to server %s: %s", addr, err) + assert.Lenf(t, reply.Answer, 1, "dns server %s returned reply with wrong number of answers - %d", addr, len(reply.Answer)) - err = s.Stop() - if err != nil { - t.Fatalf("DNS server failed to stop: %s", err) - } + a, ok := reply.Answer[0].(*dns.A) + assert.Truef(t, ok, "dns server %s returned wrong answer type instead of A: %v", addr, reply.Answer[0]) + assert.Equalf(t, net.IP{127, 0, 0, 1}, a.A, "dns server %s returned wrong answer instead of 8.8.8.8: %v", addr, a.A) } func TestBlockedBySafeBrowsing(t *testing.T) { + t.Skip("TODO(e.burkov): substitute the dnsfilter by one with custom safeBrowsingUpstream") + resolver := &testResolver{} + ips, _ := resolver.LookupIPAddr(context.Background(), safeBrowsingBlockHost) + addrs, _ := resolver.LookupHost(safeBrowsingBlockHost) + s := createTestServer(t) - err := s.Start() - if err != nil { - t.Fatalf("Failed to start server: %s", err) + s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{ + &testUpstream{ + ipv4: map[string][]net.IP{ + "wmconvirus.narod.ru.": {ips[0].IP}, + }, + }, } + startDeferStop(t, s) addr := s.dnsProxy.Addr(proxy.ProtoUDP) - // - // Safebrowsing blocking - // - req := dns.Msg{} - req.Id = dns.Id() - req.RecursionDesired = true - req.Question = []dns.Question{ - {Name: "wmconvirus.narod.ru.", Qtype: dns.TypeA, Qclass: dns.ClassINET}, - } - reply, err := dns.Exchange(&req, addr.String()) - if err != nil { - t.Fatalf("Couldn't talk to server %s: %s", addr, err) - } - if len(reply.Answer) != 1 { - t.Fatalf("DNS server %s returned reply with wrong number of answers - %d", addr, len(reply.Answer)) - } - if a, ok := reply.Answer[0].(*dns.A); ok { - addrs, lookupErr := net.LookupHost(safeBrowsingBlockHost) - if lookupErr != nil { - t.Fatalf("cannot resolve %s due to %s", safeBrowsingBlockHost, lookupErr) - } + // SafeBrowsing blocking. + req := createTestMessage("wmconvirus.narod.ru.") + reply, err := dns.Exchange(req, addr.String()) + assert.Nilf(t, err, "couldn't talk to server %s: %s", addr, err) + assert.Lenf(t, reply.Answer, 1, "dns server %s returned reply with wrong number of answers - %d", addr, len(reply.Answer)) + + a, ok := reply.Answer[0].(*dns.A) + if assert.Truef(t, ok, "dns server %s returned wrong answer type instead of A: %v", addr, reply.Answer[0]) { found := false for _, blockAddr := range addrs { if blockAddr == a.A.String() { found = true + break } } - - if !found { - t.Fatalf("DNS server %s returned wrong answer: %v", addr, a.A) - } - } else { - t.Fatalf("DNS server %s returned wrong answer type instead of A: %v", addr, reply.Answer[0]) - } - - err = s.Stop() - if err != nil { - t.Fatalf("DNS server failed to stop: %s", err) + assert.Truef(t, found, "dns server %s returned wrong answer: %v", addr, a.A) } } func TestRewrite(t *testing.T) { - c := dnsfilter.Config{} - c.Rewrites = []dnsfilter.RewriteEntry{ - { + c := &dnsfilter.Config{ + Rewrites: []dnsfilter.RewriteEntry{{ Domain: "test.com", Answer: "1.2.3.4", Type: dns.TypeA, - }, - { + }, { Domain: "alias.test.com", Answer: "test.com", Type: dns.TypeCNAME, - }, - { + }, { Domain: "my.alias.example.org", Answer: "example.org", Type: dns.TypeCNAME, + }}, + } + f := dnsfilter.New(c, nil) + + s := NewServer(DNSCreateParams{DNSFilter: f}) + err := s.Prepare(&ServerConfig{ + UDPListenAddr: &net.UDPAddr{Port: 0}, + TCPListenAddr: &net.TCPAddr{Port: 0}, + FilteringConfig: FilteringConfig{ + ProtectionEnabled: true, + UpstreamDNS: []string{"8.8.8.8:53"}, + }, + }) + s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{ + &testUpstream{ + cn: map[string]string{ + "example.org": "somename", + }, + ipv4: map[string][]net.IP{ + "example.org.": {{4, 3, 2, 1}}, + }, }, } - - f := dnsfilter.New(&c, nil) - s := NewServer(DNSCreateParams{DNSFilter: f}) - conf := ServerConfig{} - conf.UDPListenAddr = &net.UDPAddr{Port: 0} - conf.TCPListenAddr = &net.TCPAddr{Port: 0} - conf.ProtectionEnabled = true - conf.UpstreamDNS = []string{"8.8.8.8:53"} - - err := s.Prepare(&conf) - assert.Nil(t, err) - err = s.Start() assert.Nil(t, err) + startDeferStop(t, s) + addr := s.dnsProxy.Addr(proxy.ProtoUDP) req := createTestMessageWithType("test.com.", dns.TypeA) @@ -734,8 +728,6 @@ func TestRewrite(t *testing.T) { assert.Len(t, reply.Answer, 2) assert.Equal(t, "example.org.", reply.Answer[0].(*dns.CNAME).Target) assert.Equal(t, dns.TypeA, reply.Answer[1].Header().Rrtype) - - _ = s.Stop() } func createTestServer(t *testing.T) *Server { @@ -747,22 +739,27 @@ func createTestServer(t *testing.T) *Server { filters := []dnsfilter.Filter{{ ID: 0, Data: []byte(rules), }} - c := dnsfilter.Config{} - c.SafeBrowsingEnabled = true - c.SafeBrowsingCacheSize = 1000 - c.SafeSearchEnabled = true - c.SafeSearchCacheSize = 1000 - c.ParentalCacheSize = 1000 - c.CacheTime = 30 + c := dnsfilter.Config{ + SafeBrowsingEnabled: true, + SafeBrowsingCacheSize: 1000, + SafeSearchEnabled: true, + SafeSearchCacheSize: 1000, + ParentalCacheSize: 1000, + CacheTime: 30, + } f := dnsfilter.New(&c, filters) s := NewServer(DNSCreateParams{DNSFilter: f}) - s.conf.UDPListenAddr = &net.UDPAddr{Port: 0} - s.conf.TCPListenAddr = &net.TCPAddr{Port: 0} - s.conf.UpstreamDNS = []string{"8.8.8.8:53", "8.8.4.4:53"} - s.conf.FilteringConfig.ProtectionEnabled = true - s.conf.ConfigModified = func() {} + s.conf = ServerConfig{ + UDPListenAddr: &net.UDPAddr{Port: 0}, + TCPListenAddr: &net.TCPAddr{Port: 0}, + FilteringConfig: FilteringConfig{ + ProtectionEnabled: true, + UpstreamDNS: []string{"8.8.8.8:53", "8.8.4.4:53"}, + }, + ConfigModified: func() {}, + } err := s.Prepare(nil) assert.Nil(t, err) @@ -770,16 +767,14 @@ func createTestServer(t *testing.T) *Server { } func createServerTLSConfig(t *testing.T) (*tls.Config, []byte, []byte) { + t.Helper() + privateKey, err := rsa.GenerateKey(rand.Reader, 2048) - if err != nil { - t.Fatalf("cannot generate RSA key: %s", err) - } + assert.Nilf(t, err, "cannot generate RSA key: %s", err) serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) - if err != nil { - t.Fatalf("failed to generate serial number: %s", err) - } + assert.Nilf(t, err, "failed to generate serial number: %s", err) notBefore := time.Now() notAfter := notBefore.Add(5 * 365 * time.Hour * 24) @@ -800,76 +795,66 @@ func createServerTLSConfig(t *testing.T) (*tls.Config, []byte, []byte) { template.DNSNames = append(template.DNSNames, tlsServerName) derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, publicKey(privateKey), privateKey) - if err != nil { - t.Fatalf("failed to create certificate: %s", err) - } + assert.Nilf(t, err, "failed to create certificate: %s", err) certPem := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) keyPem := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(privateKey)}) cert, err := tls.X509KeyPair(certPem, keyPem) - if err != nil { - t.Fatalf("failed to create certificate: %s", err) - } + assert.Nilf(t, err, "failed to create certificate: %s", err) - return &tls.Config{Certificates: []tls.Certificate{cert}, ServerName: tlsServerName, MinVersion: tls.VersionTLS12}, certPem, keyPem + return &tls.Config{ + Certificates: []tls.Certificate{cert}, + ServerName: tlsServerName, + MinVersion: tls.VersionTLS12, + }, certPem, keyPem } -func sendTestMessageAsync(t *testing.T, conn *dns.Conn, g *sync.WaitGroup) { - defer func() { - g.Done() - }() - - req := createGoogleATestMessage() - err := conn.WriteMsg(req) - if err != nil { - panic(fmt.Sprintf("cannot write message: %s", err)) - } - - res, err := conn.ReadMsg() - if err != nil { - panic(fmt.Sprintf("cannot read response to message: %s", err)) - } - assertGoogleAResponse(t, res) -} - -// sendTestMessagesAsync sends messages in parallel -// so that we could find race issues +// sendTestMessagesAsync sends messages in parallel to check for race issues. +//lint:ignore U1000 it's called from the function which is skipped for now. func sendTestMessagesAsync(t *testing.T, conn *dns.Conn) { - g := &sync.WaitGroup{} - g.Add(testMessagesCount) + wg := &sync.WaitGroup{} for i := 0; i < testMessagesCount; i++ { - go sendTestMessageAsync(t, conn, g) + msg := createGoogleATestMessage() + wg.Add(1) + + go func() { + defer wg.Done() + + err := conn.WriteMsg(msg) + assert.Nilf(t, err, "cannot write message: %s", err) + + res, err := conn.ReadMsg() + assert.Nilf(t, err, "cannot read response to message: %s", err) + + assertGoogleAResponse(t, res) + }() } - g.Wait() + wg.Wait() } func sendTestMessages(t *testing.T, conn *dns.Conn) { - for i := 0; i < 10; i++ { + t.Helper() + + for i := 0; i < testMessagesCount; i++ { req := createGoogleATestMessage() err := conn.WriteMsg(req) - if err != nil { - t.Fatalf("cannot write message #%d: %s", i, err) - } + assert.Nilf(t, err, "cannot write message #%d: %s", i, err) res, err := conn.ReadMsg() - if err != nil { - t.Fatalf("cannot read response to message #%d: %s", i, err) - } + assert.Nilf(t, err, "cannot read response to message #%d: %s", i, err) assertGoogleAResponse(t, res) } } -func exchangeAndAssertResponse(t *testing.T, client *dns.Client, addr net.Addr, host, ip string) { +func exchangeAndAssertResponse(t *testing.T, client *dns.Client, addr net.Addr, host string, ip net.IP) { t.Helper() req := createTestMessage(host) reply, _, err := client.Exchange(req, addr.String()) - if err != nil { - t.Fatalf("Couldn't talk to server %s: %s", addr, err) - } + assert.Nilf(t, err, "couldn't talk to server %s: %s", addr, err) assertResponse(t, reply, ip) } @@ -878,41 +863,36 @@ func createGoogleATestMessage() *dns.Msg { } func createTestMessage(host string) *dns.Msg { - req := dns.Msg{} - req.Id = dns.Id() - req.RecursionDesired = true - req.Question = []dns.Question{ - {Name: host, Qtype: dns.TypeA, Qclass: dns.ClassINET}, + return &dns.Msg{ + MsgHdr: dns.MsgHdr{ + Id: dns.Id(), + RecursionDesired: true, + }, + Question: []dns.Question{{ + Name: host, + Qtype: dns.TypeA, + Qclass: dns.ClassINET, + }}, } - return &req } func createTestMessageWithType(host string, qtype uint16) *dns.Msg { - req := dns.Msg{} - req.Id = dns.Id() - req.RecursionDesired = true - req.Question = []dns.Question{ - {Name: host, Qtype: qtype, Qclass: dns.ClassINET}, - } - return &req + req := createTestMessage(host) + req.Question[0].Qtype = qtype + return req } func assertGoogleAResponse(t *testing.T, reply *dns.Msg) { - assertResponse(t, reply, "8.8.8.8") + assertResponse(t, reply, net.IP{8, 8, 8, 8}) } -func assertResponse(t *testing.T, reply *dns.Msg, ip string) { +func assertResponse(t *testing.T, reply *dns.Msg, ip net.IP) { t.Helper() - if len(reply.Answer) != 1 { - t.Fatalf("DNS server returned reply with wrong number of answers - %d", len(reply.Answer)) - } - if a, ok := reply.Answer[0].(*dns.A); ok { - if !net.ParseIP(ip).Equal(a.A) { - t.Fatalf("DNS server returned wrong answer instead of %s: %v", ip, a.A) - } - } else { - t.Fatalf("DNS server returned wrong answer type instead of A: %v", reply.Answer[0]) + assert.Lenf(t, reply.Answer, 1, "dns server returned reply with wrong number of answers - %d", len(reply.Answer)) + a, ok := reply.Answer[0].(*dns.A) + if assert.Truef(t, ok, "dns server returned wrong answer type instead of A: %v", reply.Answer[0]) { + assert.Truef(t, a.A.Equal(ip), "dns server returned wrong answer instead of %s: %s", ip, a.A) } } @@ -928,68 +908,99 @@ func publicKey(priv interface{}) interface{} { } func TestValidateUpstream(t *testing.T) { - invalidUpstreams := []string{ - "1.2.3.4.5", - "123.3.7m", - "htttps://google.com/dns-query", - "[/host.com]tls://dns.adguard.com", - "[host.ru]#", - } + testCases := []struct { + name string + upstream string + valid bool + wantDef bool + }{{ + name: "invalid", + upstream: "1.2.3.4.5", + valid: false, + }, { + name: "invalid", + upstream: "123.3.7m", + valid: false, + }, { + name: "invalid", + upstream: "htttps://google.com/dns-query", + valid: false, + }, { + name: "invalid", + upstream: "[/host.com]tls://dns.adguard.com", + valid: false, + }, { + name: "invalid", + upstream: "[host.ru]#", + valid: false, + }, { + name: "valid_default", + upstream: "1.1.1.1", + valid: true, + wantDef: true, + }, { + name: "valid_default", + upstream: "tls://1.1.1.1", + valid: true, + wantDef: true, + }, { + name: "valid_default", + upstream: "https://dns.adguard.com/dns-query", + valid: true, + wantDef: true, + }, { + name: "valid_default", + upstream: "sdns://AQMAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzMDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20", + valid: true, + wantDef: true, + }, { + name: "valid", + upstream: "[/host.com/]1.1.1.1", + valid: true, + wantDef: false, + }, { + name: "valid", + upstream: "[//]tls://1.1.1.1", + valid: true, + wantDef: false, + }, { + name: "valid", + upstream: "[/www.host.com/]#", + valid: true, + wantDef: false, + }, { + name: "valid", + upstream: "[/host.com/google.com/]8.8.8.8", + valid: true, + wantDef: false, + }, { + name: "valid", + upstream: "[/host/]sdns://AQMAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzMDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20", + valid: true, + wantDef: false, + }} - validDefaultUpstreams := []string{ - "1.1.1.1", - "tls://1.1.1.1", - "https://dns.adguard.com/dns-query", - "sdns://AQMAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzMDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20", - } - - validUpstreams := []string{ - "[/host.com/]1.1.1.1", - "[//]tls://1.1.1.1", - "[/www.host.com/]#", - "[/host.com/google.com/]8.8.8.8", - "[/host/]sdns://AQMAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzMDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20", - } - for _, u := range invalidUpstreams { - _, err := validateUpstream(u) - if err == nil { - t.Fatalf("upstream %s is invalid but it pass through validation", u) - } - } - - for _, u := range validDefaultUpstreams { - defaultUpstream, err := validateUpstream(u) - if err != nil { - t.Fatalf("upstream %s is valid but it doen't pass through validation cause: %s", u, err) - } - if !defaultUpstream { - t.Fatalf("upstream %s is default one!", u) - } - } - - for _, u := range validUpstreams { - defaultUpstream, err := validateUpstream(u) - if err != nil { - t.Fatalf("upstream %s is valid but it doen't pass through validation cause: %s", u, err) - } - if defaultUpstream { - t.Fatalf("upstream %s is default one!", u) - } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + defaultUpstream, err := validateUpstream(tc.upstream) + assert.Equal(t, tc.valid, err == nil) + if err == nil { + assert.Equal(t, tc.wantDef, defaultUpstream) + } + }) } } func TestValidateUpstreamsSet(t *testing.T) { - // Empty upstreams array + // Empty upstreams array. var upstreamsSet []string - err := ValidateUpstreams(upstreamsSet) - assert.Nil(t, err, "empty upstreams array should be valid") + assert.Nil(t, ValidateUpstreams(upstreamsSet), "empty upstreams array should be valid") - // Comment in upstreams array + // Comment in upstreams array. upstreamsSet = []string{"# comment"} - err = ValidateUpstreams(upstreamsSet) - assert.Nil(t, err, "comments should not be validated") + assert.Nil(t, ValidateUpstreams(upstreamsSet), "comments should not be validated") - // Set of valid upstreams. There is no default upstream specified + // Set of valid upstreams. There is no default upstream specified. upstreamsSet = []string{ "[/host.com/]1.1.1.1", "[//]tls://1.1.1.1", @@ -997,49 +1008,80 @@ func TestValidateUpstreamsSet(t *testing.T) { "[/host.com/google.com/]8.8.8.8", "[/host/]sdns://AQMAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzMDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20", } - err = ValidateUpstreams(upstreamsSet) - assert.NotNil(t, err, "there is no default upstream") + assert.NotNil(t, ValidateUpstreams(upstreamsSet), "there is no default upstream") - // Let's add default upstream + // Let's add default upstream. upstreamsSet = append(upstreamsSet, "8.8.8.8") - err = ValidateUpstreams(upstreamsSet) + err := ValidateUpstreams(upstreamsSet) assert.Nilf(t, err, "upstreams set is valid, but doesn't pass through validation cause: %s", err) - // Let's add invalid upstream + // Let's add invalid upstream. upstreamsSet = append(upstreamsSet, "dhcp://fake.dns") - err = ValidateUpstreams(upstreamsSet) - assert.NotNil(t, err, "there is an invalid upstream in set, but it pass through validation") + assert.NotNil(t, ValidateUpstreams(upstreamsSet), "there is an invalid upstream in set, but it pass through validation") } func TestIPStringFromAddr(t *testing.T) { - addr := net.UDPAddr{} - addr.IP = net.ParseIP("1:2:3::4") - addr.Port = 12345 - addr.Zone = "eth0" - assert.Equal(t, IPStringFromAddr(&addr), net.ParseIP("1:2:3::4").String()) - + addr := net.UDPAddr{ + IP: net.ParseIP("1:2:3::4"), + Port: 12345, + Zone: "eth0", + } + assert.Equal(t, IPStringFromAddr(&addr), addr.IP.String()) assert.Empty(t, IPStringFromAddr(nil)) } func TestMatchDNSName(t *testing.T) { dnsNames := []string{"host1", "*.host2", "1.2.3.4"} sort.Strings(dnsNames) - assert.True(t, matchDNSName(dnsNames, "host1")) - assert.True(t, matchDNSName(dnsNames, "a.host2")) - assert.True(t, matchDNSName(dnsNames, "b.a.host2")) - assert.True(t, matchDNSName(dnsNames, "1.2.3.4")) - assert.False(t, matchDNSName(dnsNames, "host2")) - assert.False(t, matchDNSName(dnsNames, "")) - assert.False(t, matchDNSName(dnsNames, "*.host2")) + + testCases := []struct { + name string + dnsName string + want bool + }{{ + name: "match", + dnsName: "host1", + want: true, + }, { + name: "match", + dnsName: "a.host2", + want: true, + }, { + name: "match", + dnsName: "b.a.host2", + want: true, + }, { + name: "match", + dnsName: "1.2.3.4", + want: true, + }, { + name: "mismatch", + dnsName: "host2", + want: false, + }, { + name: "mismatch", + dnsName: "", + want: false, + }, { + name: "mismatch", + dnsName: "*.host2", + want: false, + }} + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + assert.Equal(t, tc.want, matchDNSName(dnsNames, tc.dnsName)) + }) + } } type testDHCP struct{} func (d *testDHCP) Leases(flags int) []dhcpd.Lease { - l := dhcpd.Lease{} - l.IP = net.IP{127, 0, 0, 1} - l.HWAddr, _ = net.ParseMAC("aa:aa:aa:aa:aa:aa") - l.Hostname = "localhost" + l := dhcpd.Lease{ + IP: net.IP{127, 0, 0, 1}, + HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, + Hostname: "localhost", + } return []dhcpd.Lease{l} } func (d *testDHCP) SetOnLeaseChanged(onLeaseChanged dhcpd.OnLeaseChangedT) {} @@ -1047,26 +1089,30 @@ func (d *testDHCP) SetOnLeaseChanged(onLeaseChanged dhcpd.OnLeaseChangedT) {} func TestPTRResponseFromDHCPLeases(t *testing.T) { dhcp := &testDHCP{} - c := dnsfilter.Config{} - f := dnsfilter.New(&c, nil) - s := NewServer(DNSCreateParams{DNSFilter: f, DHCPServer: dhcp}) + s := NewServer(DNSCreateParams{ + DNSFilter: dnsfilter.New(&dnsfilter.Config{}, nil), + DHCPServer: dhcp, + }) + s.conf.UDPListenAddr = &net.UDPAddr{Port: 0} s.conf.TCPListenAddr = &net.TCPAddr{Port: 0} s.conf.UpstreamDNS = []string{"127.0.0.1:53"} s.conf.FilteringConfig.ProtectionEnabled = true err := s.Prepare(nil) assert.Nil(t, err) - assert.Nil(t, s.Start()) + assert.Nil(t, s.Start()) addr := s.dnsProxy.Addr(proxy.ProtoUDP) - req := createTestMessage("1.0.0.127.in-addr.arpa.") - req.Question[0].Qtype = dns.TypePTR + + req := createTestMessageWithType("1.0.0.127.in-addr.arpa.", dns.TypePTR) resp, err := dns.Exchange(req, addr.String()) + assert.Nil(t, err) assert.Len(t, resp.Answer, 1) assert.Equal(t, dns.TypePTR, resp.Answer[0].Header().Rrtype) assert.Equal(t, "1.0.0.127.in-addr.arpa.", resp.Answer[0].Header().Name) + ptr := resp.Answer[0].(*dns.PTR) assert.Equal(t, "localhost.", ptr.Ptr) @@ -1078,37 +1124,40 @@ func TestPTRResponseFromHosts(t *testing.T) { AutoHosts: &util.AutoHosts{}, } - // Prepare test hosts file - hf, _ := ioutil.TempFile("", "") - defer func() { _ = os.Remove(hf.Name()) }() - defer hf.Close() + // Prepare test hosts file. + hf, err := ioutil.TempFile("", "") + if assert.Nil(t, err) { + t.Cleanup(func() { + assert.Nil(t, hf.Close()) + assert.Nil(t, os.Remove(hf.Name())) + }) + } _, _ = hf.WriteString(" 127.0.0.1 host # comment \n") _, _ = hf.WriteString(" ::1 localhost#comment \n") - // Init auto hosts + // Init auto hosts. c.AutoHosts.Init(hf.Name()) - defer c.AutoHosts.Close() + t.Cleanup(c.AutoHosts.Close) - f := dnsfilter.New(&c, nil) - s := NewServer(DNSCreateParams{DNSFilter: f}) + s := NewServer(DNSCreateParams{DNSFilter: dnsfilter.New(&c, nil)}) s.conf.UDPListenAddr = &net.UDPAddr{Port: 0} s.conf.TCPListenAddr = &net.TCPAddr{Port: 0} s.conf.UpstreamDNS = []string{"127.0.0.1:53"} s.conf.FilteringConfig.ProtectionEnabled = true - err := s.Prepare(nil) - assert.Nil(t, err) + assert.Nil(t, s.Prepare(nil)) + assert.Nil(t, s.Start()) addr := s.dnsProxy.Addr(proxy.ProtoUDP) - req := createTestMessage("1.0.0.127.in-addr.arpa.") - req.Question[0].Qtype = dns.TypePTR + req := createTestMessageWithType("1.0.0.127.in-addr.arpa.", dns.TypePTR) resp, err := dns.Exchange(req, addr.String()) assert.Nil(t, err) assert.Len(t, resp.Answer, 1) assert.Equal(t, dns.TypePTR, resp.Answer[0].Header().Rrtype) assert.Equal(t, "1.0.0.127.in-addr.arpa.", resp.Answer[0].Header().Name) + ptr := resp.Answer[0].(*dns.PTR) assert.Equal(t, "host.", ptr.Ptr) From c82ebf5903d5886f5d9d0c855d95e7387c98d4ac Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Tue, 2 Feb 2021 18:31:47 +0300 Subject: [PATCH 084/141] Pull request: scripts: fix version.txt creation Merge in DNS/adguard-home from fix-version-txt to master Squashed commit of the following: commit b406bbb0b0c862016e5801fd1d5131f85972f16b Author: Ainar Garipov Date: Tue Feb 2 18:13:49 2021 +0300 scripts: fix version.txt creation --- scripts/make/build-release.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/make/build-release.sh b/scripts/make/build-release.sh index b191ac9b..2fc3944f 100644 --- a/scripts/make/build-release.sh +++ b/scripts/make/build-release.sh @@ -293,7 +293,7 @@ log "calculating checksums" log "writing versions" -echo "$version" > "./${dist}/version.txt" +echo "version=$version" > "./${dist}/version.txt" # Create the verison.json file. # From 9cd9054cdb825a2078dc8114801127e71526227d Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Tue, 2 Feb 2021 20:32:44 +0300 Subject: [PATCH 085/141] Pull request: updater: fix pkg unpacking Merge in DNS/adguard-home from 2630-updates to master Updates #2630. Squashed commit of the following: commit 6dab69d2ed11bdf735df2d3acd5afa77a7d7b1c0 Author: Ainar Garipov Date: Tue Feb 2 20:00:59 2021 +0300 all: changelog commit 63720bb5cb821bf68774e45836dd1daf9d0fc8f1 Author: Ainar Garipov Date: Tue Feb 2 19:54:28 2021 +0300 updater: fix pkg unpacking --- CHANGELOG.md | 6 ++++-- internal/updater/updater.go | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2328d259..b4af0e4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -70,13 +70,14 @@ and this project adheres to ### Deprecated -- _Go_ 1.14 support. v0.106.0 will require at least _Go_ 1.15 to build. +- Go 1.14 support. v0.106.0 will require at least Go 1.15 to build. - The `darwin/386` port. It will be removed in v0.106.0. - The `"rule"` and `"filter_id"` fields in `GET /filtering/check_host` and `GET /querylog` responses. They will be removed in v0.106.0 ([#2102]). ### Fixed +- Autoupdate bug in the Darwin (macOS) version ([#2630]). - Unnecessary conversions from `string` to `net.IP`, and vice versa ([#2508]). - Inability to set DNS cache TTL limits ([#2459]). - Possible freezes on slower machines ([#2225]). @@ -92,6 +93,7 @@ and this project adheres to [#2355]: https://github.com/AdguardTeam/AdGuardHome/issues/2355 [#2459]: https://github.com/AdguardTeam/AdGuardHome/issues/2459 [#2508]: https://github.com/AdguardTeam/AdGuardHome/issues/2508 +[#2630]: https://github.com/AdguardTeam/AdGuardHome/issues/2630 ### Removed @@ -127,7 +129,7 @@ and this project adheres to - Query logs from file not loading after the ones buffered in memory ([#2325]). - Unnecessary errors in query logs when switching between log files ([#2324]). -- `404 Not Found` errors on the DHCP settings page on *Windows*. The page now +- `404 Not Found` errors on the DHCP settings page on Windows. The page now correctly shows that DHCP is not currently available on that OS ([#2295]). - Infinite loop in `/dhcp/find_active_dhcp` ([#2301]). diff --git a/internal/updater/updater.go b/internal/updater/updater.go index 8d0bee5f..73a2f7cb 100644 --- a/internal/updater/updater.go +++ b/internal/updater/updater.go @@ -355,6 +355,16 @@ func tarGzFileUnpack(tarfile, outdir string) ([]string, error) { outputName := filepath.Join(outdir, inputNameOnly) if header.Typeflag == tar.TypeDir { + if inputNameOnly == "AdGuardHome" { + // Top-level AdGuardHome/. Skip it. + // + // TODO(a.garipov): This whole package needs to + // be rewritten and covered in more integration + // tests. It has weird assumptions and file + // mode issues. + continue + } + err = os.Mkdir(outputName, os.FileMode(header.Mode&0o777)) if err != nil && !os.IsExist(err) { err2 = fmt.Errorf("os.Mkdir(%s): %w", outputName, err) @@ -422,6 +432,14 @@ func zipFileUnpack(zipfile, outdir string) ([]string, error) { outputName := filepath.Join(outdir, inputNameOnly) if fi.IsDir() { + if inputNameOnly == "AdGuardHome" { + // Top-level AdGuardHome/. Skip it. + // + // TODO(a.garipov): See the similar todo in + // tarGzFileUnpack. + continue + } + err = os.Mkdir(outputName, fi.Mode()) if err != nil && !os.IsExist(err) { err2 = fmt.Errorf("os.Mkdir(): %w", err) From 0c127039cfa55d4cadfce20bcfa2c542840bd7ed Mon Sep 17 00:00:00 2001 From: Vladislav Abdulmyanov Date: Wed, 3 Feb 2021 16:14:20 +0300 Subject: [PATCH 086/141] Pull request #961: New client dashboard Merge in DNS/adguard-home from new-client-dashboard to master Squashed commit of the following: commit 7bbd67c1e3d2af62b96bf41bb356cd6b784e473e Merge: 113743a6 9cd9054c Author: Vlad Date: Wed Feb 3 16:01:17 2021 +0300 Merge branch 'master' into new-client-dashboard commit 113743a60665e40383d367dc17fa709dc54e4e2e Author: Vlad Date: Wed Feb 3 15:45:16 2021 +0300 Remove unneded modal styles commit 04f9d93a9ac17ee046f0d5bedfb2bf5a5e6c0a48 Author: Vlad Date: Wed Feb 3 14:19:56 2021 +0300 Consider comments commit 78a96cd8fed8b3e03547e7e45724c23db295f67b Author: Vlad Date: Mon Feb 1 18:46:52 2021 +0300 Remove old params for MiniCssExtractPlugin commit 40e5a9b2b1e04036deb70af17f2719eadd0c9c02 Author: Vlad Date: Mon Feb 1 18:27:46 2021 +0300 Fix mobile version commit 509cefc308f945b03cafa62bf48257490a0a4be1 Author: Vlad Date: Mon Feb 1 18:20:56 2021 +0300 Remove unneeded imports commit d192f39cd2503b8ec942f00ba78fca02cac9fa60 Author: Vlad Date: Mon Feb 1 18:20:13 2021 +0300 Finish first version of dashboard commit f82429e53d334874ff7dd0641092ec83c66ab61c Merge: fd91a0a3 3e0238aa Author: Vlad Date: Mon Feb 1 17:12:59 2021 +0300 Merge branch 'master' into new-client-dashboard commit fd91a0a3d76c2a052a6548232b75d151d6065b88 Author: Vlad Date: Mon Feb 1 17:12:27 2021 +0300 wip commit 237679965052d38acfcd6a72d24b2444cc5b3896 Author: Vlad Date: Fri Jan 29 11:18:10 2021 +0300 Finish general settings commit 397a7e10efd34a8d31bb175a5a5a7158338388d4 Author: Vlad Date: Thu Jan 28 19:24:03 2021 +0300 Add General settings page commit 486aaa6f3f9ad66f3a0dcfcccad9a32659767e90 Author: Vlad Date: Thu Jan 28 14:05:16 2021 +0300 Remove husky commit b895306c0655019ca56ce161e050d83b4e7f5ff1 Merge: a195f1f4 154c9c1c Author: Vlad Date: Thu Jan 28 14:03:37 2021 +0300 Merge branch 'master' into new-client-dashboard commit a195f1f4d46043d9c53dea08734733f9817b95a0 Merge: c45c5fe9 362f390f Author: Vlad Date: Wed Jan 27 15:46:18 2021 +0300 Merge branch 'new-client-dashboard' of ssh://bit.adguard.com:7999/dns/adguard-home into new-client-dashboard commit c45c5fe92e6c5c852bec8f512dc46b4cd513156c Author: Vlad Date: Wed Jan 27 15:46:01 2021 +0300 wip commit 362f390fd3dcfca75633a8d30a2e54c3c30b4f3d Author: Vladislav Abdulmyanov Date: Wed Jan 27 15:45:12 2021 +0300 Pull request #949: + client: add setup guide page Merge in DNS/adguard-home from 2554-setup-guide to new-client-dashboard Squashed commit of the following: commit c240d52e9e5d90429f2018fde808f4d04ccec138 Merge: 256f1056 137b88e4 Author: Ildar Kamalov Date: Wed Jan 27 14:13:52 2021 +0300 Merge branch 'new-client-dashboard' into 2554-setup-guide commit 256f1056770c67339e93275ab6dc7aaf2c10da0b Author: Ildar Kamalov Date: Wed Jan 27 14:10:45 2021 +0300 + client: add DNS addresses to the setup guide commit 0ecf91275a16ecc0dca23cae2ae209836fc622d2 Author: Ildar Kamalov Date: Wed Jan 27 14:00:12 2021 +0300 + client: add setup guide tabs commit 137b88e4253af5be32d542adbe74575ef74805c8 Author: Vlad Date: Thu Jan 21 19:17:58 2021 +0300 Add clients top commit c3318e6932d87fdff5f22d76bee12b49f099129a Merge: 2776276b 021eb22f Author: Vlad Date: Thu Jan 21 19:15:57 2021 +0300 Merge branch 'new-client-dashboard' of ssh://bit.adguard.com:7999/dns/adguard-home into new-client-dashboard commit 2776276b2e6dc026e1326b02c388fcf7d48d47ff Author: Vlad Date: Thu Jan 21 19:15:53 2021 +0300 Add top client info commit 021eb22ff877aec12eb7fab60147a2cc2ddd08b7 Author: Ildar Kamalov Date: Thu Jan 21 14:13:54 2021 +0300 Merge: client: add sidebar Squashed commit of the following: commit 6885ba953971e68602889fbb3219221f90265421 Author: Ildar Kamalov Date: Thu Jan 21 13:56:55 2021 +0300 add sidebar mask commit f069bfe8cba2b31355e19a51ca00bf774ee9e560 Author: Ildar Kamalov Date: Thu Jan 21 13:03:47 2021 +0300 fix store commit 77c8791002887ae022da07dc264d9010576e7bab Merge: d0a6eff6 ea6d54d4 Author: Ildar Kamalov Date: Thu Jan 21 13:01:04 2021 +0300 Merge branch 'new-client-dashboard' into 2254-sidebar commit d0a6eff67fd74533d63f5d56382085e98ddbb702 Author: Ildar Kamalov Date: Thu Jan 21 12:47:32 2021 +0300 client: remove unused file commit 9d2424477de85503fe41fa00cc1294cb0c0e7dfa Author: Ildar Kamalov Date: Thu Jan 21 12:39:13 2021 +0300 client: header commit 9ddea19c136f15b184caa72d7e82738f7d4f3f1f Merge: 797f1248 b694bb05 Author: Ildar Kamalov Date: Thu Jan 21 10:57:24 2021 +0300 Merge branch 'new-client-dashboard' into 2254-sidebar commit 797f1248df5c1ef8e59c2a9999138f9e05a7adaa Author: Ildar Kamalov Date: Thu Jan 21 10:51:57 2021 +0300 client: sidebar ... and 14 more commits --- client2/package.json | 11 +- .../scripts/generator/src/generateEntities.ts | 92 +- client2/scripts/generator/src/utils.ts | 13 +- client2/scripts/webpack/webpack.config.dev.js | 3 +- .../scripts/webpack/webpack.config.prod.js | 6 - client2/src/components/App.tsx | 16 - client2/src/components/App/App.tsx | 20 + .../components/App/Dashboard/Dashboard.tsx | 136 + .../BlockCard/BlockCard.module.pcss | 20 + .../components/BlockCard/BlockCard.tsx | 35 + .../Dashboard/components/BlockCard/index.ts | 1 + .../BlockedQueries/BlockedQueries.module.pcss | 16 + .../BlockedQueries/BlockedQueries.tsx | 76 + .../components/BlockedQueries/index.ts | 1 + .../ServerStatistics.module.pcss | 46 + .../ServerStatistics/ServerStatistics.tsx | 89 + .../components/ServerStatistics/index.ts | 1 + .../TopClients/TopClients.module.pcss | 43 + .../components/TopClients/TopClients.tsx | 71 + .../Dashboard/components/TopClients/index.ts | 1 + .../TopDomains/TopDomains.module.pcss | 62 + .../components/TopDomains/TopDomains.tsx | 73 + .../Dashboard/components/TopDomains/index.ts | 1 + .../App/Dashboard/components/index.ts | 5 + client2/src/components/App/Dashboard/index.ts | 1 + .../components/App/Errors/ErrorBoundary.tsx | 31 + .../components/App/Errors/Errors.module.pcss | 79 + client2/src/components/App/Errors/index.ts | 1 + .../components/App/Header/Header.module.pcss | 81 + client2/src/components/App/Header/Header.tsx | 60 + client2/src/components/App/Header/index.ts | 1 + .../components/App/Login/ForgotPassword.tsx | 65 + .../components/App/Login/Login.module.pcss | 34 + client2/src/components/App/Login/Login.tsx | 102 + client2/src/components/App/Login/index.ts | 2 + client2/src/components/App/Routes/Paths.ts | 63 + .../components/App/Routes/Routes.module.pcss | 3 + client2/src/components/App/Routes/Routes.tsx | 76 + client2/src/components/App/Routes/index.ts | 1 + .../GeneralSettings/GeneralSettings.tsx | 52 + .../components/Common.module.pcss | 45 + .../GeneralSettings/components/General.tsx | 169 + .../GeneralSettings/components/QueryLog.tsx | 124 + .../GeneralSettings/components/Statistics.tsx | 105 + .../GeneralSettings/components/index.ts | 9 + .../App/Settings/GeneralSettings/index.ts | 1 + client2/src/components/App/Settings/index.ts | 1 + .../App/SetupGuide/SetupGuide.module.pcss | 31 + .../components/App/SetupGuide/SetupGuide.tsx | 92 + .../src/components/App/SetupGuide/index.tsx | 1 + .../App/Sidebar/Sidebar.module.pcss | 23 + .../src/components/App/Sidebar/Sidebar.tsx | 116 + client2/src/components/App/Sidebar/index.ts | 1 + client2/src/components/App/index.ts | 1 + client2/src/components/Install/Install.tsx | 7 +- .../ConfigureDevices/ConfigureDevices.tsx | 18 +- .../common/controls/Button/Button.tsx | 67 + .../common/controls/Button/index.ts | 1 + .../common/controls/Radio/Radio.tsx | 4 +- .../src/components/common/controls/index.ts | 1 + .../src/components/common/formating/code.tsx | 12 + .../common/formating/externalLink.tsx | 13 + .../src/components/common/formating/index.ts | 2 + .../src/components/common/ui/Icon/Icon.tsx | 4 +- .../common/ui}/Icons/Icon.pcss | 0 .../common/ui}/Icons/index.tsx | 54 +- .../ui/LangSelect/LangSelect.module.pcss | 10 + .../common/ui/LangSelect/LangSelect.tsx | 23 + .../components/common/ui/LangSelect/index.tsx | 1 + client2/src/components/common/ui/Link.tsx | 63 + .../common/ui/Mask/Mask.module.pcss | 26 + .../src/components/common/ui/Mask/Mask.tsx | 23 + .../src/components/common/ui/Mask/index.ts | 1 + client2/src/components/common/ui/index.ts | 4 + .../common/ui/layouts/CommonLayout.tsx | 16 + .../common/ui/layouts/CommonModalLayout.tsx | 87 + .../common/ui/layouts/ConfirmModalLayout.tsx | 34 + .../common/ui/layouts/InnerLayout.tsx | 41 + .../src/components/common/ui/layouts/index.ts | 4 + client2/src/lib/ant/Modal.pcss | 47 + client2/src/lib/ant/Sidebar.pcss | 26 + client2/src/lib/ant/Tabs.pcss | 45 + client2/src/lib/ant/ant-overrides.less | 4 + client2/src/lib/ant/ant.less | 4 + client2/src/lib/ant/index.ts | 3 + client2/src/lib/apis/clients.ts | 36 + client2/src/lib/apis/dhcp.ts | 13 + client2/src/lib/apis/mobileconfig.ts | 6 +- client2/src/lib/consts/common.ts | 2 + client2/src/lib/entities/AccessList.ts | 76 + .../src/lib/entities/AccessListResponse.ts | 6 + client2/src/lib/entities/AccessSetRequest.ts | 6 + client2/src/lib/entities/AddressesInfo.ts | 16 +- .../src/lib/entities/ClientFindSubEntry.ts | 12 +- client2/src/lib/entities/ClientsFindEntry.ts | 28 +- .../entities/DhcpSearchResultOtherServer.ts | 3 +- .../lib/entities/DhcpSearchResultStaticIP.ts | 3 +- client2/src/lib/entities/Filter.ts | 24 +- client2/src/lib/entities/Login.ts | 30 +- client2/src/lib/entities/NetInterface.ts | 3 +- client2/src/lib/entities/NetInterfaces.ts | 33 + client2/src/lib/entities/QueryLogItem.ts | 22 +- client2/src/lib/entities/ServerStatus.ts | 58 +- client2/src/lib/entities/Stats.ts | 2 +- client2/src/lib/entities/TopArrayEntry.ts | 20 +- .../lib/entities/UpstreamsConfigResponse.ts | 26 +- client2/src/lib/entities/VersionInfo.ts | 15 + client2/src/lib/entities/WhoisInfo.ts | 38 +- client2/src/lib/theme/Content.module.pcss | 51 + client2/src/lib/theme/Install.module.pcss | 23 - client2/src/lib/theme/Link.module.pcss | 13 + client2/src/lib/theme/Text.module.pcss | 12 + client2/src/lib/theme/colors.ts | 8 + client2/src/lib/theme/index.ts | 5 + .../src/localization/Translator/Translator.ts | 82 - client2/src/localization/Translator/index.ts | 2 - .../localization/Translator/lib/formatter.ts | 100 - .../src/localization/Translator/lib/nodes.ts | 50 - .../src/localization/Translator/lib/parser.ts | 335 -- .../src/localization/Translator/lib/plural.ts | 394 -- .../localization/Translator/lib/translator.ts | 9 - .../localization/Translator/lib/validator.ts | 60 - client2/src/localization/index.ts | 13 +- client2/src/localization/locales/en.json | 91 +- client2/src/localization/locales/index.ts | 9 +- client2/src/main.pcss | 11 +- client2/src/store/store.ts | 21 + client2/src/store/stores/Dasnboard.ts | 120 + client2/src/store/stores/GeneralSettings.ts | 218 ++ client2/src/store/stores/Install.ts | 2 +- client2/src/store/stores/Login.ts | 45 + client2/src/store/stores/System.ts | 75 + client2/src/store/stores/ui.ts | 23 +- client2/src/store/stores/utils.ts | 38 + client2/tsconfig.json | 5 +- client2/yarn.lock | 3175 ++++++++--------- 136 files changed, 5384 insertions(+), 2938 deletions(-) delete mode 100644 client2/src/components/App.tsx create mode 100644 client2/src/components/App/App.tsx create mode 100644 client2/src/components/App/Dashboard/Dashboard.tsx create mode 100644 client2/src/components/App/Dashboard/components/BlockCard/BlockCard.module.pcss create mode 100644 client2/src/components/App/Dashboard/components/BlockCard/BlockCard.tsx create mode 100644 client2/src/components/App/Dashboard/components/BlockCard/index.ts create mode 100644 client2/src/components/App/Dashboard/components/BlockedQueries/BlockedQueries.module.pcss create mode 100644 client2/src/components/App/Dashboard/components/BlockedQueries/BlockedQueries.tsx create mode 100644 client2/src/components/App/Dashboard/components/BlockedQueries/index.ts create mode 100644 client2/src/components/App/Dashboard/components/ServerStatistics/ServerStatistics.module.pcss create mode 100644 client2/src/components/App/Dashboard/components/ServerStatistics/ServerStatistics.tsx create mode 100644 client2/src/components/App/Dashboard/components/ServerStatistics/index.ts create mode 100644 client2/src/components/App/Dashboard/components/TopClients/TopClients.module.pcss create mode 100644 client2/src/components/App/Dashboard/components/TopClients/TopClients.tsx create mode 100644 client2/src/components/App/Dashboard/components/TopClients/index.ts create mode 100644 client2/src/components/App/Dashboard/components/TopDomains/TopDomains.module.pcss create mode 100644 client2/src/components/App/Dashboard/components/TopDomains/TopDomains.tsx create mode 100644 client2/src/components/App/Dashboard/components/TopDomains/index.ts create mode 100644 client2/src/components/App/Dashboard/components/index.ts create mode 100644 client2/src/components/App/Dashboard/index.ts create mode 100644 client2/src/components/App/Errors/ErrorBoundary.tsx create mode 100644 client2/src/components/App/Errors/Errors.module.pcss create mode 100644 client2/src/components/App/Errors/index.ts create mode 100644 client2/src/components/App/Header/Header.module.pcss create mode 100644 client2/src/components/App/Header/Header.tsx create mode 100644 client2/src/components/App/Header/index.ts create mode 100644 client2/src/components/App/Login/ForgotPassword.tsx create mode 100644 client2/src/components/App/Login/Login.module.pcss create mode 100644 client2/src/components/App/Login/Login.tsx create mode 100644 client2/src/components/App/Login/index.ts create mode 100644 client2/src/components/App/Routes/Paths.ts create mode 100644 client2/src/components/App/Routes/Routes.module.pcss create mode 100644 client2/src/components/App/Routes/Routes.tsx create mode 100644 client2/src/components/App/Routes/index.ts create mode 100644 client2/src/components/App/Settings/GeneralSettings/GeneralSettings.tsx create mode 100644 client2/src/components/App/Settings/GeneralSettings/components/Common.module.pcss create mode 100644 client2/src/components/App/Settings/GeneralSettings/components/General.tsx create mode 100644 client2/src/components/App/Settings/GeneralSettings/components/QueryLog.tsx create mode 100644 client2/src/components/App/Settings/GeneralSettings/components/Statistics.tsx create mode 100644 client2/src/components/App/Settings/GeneralSettings/components/index.ts create mode 100644 client2/src/components/App/Settings/GeneralSettings/index.ts create mode 100644 client2/src/components/App/Settings/index.ts create mode 100644 client2/src/components/App/SetupGuide/SetupGuide.module.pcss create mode 100644 client2/src/components/App/SetupGuide/SetupGuide.tsx create mode 100644 client2/src/components/App/SetupGuide/index.tsx create mode 100644 client2/src/components/App/Sidebar/Sidebar.module.pcss create mode 100644 client2/src/components/App/Sidebar/Sidebar.tsx create mode 100644 client2/src/components/App/Sidebar/index.ts create mode 100644 client2/src/components/App/index.ts create mode 100644 client2/src/components/common/controls/Button/Button.tsx create mode 100644 client2/src/components/common/controls/Button/index.ts create mode 100644 client2/src/components/common/formating/code.tsx create mode 100644 client2/src/components/common/formating/externalLink.tsx rename client2/src/{lib/theme => components/common/ui}/Icons/Icon.pcss (100%) rename client2/src/{lib/theme => components/common/ui}/Icons/index.tsx (50%) create mode 100644 client2/src/components/common/ui/LangSelect/LangSelect.module.pcss create mode 100644 client2/src/components/common/ui/LangSelect/LangSelect.tsx create mode 100644 client2/src/components/common/ui/LangSelect/index.tsx create mode 100644 client2/src/components/common/ui/Link.tsx create mode 100644 client2/src/components/common/ui/Mask/Mask.module.pcss create mode 100644 client2/src/components/common/ui/Mask/Mask.tsx create mode 100644 client2/src/components/common/ui/Mask/index.ts create mode 100644 client2/src/components/common/ui/layouts/CommonLayout.tsx create mode 100644 client2/src/components/common/ui/layouts/CommonModalLayout.tsx create mode 100644 client2/src/components/common/ui/layouts/ConfirmModalLayout.tsx create mode 100644 client2/src/components/common/ui/layouts/InnerLayout.tsx create mode 100644 client2/src/components/common/ui/layouts/index.ts create mode 100644 client2/src/lib/ant/Modal.pcss create mode 100644 client2/src/lib/ant/Sidebar.pcss create mode 100644 client2/src/lib/ant/Tabs.pcss create mode 100644 client2/src/lib/entities/AccessList.ts create mode 100644 client2/src/lib/entities/AccessListResponse.ts create mode 100644 client2/src/lib/entities/AccessSetRequest.ts create mode 100644 client2/src/lib/entities/NetInterfaces.ts create mode 100644 client2/src/lib/theme/Content.module.pcss create mode 100644 client2/src/lib/theme/colors.ts delete mode 100644 client2/src/localization/Translator/Translator.ts delete mode 100644 client2/src/localization/Translator/index.ts delete mode 100644 client2/src/localization/Translator/lib/formatter.ts delete mode 100644 client2/src/localization/Translator/lib/nodes.ts delete mode 100644 client2/src/localization/Translator/lib/parser.ts delete mode 100644 client2/src/localization/Translator/lib/plural.ts delete mode 100644 client2/src/localization/Translator/lib/translator.ts delete mode 100644 client2/src/localization/Translator/lib/validator.ts create mode 100644 client2/src/store/stores/Dasnboard.ts create mode 100644 client2/src/store/stores/GeneralSettings.ts create mode 100644 client2/src/store/stores/Login.ts create mode 100644 client2/src/store/stores/System.ts create mode 100644 client2/src/store/stores/utils.ts diff --git a/client2/package.json b/client2/package.json index d48f1ca6..71f4b70c 100644 --- a/client2/package.json +++ b/client2/package.json @@ -12,13 +12,10 @@ "go:build": "cd .. && make REBUILD_CLIENT=0 build", "go:run": "sudo ../AdguardHome" }, - "husky": { - "hooks": { - "pre-commit": "yarn lint" - } - }, "license": "ISC", "dependencies": { + "@adguard/translate": "^0.2.0", + "@ant-design/icons": "^4.4.0", "@sentry/react": "^5.27.0", "antd": "^4.7.2", "classnames": "^2.2.6", @@ -29,7 +26,8 @@ "qs": "^6.9.4", "react": "^17.0.0", "react-dom": "^17.0.0", - "react-router-dom": "^5.2.0" + "react-router-dom": "^5.2.0", + "recharts": "^2.0.3" }, "devDependencies": { "@types/classnames": "^2.2.10", @@ -56,7 +54,6 @@ "file-loader": "^6.1.1", "html-webpack-plugin": "^4.5.0", "http-proxy-middleware": "^1.0.6", - "husky": "^4.3.0", "less": "^3.12.2", "less-loader": "^5.0.0", "mini-css-extract-plugin": "^1.1.1", diff --git a/client2/scripts/generator/src/generateEntities.ts b/client2/scripts/generator/src/generateEntities.ts index b12e02a7..7ace9fa1 100644 --- a/client2/scripts/generator/src/generateEntities.ts +++ b/client2/scripts/generator/src/generateEntities.ts @@ -4,7 +4,7 @@ import * as path from 'path'; import * as morph from 'ts-morph'; import { ENT_DIR } from '../../consts'; -import { TYPES, toCamel, schemaParamParser } from './utils'; +import { TYPES, toCamel, schemaParamParser, uncapitalize } from './utils'; const { Project, QuoteKind } = morph; @@ -125,14 +125,37 @@ class EntitiesGenerator { '', ]); - const { properties: sProps, required } = this.schemas[sName]; + + const { properties: sProps, required, $ref, additionalProperties } = this.schemas[sName]; + if ($ref) { + const temp = $ref.split('/'); + const importSchemaName = `${temp[temp.length - 1]}`; + entityFile.addImportDeclaration({ + defaultImport: importSchemaName, + moduleSpecifier: `./${importSchemaName}`, + namedImports: [`I${importSchemaName}`], + }); + + entityFile.addTypeAlias({ + name: `I${sName}`, + type: `I${importSchemaName}`, + isExported: true, + }) + + entityFile.addStatements(`export default ${importSchemaName};`); + this.entities.push(entityFile); + return; + } const importEntities: { type: string, isClass: boolean }[] = []; const entityInterface = entityFile.addInterface({ name: `I${sName}`, isExported: true, }); + const sortedSProps = Object.keys(sProps || {}).sort(); + const additionalPropsOnly = additionalProperties && sortedSProps.length === 0; + // add server response interface to entityFile sortedSProps.forEach((sPropName) => { const [ @@ -153,6 +176,23 @@ class EntitiesGenerator { ), }); }); + if (additionalProperties) { + const [ + pType, isArray, isClass, isImport, isAdditional + ] = schemaParamParser(additionalProperties, this.openapi); + + if (isImport) { + importEntities.push({ type: pType, isClass }); + } + const type = isAdditional + ? `{ [key: string]: ${isClass ? 'I' : ''}${pType}${isArray ? '[]' : ''} }` + : `${isClass ? 'I' : ''}${pType}${isArray ? '[]' : ''}`; + entityInterface.addIndexSignature({ + keyName: 'key', + keyType: 'string', + returnType: additionalPropsOnly ? type : `${type} | undefined`, + }); + } // add import const imports: { type: string, isClass: boolean }[] = []; @@ -310,7 +350,18 @@ class EntitiesGenerator { } } }); - + if (additionalProperties) { + const [ + pType, isArray, isClass, isImport, isAdditional + ] = schemaParamParser(additionalProperties, this.openapi); + const type = `Record`; + + entityClass.addProperty({ + name: additionalPropsOnly ? 'data' : `${uncapitalize(pType)}Data`, + isReadonly: true, + type: type, + }); + } // add constructor; const ctor = entityClass.addConstructor({ parameters: [{ @@ -319,6 +370,20 @@ class EntitiesGenerator { }], }); ctor.setBodyText((w) => { + if (additionalProperties) { + const [ + pType, isArray, isClass, isImport, isAdditional + ] = schemaParamParser(additionalProperties, this.openapi); + w.writeLine(`this.${additionalPropsOnly ? 'data' : `${uncapitalize(pType)}Data`} = Object.entries(props).reduce>((prev, [key, value]) => {`); + if (isClass) { + w.writeLine(` prev[key] = new ${pType}(value!);`); + } else { + w.writeLine(' prev[key] = value!;') + } + w.writeLine(' return prev;'); + w.writeLine('}, {})'); + return; + } sortedSProps.forEach((sPropName) => { const [ pType, isArray, isClass, , isAdditional @@ -369,6 +434,7 @@ class EntitiesGenerator { w.writeLine('}'); } }); + }); // add serialize method; @@ -378,6 +444,20 @@ class EntitiesGenerator { returnType: `I${sName}`, }); serialize.setBodyText((w) => { + if (additionalProperties) { + const [ + pType, isArray, isClass, isImport, isAdditional + ] = schemaParamParser(additionalProperties, this.openapi); + w.writeLine(`return Object.entries(this.${additionalPropsOnly ? 'data' : `${uncapitalize(pType)}Data`}).reduce>((prev, [key, value]) => {`); + if (isClass) { + w.writeLine(` prev[key] = value.serialize();`); + } else { + w.writeLine(' prev[key] = value;') + } + w.writeLine(' return prev;'); + w.writeLine('}, {})'); + return; + } w.writeLine(`const data: I${sName} = {`); const unReqFields: string[] = []; sortedSProps.forEach((sPropName) => { @@ -442,6 +522,10 @@ class EntitiesGenerator { returnType: `string[]`, }) validate.setBodyText((w) => { + if (additionalPropsOnly) { + w.writeLine('return []') + return; + } w.writeLine('const validate = {'); Object.keys(sProps || {}).forEach((sPropName) => { const [pType, isArray, isClass, , isAdditional] = schemaParamParser(sProps[sPropName], this.openapi); @@ -502,7 +586,7 @@ class EntitiesGenerator { }); update.addParameter({ name: 'props', - type: `Partial`, + type: additionalPropsOnly ? `I${sName}` : `Partial`, }); update.setBodyText((w) => { w.writeLine(`return new ${sName}({ ...this.serialize(), ...props });`); }); diff --git a/client2/scripts/generator/src/utils.ts b/client2/scripts/generator/src/utils.ts index 3ab2ef26..5183b6c5 100644 --- a/client2/scripts/generator/src/utils.ts +++ b/client2/scripts/generator/src/utils.ts @@ -8,6 +8,9 @@ const toCamel = (s: string) => { const capitalize = (s: string) => { return s[0].toUpperCase() + s.slice(1); }; +const uncapitalize = (s: string) => { + return s[0].toLowerCase() + s.slice(1); +}; const TYPES = { integer: 'number', float: 'number', @@ -37,7 +40,13 @@ const schemaParamParser = (schemaProp: any, openApi: any): [string, boolean, boo type = `${temp[temp.length - 1]}`; - const cl = openApi ? openApi.components.schemas[temp[temp.length - 1]] : {}; + const cl = openApi ? openApi.components.schemas[type] : {}; + + if (cl.$ref) { + const link = schemaParamParser(cl, openApi); + link.shift(); + return [type, ...link] as any; + } if (cl.type === 'string' && cl.enum) { isImport = true; @@ -71,4 +80,4 @@ const schemaParamParser = (schemaProp: any, openApi: any): [string, boolean, boo return [type, isArray, isClass, isImport, isAdditional]; }; -export { TYPES, toCamel, capitalize, schemaParamParser }; +export { TYPES, toCamel, capitalize, uncapitalize, schemaParamParser }; diff --git a/client2/scripts/webpack/webpack.config.dev.js b/client2/scripts/webpack/webpack.config.dev.js index 3a12f12e..2d5c1c8a 100644 --- a/client2/scripts/webpack/webpack.config.dev.js +++ b/client2/scripts/webpack/webpack.config.dev.js @@ -7,10 +7,11 @@ const Webpack = require('webpack'); const { getDevServerConfig } = require('./helpers'); const baseConfig = require('./webpack.config.base'); +const devHost = process.env.DEV_HOST const target = getDevServerConfig(); const options = { - target: `http://${target.host}:${target.port}`, // target host + target: devHost || `http://${target.host}:${target.port}`, // target host changeOrigin: true, // needed for virtual hosted sites }; const apiProxy = proxy.createProxyMiddleware(options); diff --git a/client2/scripts/webpack/webpack.config.prod.js b/client2/scripts/webpack/webpack.config.prod.js index 958c7e68..c0bddae4 100644 --- a/client2/scripts/webpack/webpack.config.prod.js +++ b/client2/scripts/webpack/webpack.config.prod.js @@ -46,9 +46,6 @@ module.exports = merge(baseConfig, { }, use: [{ loader: MiniCssExtractPlugin.loader, - options: { - esModules: true, - } }, 'css-loader', 'postcss-loader', { loader: 'less-loader', options: { @@ -62,9 +59,6 @@ module.exports = merge(baseConfig, { use: [ { loader: MiniCssExtractPlugin.loader, - options: { - esModules: true, - } }, { loader: 'css-loader', diff --git a/client2/src/components/App.tsx b/client2/src/components/App.tsx deleted file mode 100644 index f27d5c17..00000000 --- a/client2/src/components/App.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { observer } from 'mobx-react-lite'; -import React, { FC, useContext } from 'react'; -import Store from 'Store'; -import Icons from 'Lib/theme/Icons'; - -const App: FC = observer(() => { - const store = useContext(Store); - return ( -
    - {store.ui.currentLang} - -
    - ); -}); - -export default App; diff --git a/client2/src/components/App/App.tsx b/client2/src/components/App/App.tsx new file mode 100644 index 00000000..cddb50c6 --- /dev/null +++ b/client2/src/components/App/App.tsx @@ -0,0 +1,20 @@ +import React, { FC } from 'react'; +import { BrowserRouter } from 'react-router-dom'; + +import Icons from 'Common/ui/Icons'; +import Routes from './Routes'; + +import { ErrorBoundary } from './Errors'; + +const App: FC = () => { + return ( + + + + + + + ); +}; + +export default App; diff --git a/client2/src/components/App/Dashboard/Dashboard.tsx b/client2/src/components/App/Dashboard/Dashboard.tsx new file mode 100644 index 00000000..02846ca1 --- /dev/null +++ b/client2/src/components/App/Dashboard/Dashboard.tsx @@ -0,0 +1,136 @@ +import React, { FC, useContext } from 'react'; +import { Row, Col } from 'antd'; +import { observer } from 'mobx-react-lite'; + +import Store from 'Store'; +import { InnerLayout } from 'Common/ui/layouts'; +import theme from 'Lib/theme'; +import { BlockCard, TopDomains, BlockedQueries, TopClients, ServerStatistics } from './components'; + +const Dashboard:FC = observer(() => { + const store = useContext(Store); + const { + dashboard: { stats, filteringConfig }, + system: { status }, + ui: { intl }, + } = store; + + if (!stats || !filteringConfig) { + return null; + } + + const { + numBlockedFiltering, + numReplacedParental, + numReplacedSafebrowsing, + replacedParental, + replacedSafebrowsing, + avgProcessingTime, + blockedFiltering, + + topBlockedDomains, + topQueriedDomains, + dnsQueries, + numDnsQueries, + + } = stats; + + const { filters } = filteringConfig!; + const allFilters = filters?.length; + const allRules = filters?.reduce((prev, e) => prev + (e.rulesCount || 0), 0); + const enabled = filters?.filter((e) => e.enabled).length; + + return ( + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {/* TODO: fix chart */} + + + + + +
    +
    + ); +}); + +export default Dashboard; diff --git a/client2/src/components/App/Dashboard/components/BlockCard/BlockCard.module.pcss b/client2/src/components/App/Dashboard/components/BlockCard/BlockCard.module.pcss new file mode 100644 index 00000000..62eb6b4f --- /dev/null +++ b/client2/src/components/App/Dashboard/components/BlockCard/BlockCard.module.pcss @@ -0,0 +1,20 @@ +.container { + display: flex; + flex-flow: column; + padding: 24px; + background-color: var(--white); +} + +.title { + font-size: 14px; + line-height: 22px; + margin-bottom: 4px; + color: var(--gray700); +} + +.overal { + font-size: 30px; + line-height: 38px; + margin-bottom: 18px; + color: var(--gray900); +} \ No newline at end of file diff --git a/client2/src/components/App/Dashboard/components/BlockCard/BlockCard.tsx b/client2/src/components/App/Dashboard/components/BlockCard/BlockCard.tsx new file mode 100644 index 00000000..b962db01 --- /dev/null +++ b/client2/src/components/App/Dashboard/components/BlockCard/BlockCard.tsx @@ -0,0 +1,35 @@ +import React, { FC } from 'react'; +import { AreaChart, Area, ResponsiveContainer } from 'recharts'; + +import s from './BlockCard.module.pcss'; + +interface BlockCardProps { + overal: number | string; + data?: number[]; + text?: string; + color?: string; + title: string; +} + +const BlockCard: FC = ({ overal, data, color, title, text }) => { + return ( +
    +
    {title}
    +
    {overal}
    + {data && ( + + ({ name: 'data', value: n }))}> + + + + )} + {text && ( +
    + {text} +
    + )} +
    + ); +}; + +export default BlockCard; diff --git a/client2/src/components/App/Dashboard/components/BlockCard/index.ts b/client2/src/components/App/Dashboard/components/BlockCard/index.ts new file mode 100644 index 00000000..085552ae --- /dev/null +++ b/client2/src/components/App/Dashboard/components/BlockCard/index.ts @@ -0,0 +1 @@ +export { default as BlockCard } from './BlockCard'; diff --git a/client2/src/components/App/Dashboard/components/BlockedQueries/BlockedQueries.module.pcss b/client2/src/components/App/Dashboard/components/BlockedQueries/BlockedQueries.module.pcss new file mode 100644 index 00000000..7520f474 --- /dev/null +++ b/client2/src/components/App/Dashboard/components/BlockedQueries/BlockedQueries.module.pcss @@ -0,0 +1,16 @@ +.container { + display: flex; + flex-flow: column; + padding: 24px; + background-color: var(--white); +} + +.title { + font-size: 14px; + line-height: 22px; + margin-bottom: 4px; + color: var(--gray700); +} +.pie { + padding: 34px 0px; +} \ No newline at end of file diff --git a/client2/src/components/App/Dashboard/components/BlockedQueries/BlockedQueries.tsx b/client2/src/components/App/Dashboard/components/BlockedQueries/BlockedQueries.tsx new file mode 100644 index 00000000..7b8eec9d --- /dev/null +++ b/client2/src/components/App/Dashboard/components/BlockedQueries/BlockedQueries.tsx @@ -0,0 +1,76 @@ +import theme from 'Lib/theme'; +import React, { FC, useContext, useState } from 'react'; +import { PieChart, Pie, ResponsiveContainer, Sector, Cell } from 'recharts'; + +import Store from 'Store'; + +import s from './BlockedQueries.module.pcss'; + +interface BlockCardProps { + ads: number; + trackers: number; + other: number; +} + +const renderActiveShape = (props: any): any => { + const { + cx, cy, innerRadius, outerRadius, startAngle, endAngle, + fill, payload, percent, + } = props; + return ( + + {payload.name} + {Math.round(percent * 100)}% + + + ); +}; + +const BlockedQueries: FC = ({ ads, trackers, other }) => { + const store = useContext(Store); + const [activeIndex, setActiveIndex] = useState(0); + const { ui: { intl } } = store; + const data = [ + { name: intl.getMessage('other'), value: other, color: theme.chartColors.gray700 }, + { name: intl.getMessage('ads'), value: ads, color: theme.chartColors.red }, + { name: intl.getMessage('trackers'), value: trackers, color: theme.chartColors.orange }, + ]; + const onChart: any = (_: any, index: number) => { + setActiveIndex(index); + }; + return ( +
    +
    {intl.getMessage('dashboard_blocked_queries')}
    +
    + + + + {data.map((entry, index) => ( + + ))} + + + +
    +
    + ); +}; + +export default BlockedQueries; diff --git a/client2/src/components/App/Dashboard/components/BlockedQueries/index.ts b/client2/src/components/App/Dashboard/components/BlockedQueries/index.ts new file mode 100644 index 00000000..019ab9c9 --- /dev/null +++ b/client2/src/components/App/Dashboard/components/BlockedQueries/index.ts @@ -0,0 +1 @@ +export { default as BlockedQueries } from './BlockedQueries'; diff --git a/client2/src/components/App/Dashboard/components/ServerStatistics/ServerStatistics.module.pcss b/client2/src/components/App/Dashboard/components/ServerStatistics/ServerStatistics.module.pcss new file mode 100644 index 00000000..cc8003da --- /dev/null +++ b/client2/src/components/App/Dashboard/components/ServerStatistics/ServerStatistics.module.pcss @@ -0,0 +1,46 @@ +.container { + display: flex; + flex-flow: column; + background-color: var(--white); + margin-top: 24px; +} + +.title { + padding: 24px; + font-size: 16px; + font-weight: 500; + line-height: 22px; + border-bottom: 1px solid var(--gray300); + color: var(--gray900); +} + +.card { + padding: 24px; + height: 100%; +} + +.cardBorder { + border-right: 1px solid var(--gray300); + + &:last-of-type { + border-right: 0; + } +} + +.cardTitle { + font-weight: 500; + margin-bottom: 12px; +} + +.cardDesc { + color: var(--gray700); +} + +.cardValue { + color: var(--gray900); + font-size: 30px; +} + +.chart { + margin-top: 24px; +} \ No newline at end of file diff --git a/client2/src/components/App/Dashboard/components/ServerStatistics/ServerStatistics.tsx b/client2/src/components/App/Dashboard/components/ServerStatistics/ServerStatistics.tsx new file mode 100644 index 00000000..1ec98476 --- /dev/null +++ b/client2/src/components/App/Dashboard/components/ServerStatistics/ServerStatistics.tsx @@ -0,0 +1,89 @@ +import React, { FC, useContext } from 'react'; +import { Row, Col } from 'antd'; +import { AreaChart, Area, ResponsiveContainer } from 'recharts'; + +import Store from 'Store'; +import theme from 'Lib/theme'; + +import s from './ServerStatistics.module.pcss'; + +const ServerStatistics: FC = () => { + const store = useContext(Store); + const { ui: { intl } } = store; + + const data = [0, 10, 2, 14, 12, 24, 5, 8, 10, 0, 3, 5, 7, 8, 3]; + return ( +
    +
    {intl.getMessage('dashboard_server_statistics')}
    + + +
    +
    + Average server load +
    +
    +
    + Processes: 213 +
    +
    + Cores: 2 +
    +
    + + ({ name: 'data', value: n }))}> + + + +
    + + +
    +
    + Memory usage +
    +
    + 236 Mb +
    + + ({ name: 'data', value: n }))}> + + + +
    + + +
    +
    + DNS cashe size +
    +
    + 2 363 records +
    +
    +
    + 32 Mb +
    +
    +
    + + +
    +
    + Upstream servers data +
    +
    +
    + Processes: 213 +
    +
    + Cores: 2 +
    +
    +
    + +
    +
    + ); +}; + +export default ServerStatistics; diff --git a/client2/src/components/App/Dashboard/components/ServerStatistics/index.ts b/client2/src/components/App/Dashboard/components/ServerStatistics/index.ts new file mode 100644 index 00000000..98228599 --- /dev/null +++ b/client2/src/components/App/Dashboard/components/ServerStatistics/index.ts @@ -0,0 +1 @@ +export { default as ServerStatistics } from './ServerStatistics'; diff --git a/client2/src/components/App/Dashboard/components/TopClients/TopClients.module.pcss b/client2/src/components/App/Dashboard/components/TopClients/TopClients.module.pcss new file mode 100644 index 00000000..d4b4b01a --- /dev/null +++ b/client2/src/components/App/Dashboard/components/TopClients/TopClients.module.pcss @@ -0,0 +1,43 @@ +.container { + display: flex; + flex-flow: column; + background-color: var(--white); +} + +.title { + font-size: 16px; + line-height: 22px; + margin-bottom: 4px; + padding: 24px; + color: var(--gray900); +} + +.table { + position: relative; +} + +.tableTitle { + color: var(--gray700); + background-color: #fafafa; + padding: 24px; + position: sticky; + top: 0; +} + +.tableGrid { + display: grid; + grid-template-columns: 4fr 1fr 1fr 1.5fr 1fr .5fr; + padding: 16px 24px; + border-bottom: 1px solid var(--gray300); + + &:last-of-type { + border-bottom: 0; + } + + > div { + align-self: center; + } +} +.ids { + color: var(--gray700) +} \ No newline at end of file diff --git a/client2/src/components/App/Dashboard/components/TopClients/TopClients.tsx b/client2/src/components/App/Dashboard/components/TopClients/TopClients.tsx new file mode 100644 index 00000000..9e743964 --- /dev/null +++ b/client2/src/components/App/Dashboard/components/TopClients/TopClients.tsx @@ -0,0 +1,71 @@ +import React, { FC, useContext } from 'react'; +import { Button } from 'antd'; +import cn from 'classnames'; +import { observer } from 'mobx-react-lite'; + +import Store from 'Store'; + +import s from './TopClients.module.pcss'; + +const TopClients: FC = observer(() => { + const store = useContext(Store); + const { ui: { intl }, dashboard } = store; + const { clientsInfo, stats } = dashboard; + const topClients = new Map(); + stats?.topClients?.forEach((client) => { + const [id, requests] = Object.entries(client.numberData); + topClients.set(id, requests); + }); + const clients = Array.from(clientsInfo.entries()); + + return ( +
    +
    {intl.getMessage('Top Clients')}
    +
    +
    +
    {intl.getMessage('client_table_header')}
    +
    {intl.getMessage('requests')}
    +
    {intl.getMessage('show_blocked_responses')}
    +
    %
    +
    +
    +
    + {clients.map(([id, c]) => { + const request = topClients.get(id); + return ( +
    +
    + {c.name} +
    + {c.ids?.map((cid) => ( +
    {cid}
    + ))} +
    +
    +
    + {request} +
    +
    + API + {/* TODO: api */} +
    +
    + API / {request} +
    +
    + +
    +
    + ... +
    +
    + ); + })} +
    +
    + ); +}); + +export default TopClients; diff --git a/client2/src/components/App/Dashboard/components/TopClients/index.ts b/client2/src/components/App/Dashboard/components/TopClients/index.ts new file mode 100644 index 00000000..b495d493 --- /dev/null +++ b/client2/src/components/App/Dashboard/components/TopClients/index.ts @@ -0,0 +1 @@ +export { default as TopClients } from './TopClients'; diff --git a/client2/src/components/App/Dashboard/components/TopDomains/TopDomains.module.pcss b/client2/src/components/App/Dashboard/components/TopDomains/TopDomains.module.pcss new file mode 100644 index 00000000..b61ab72a --- /dev/null +++ b/client2/src/components/App/Dashboard/components/TopDomains/TopDomains.module.pcss @@ -0,0 +1,62 @@ +.container { + display: flex; + flex-flow: column; + background-color: var(--white); +} + +.title { + padding: 24px; + font-size: 16px; + font-weight: 500; + line-height: 22px; + border-bottom: 1px solid var(--gray300); + margin-bottom: 16px; + color: var(--gray900); +} + +.content { + padding: 24px; + +} + +.overal { + font-size: 24px; + line-height: 32px; + margin-bottom: 24px; + color: var(--gray900); +} + +.table { + position: relative; + overflow-y: auto; + max-height: 280px; + width: 100%; +} + +.tableHeader { + /* TODO: color */ + position: sticky; + top: 0; + width: inherit; + background-color: #fafafa; + font-weight: 500; + z-index: 10; +} + +.tableRow { + display: grid; + grid-template-columns: 3fr 1fr 1.5fr; + grid-column-gap: 10px; + padding: 8px 16px; + border-bottom: 1px solid var(--gray300); +} + +.domain { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; +} + +.progress { + display: flex; +} \ No newline at end of file diff --git a/client2/src/components/App/Dashboard/components/TopDomains/TopDomains.tsx b/client2/src/components/App/Dashboard/components/TopDomains/TopDomains.tsx new file mode 100644 index 00000000..35fe686f --- /dev/null +++ b/client2/src/components/App/Dashboard/components/TopDomains/TopDomains.tsx @@ -0,0 +1,73 @@ +import React, { FC, useContext } from 'react'; +import { Progress } from 'antd'; +import cn from 'classnames'; +import { AreaChart, Area, ResponsiveContainer } from 'recharts'; + +import TopArrayEntry from 'Entities/TopArrayEntry'; +import theme from 'Lib/theme'; +import Store from 'Store'; + +import s from './TopDomains.module.pcss'; + +interface TopDomainsProps { + title: string; + overal: number; + chartData: number[]; + tableData: TopArrayEntry[]; + color: string; + useValueColor?: boolean; +} + +const TopDomains: FC = ( + { title, overal, chartData, tableData, color, useValueColor }, +) => { + const store = useContext(Store); + const { ui: { intl } } = store; + const data = tableData.map((e) => { + const [domain, value] = Object.entries(e.numberData)[0]; + return { domain, value }; + }); + + return ( +
    +
    {title}
    +
    +
    + {overal.toLocaleString('en')} + + ({ name: 'data', value: n }))}> + + + +
    +
    +
    +
    + {intl.getMessage('domain')} +
    +
    + {intl.getMessage('all_queries')} +
    +
    + % +
    +
    + {data.map(({ domain, value }) => ( +
    +
    {domain}
    +
    {value}
    + +
    + ))} +
    +
    +
    + ); +}; + +export default TopDomains; diff --git a/client2/src/components/App/Dashboard/components/TopDomains/index.ts b/client2/src/components/App/Dashboard/components/TopDomains/index.ts new file mode 100644 index 00000000..79fcc1b8 --- /dev/null +++ b/client2/src/components/App/Dashboard/components/TopDomains/index.ts @@ -0,0 +1 @@ +export { default as TopDomains } from './TopDomains'; diff --git a/client2/src/components/App/Dashboard/components/index.ts b/client2/src/components/App/Dashboard/components/index.ts new file mode 100644 index 00000000..9fc2523c --- /dev/null +++ b/client2/src/components/App/Dashboard/components/index.ts @@ -0,0 +1,5 @@ +export { BlockCard } from './BlockCard'; +export { TopClients } from './TopClients'; +export { TopDomains } from './TopDomains'; +export { BlockedQueries } from './BlockedQueries'; +export { ServerStatistics } from './ServerStatistics'; diff --git a/client2/src/components/App/Dashboard/index.ts b/client2/src/components/App/Dashboard/index.ts new file mode 100644 index 00000000..449ae567 --- /dev/null +++ b/client2/src/components/App/Dashboard/index.ts @@ -0,0 +1 @@ +export { default } from './Dashboard'; diff --git a/client2/src/components/App/Errors/ErrorBoundary.tsx b/client2/src/components/App/Errors/ErrorBoundary.tsx new file mode 100644 index 00000000..42204783 --- /dev/null +++ b/client2/src/components/App/Errors/ErrorBoundary.tsx @@ -0,0 +1,31 @@ +import React, { Component, ReactNode } from 'react'; +import cn from 'classnames'; + +import s from './Errors.module.pcss'; + +export default class ErrorBoundary extends Component { + state = { + isError: false, + }; + + static getDerivedStateFromError(): { isError: boolean } { + return { isError: true }; + } + + render(): ReactNode { + const { isError } = this.state; + const { children } = this.props; + + if (isError) { + return ( +
    +
    + Something went wrong +
    +
    + ); + } + + return children; + } +} diff --git a/client2/src/components/App/Errors/Errors.module.pcss b/client2/src/components/App/Errors/Errors.module.pcss new file mode 100644 index 00000000..92e6f908 --- /dev/null +++ b/client2/src/components/App/Errors/Errors.module.pcss @@ -0,0 +1,79 @@ +.content { + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + max-width: 455px; + min-height: calc(100vh - var(--header-height) - 64px); + margin: 0 auto; + text-align: center; + + &_boundary { + min-height: 100vh; + } +} + +.title { + margin-bottom: 8px; + font-size: 18px; + font-weight: 500; + + @media (--s-viewport) { + margin-bottom: 20px; + font-size: 24px; + } +} + +.code { + position: relative; + margin-bottom: 32px; + font-size: 120px; + font-weight: 700; + line-height: 108px; + color: var(--morning); + user-select: none; + + @media (--s-viewport) { + margin-bottom: 54px; + font-size: 180px; + line-height: 162px; + } +} + +.warning { + width: 160px; + height: 173px; + + @media (--s-viewport) { + width: 243px; + height: 262px; + } + + &_code { + position: absolute; + top: -20px; + left: 50%; + transform: translateX(-50%); + + @media (--s-viewport) { + top: -34px; + } + } +} + +.error { + margin-bottom: 10px; + cursor: pointer; +} + +.desc { + margin-bottom: 8px; + max-width: 384px; + font-size: 13px; + color: var(--gray); + + @media (--s-viewport) { + margin-bottom: 20px; + font-size: 14px; + } +} diff --git a/client2/src/components/App/Errors/index.ts b/client2/src/components/App/Errors/index.ts new file mode 100644 index 00000000..e5d6dda2 --- /dev/null +++ b/client2/src/components/App/Errors/index.ts @@ -0,0 +1 @@ +export { default as ErrorBoundary } from './ErrorBoundary'; diff --git a/client2/src/components/App/Header/Header.module.pcss b/client2/src/components/App/Header/Header.module.pcss new file mode 100644 index 00000000..78f6f067 --- /dev/null +++ b/client2/src/components/App/Header/Header.module.pcss @@ -0,0 +1,81 @@ +.header { + position: relative; + z-index: 1; + color: var(--gray900); + background-color: var(--white); + box-shadow: 0 1px 4px 0 rgba(0, 21, 41, 0.12); +} + +.top, +.bottom { + padding: 12px 16px; + + @media (--l-viewport) { + padding: 12px 32px; + } +} + +.top { + background-color: var(--black); + + @media (--l-viewport) { + display: none; + } +} + +.bottom { + display: flex; + flex-direction: column; + + @media (--l-viewport) { + align-items: center; + flex-direction: row; + height: var(--header-height); + } +} + +.icon { + margin-right: 10px; +} + +.status { + display: flex; + align-items: center; + margin-bottom: 12px; + + @media (--l-viewport) { + margin: 0 16px 0 0; + } +} + +.action { + min-width: 80px; + margin-right: auto; +} + +.languages, +.user { + display: none; + + @media (--l-viewport) { + display: flex; + align-items: center; + } +} + +.user { + margin-right: 32px; +} + +.menu { + color: var(--white); + background-color: transparent; + border: 0; + + &:hover, + &:focus, + &:active { + color: var(--gray400); + background-color: transparent; + } +} diff --git a/client2/src/components/App/Header/Header.tsx b/client2/src/components/App/Header/Header.tsx new file mode 100644 index 00000000..d5da7456 --- /dev/null +++ b/client2/src/components/App/Header/Header.tsx @@ -0,0 +1,60 @@ +import React, { FC, useContext } from 'react'; +import { Button } from 'antd'; +import { MenuOutlined } from '@ant-design/icons'; +import { observer } from 'mobx-react-lite'; + +import { Icon, LangSelect } from 'Common/ui'; +import Store from 'Store'; + +import s from './Header.module.pcss'; + +const Header: FC = observer(() => { + const store = useContext(Store); + const { ui: { intl }, system, ui } = store; + const { status, profile } = system; + + const updateServerStatus = () => { + system.switchServerStatus(!status?.protectionEnabled); + }; + + return ( +
    +
    +
    +
    +
    + + {status?.protectionEnabled + ? intl.getMessage('header_adguard_status_enabled') + : intl.getMessage('header_adguard_status_disabled')} +
    + + {profile?.name && ( +
    + + {profile?.name} +
    + )} +
    + +
    +
    +
    + ); +}); + +export default Header; diff --git a/client2/src/components/App/Header/index.ts b/client2/src/components/App/Header/index.ts new file mode 100644 index 00000000..579f1ac2 --- /dev/null +++ b/client2/src/components/App/Header/index.ts @@ -0,0 +1 @@ +export { default } from './Header'; diff --git a/client2/src/components/App/Login/ForgotPassword.tsx b/client2/src/components/App/Login/ForgotPassword.tsx new file mode 100644 index 00000000..f19b8a60 --- /dev/null +++ b/client2/src/components/App/Login/ForgotPassword.tsx @@ -0,0 +1,65 @@ +import React, { FC, useContext } from 'react'; +import { Button } from 'antd'; +import cn from 'classnames'; + +import { CommonLayout } from 'Common/ui/layouts'; +import { code } from 'Common/formating'; +import { Link } from 'Common/ui'; +import Store from 'Store'; +import theme from 'Lib/theme'; + +import s from './Login.module.pcss'; +import { RoutePath } from '../Routes/Paths'; + +const ForgotPassword: FC = () => { + const store = useContext(Store); + const { ui: { intl } } = store; + + return ( + +
    +
    + {intl.getMessage('login_password_title')} +
    + +

    + {intl.getMessage('login_password_hash')} +

    + +
    +
    + {intl.getMessage('login_password_step_1')} +
    +
    + {intl.getMessage('login_password_step_2', { code })} +
    +
    + {intl.getMessage('login_password_step_3', { code })} +
    +
    + {intl.getMessage('login_password_step_4')} +
    +
    + {intl.getMessage('login_password_step_5')} +
    +
    + +

    + {intl.getMessage('login_password_result')} +

    + + + + +
    +
    + ); +}; + +export default ForgotPassword; diff --git a/client2/src/components/App/Login/Login.module.pcss b/client2/src/components/App/Login/Login.module.pcss new file mode 100644 index 00000000..ae887cd9 --- /dev/null +++ b/client2/src/components/App/Login/Login.module.pcss @@ -0,0 +1,34 @@ +.title { + margin-bottom: 12px; + font-size: 28px; + text-align: center; + + &_form { + margin-bottom: 32px; + } +} + +.link { + display: inline-block; + vertical-align: middle; + margin-top: 32px; + font-size: 16px; + text-align: center; +} + +.paragraph { + font-size: 16px; + margin: 0 0 14px; +} + +.list { + margin-bottom: 16px; + padding-left: 20px; + font-size: 16px; +} + +.step { + margin-bottom: 5px; + display: list-item; + list-style: decimal; +} diff --git a/client2/src/components/App/Login/Login.tsx b/client2/src/components/App/Login/Login.tsx new file mode 100644 index 00000000..844e03f9 --- /dev/null +++ b/client2/src/components/App/Login/Login.tsx @@ -0,0 +1,102 @@ +import React, { FC, useContext } from 'react'; +import { Button } from 'antd'; +import { observer } from 'mobx-react-lite'; +import { Formik, FormikHelpers } from 'formik'; +import cn from 'classnames'; + +import { Input } from 'Common/controls'; +import { CommonLayout } from 'Common/ui/layouts'; +import { Link } from 'Common/ui'; +import { RoutePath } from 'Components/App/Routes/Paths'; +import Store from 'Store'; +import theme from 'Lib/theme'; + +import s from './Login.module.pcss'; + +type FormValues = { + name: string; + password: string; +}; + +const Login: FC = observer(() => { + const store = useContext(Store); + const { ui: { intl }, login } = store; + + const onSubmit = async (values: FormValues, { setSubmitting }: FormikHelpers) => { + const { name, password } = values; + + const error = await login.login({ + name, + password, + }); + + if (error) { + setSubmitting(false); + } + }; + + const initialValues: FormValues = { + name: '', + password: '', + }; + + return ( + +
    +
    + {intl.getMessage('login')} +
    + + + {({ + values, + handleSubmit, + setFieldValue, + isSubmitting, + }) => ( +
    + setFieldValue('name', v)} + autoFocus + /> + setFieldValue('password', v)} + /> + +
    + )} +
    + +
    + + {intl.getMessage('login_password_link')} + +
    +
    +
    + ); +}); + +export default Login; diff --git a/client2/src/components/App/Login/index.ts b/client2/src/components/App/Login/index.ts new file mode 100644 index 00000000..3da6e50e --- /dev/null +++ b/client2/src/components/App/Login/index.ts @@ -0,0 +1,2 @@ +export { default as Login } from './Login'; +export { default as ForgotPassword } from './ForgotPassword'; diff --git a/client2/src/components/App/Routes/Paths.ts b/client2/src/components/App/Routes/Paths.ts new file mode 100644 index 00000000..f722a790 --- /dev/null +++ b/client2/src/components/App/Routes/Paths.ts @@ -0,0 +1,63 @@ +import qs from 'qs'; +import { Locale } from 'Localization'; + +const BasicPath = '/'; +const pathBuilder = (path: string) => (`${BasicPath}${path}`); + +export enum RoutePath { + Dashboard = 'Dashboard', + FiltersBlocklist = 'FiltersBlocklist', + FiltersAllowlist = 'FiltersAllowlist', + FiltersRewrites = 'FiltersRewrites', + FiltersServices = 'FiltersServices', + FiltersCustom = 'FiltersCustom', + QueryLog = 'QueryLog', + SetupGuide = 'SetupGuide', + SettingsGeneral = 'SettingsGeneral', + SettingsDns = 'SettingsDns', + SettingsEncryption = 'SettingsEncryption', + SettingsClients = 'SettingsClients', + SettingsDhcp = 'SettingsDhcp', + Login = 'Login', + ForgotPassword = 'ForgotPassword', +} + +export const Paths: Record = { + Dashboard: pathBuilder('dashboard'), + FiltersBlocklist: pathBuilder('filters/blocklists'), + FiltersAllowlist: pathBuilder('filters/allowlists'), + FiltersRewrites: pathBuilder('filters/rewrites'), + FiltersServices: pathBuilder('filters/services'), + FiltersCustom: pathBuilder('filters/custom'), + QueryLog: pathBuilder('logs'), + SetupGuide: pathBuilder('guide'), + SettingsGeneral: pathBuilder('settings/general'), + SettingsDns: pathBuilder('settings/dns'), + SettingsEncryption: pathBuilder('settings/encryption'), + SettingsClients: pathBuilder('settings/clients'), + SettingsDhcp: pathBuilder('settings/dhcp'), + Login: pathBuilder(''), + ForgotPassword: pathBuilder('forgot_password'), +}; + +export enum LinkParamsKeys {} +export enum QueryParams {} +export type LinkParams = Partial>; + +export const linkPathBuilder = ( + route: RoutePath, + params?: LinkParams, + lang?: Locale, + query?: Partial>, +) => { + let path = Paths[route]; // .replace(BasicPath, `/${lang}`); + if (params) { + Object.keys(params).forEach((key: unknown) => { + path = path.replace(`:${key}`, String(params[key as LinkParamsKeys])); + }); + } + if (query) { + path += `?${qs.stringify(query)}`; + } + return path; +}; diff --git a/client2/src/components/App/Routes/Routes.module.pcss b/client2/src/components/App/Routes/Routes.module.pcss new file mode 100644 index 00000000..e1561e76 --- /dev/null +++ b/client2/src/components/App/Routes/Routes.module.pcss @@ -0,0 +1,3 @@ +.app { + min-height: 100vh; +} \ No newline at end of file diff --git a/client2/src/components/App/Routes/Routes.tsx b/client2/src/components/App/Routes/Routes.tsx new file mode 100644 index 00000000..ad6df6cd --- /dev/null +++ b/client2/src/components/App/Routes/Routes.tsx @@ -0,0 +1,76 @@ +import React, { FC, useContext } from 'react'; +import { Layout } from 'antd'; +import { Switch, Route, Redirect } from 'react-router-dom'; +import { observer } from 'mobx-react-lite'; + +import Store from 'Store'; +import { Paths } from './Paths'; + +import Dashboard from '../Dashboard'; +import { Login, ForgotPassword } from '../Login'; +import Sidebar from '../Sidebar'; +import Header from '../Header'; +import SetupGuide from '../SetupGuide'; +import { GeneralSettings } from '../Settings'; + +import s from './Routes.module.pcss'; + +const { Content } = Layout; + +const AuthRoutes: FC = React.memo(() => { + return ( + + + + + ); +}); + +const AppRoutes: FC = observer(() => { + return ( + + + +
    + + + + + + + + + + + ); +}); + +const Routes: FC = observer(() => { + const store = useContext(Store); + const { login: { loggedIn } } = store; + if (loggedIn) { + return ; + } + return ; +}); + +export default Routes; diff --git a/client2/src/components/App/Routes/index.ts b/client2/src/components/App/Routes/index.ts new file mode 100644 index 00000000..edc668a3 --- /dev/null +++ b/client2/src/components/App/Routes/index.ts @@ -0,0 +1 @@ +export { default } from './Routes'; diff --git a/client2/src/components/App/Settings/GeneralSettings/GeneralSettings.tsx b/client2/src/components/App/Settings/GeneralSettings/GeneralSettings.tsx new file mode 100644 index 00000000..00a273bb --- /dev/null +++ b/client2/src/components/App/Settings/GeneralSettings/GeneralSettings.tsx @@ -0,0 +1,52 @@ +import React, { FC, useContext, useEffect } from 'react'; +import { Tabs, Grid } from 'antd'; +import { observer } from 'mobx-react-lite'; + +import { InnerLayout } from 'Common/ui'; +import Store from 'Store'; + +import { General, QueryLog, Statistics, TAB_KEY } from './components'; + +const { useBreakpoint } = Grid; +const { TabPane } = Tabs; + +const GeneralSettings: FC = observer(() => { + const store = useContext(Store); + const { ui: { intl }, generalSettings } = store; + const { inited } = generalSettings; + const screens = useBreakpoint(); + + useEffect(() => { + if (!inited) { + generalSettings.init(); + } + }, [inited]); + + if (!inited) { + return null; + } + + const tabsPosition = screens.lg ? 'left' : 'top'; + + return ( + + + + + + + + + + + + + + ); +}); + +export default GeneralSettings; diff --git a/client2/src/components/App/Settings/GeneralSettings/components/Common.module.pcss b/client2/src/components/App/Settings/GeneralSettings/components/Common.module.pcss new file mode 100644 index 00000000..e1570e85 --- /dev/null +++ b/client2/src/components/App/Settings/GeneralSettings/components/Common.module.pcss @@ -0,0 +1,45 @@ +.title { + font-size: 20px; + font-weight: 500; + color: var(--gray900); + margin-bottom: 48px; + display: flex; + justify-content: space-between; +} + +.radio { + display: block; + height: 30px; + line-height: 30px; + + &:first-of-type { + margin-top: -12px; + } +} +.save { + display: block; + margin-top: 24px; +} + +.item { + display: flex; + justify-content: space-between; + margin-bottom: 24px; +} + +.nameTitle { + color: var(--black); +} +.nameDesc { + color: var(--gray700); + margin-right: 40px; + + @media (--m-viewport) { + margin-right: 200px; + } +} +.select { + margin-bottom: 24px; + margin-top: -12px; + width: 200px; +} \ No newline at end of file diff --git a/client2/src/components/App/Settings/GeneralSettings/components/General.tsx b/client2/src/components/App/Settings/GeneralSettings/components/General.tsx new file mode 100644 index 00000000..72847496 --- /dev/null +++ b/client2/src/components/App/Settings/GeneralSettings/components/General.tsx @@ -0,0 +1,169 @@ +import React, { FC, useContext } from 'react'; +import { Button, Switch, Select } from 'antd'; +import { Formik, FormikHelpers } from 'formik'; +import { observer } from 'mobx-react-lite'; + +import { Link } from 'Common/ui'; +import Store from 'Store'; +import { RoutePath } from 'Paths'; + +import { s } from '.'; + +const { Option } = Select; + +const General: FC = observer(() => { + const store = useContext(Store); + const { ui: { intl }, generalSettings } = store; + const { + safebrowsing, + filteringConfig, + parental, + safesearch, + } = generalSettings; + + const initialValues = { + ...filteringConfig!.serialize(), + safebrowsing, + parental, + safesearch, + }; + + type InitialValues = typeof initialValues; + + const onSubmit = async (values: InitialValues, helpers: FormikHelpers) => { + // await generalSettings.updateQueryLogConfig(values); + if (initialValues.parental !== values.parental) { + generalSettings[values.parental ? 'parentalEnable' : 'parentalDisable'](); + } + if (initialValues.safesearch !== values.safesearch) { + generalSettings[values.safesearch ? 'safebrowsingEnable' : 'safebrowsingDisable'](); + } + if (initialValues.safebrowsing !== values.safebrowsing) { + generalSettings[values.safebrowsing ? 'safebrowsingEnable' : 'safebrowsingDisable'](); + } + if (initialValues.enabled !== values.enabled + || initialValues.interval !== values.interval) { + generalSettings.updateFilteringConfig({ + interval: values.interval, + enabled: values.enabled, + }); + } + helpers.setSubmitting(false); + }; + + const filtersLink = (e: string) => { + // TODO: fix link + return {e}; + }; + + return ( + <> +
    + {intl.getMessage('filter_category_general')} +
    + + {({ + handleSubmit, + values, + setFieldValue, + isSubmitting, + dirty, + }) => ( +
    +
    +
    +
    + {intl.getMessage('block_domain_use_filters_and_hosts')} +
    +
    + {intl.getMessage('filters_block_toggle_hint', { a: filtersLink })} +
    +
    + setFieldValue('enabled', e)}/> +
    +
    +
    +
    + {intl.getMessage('filters_interval')} +
    +
    +
    + +
    +
    +
    + {intl.getMessage('use_adguard_browsing_sec')} +
    +
    + {intl.getMessage('use_adguard_browsing_sec_hint')} +
    +
    + setFieldValue('safebrowsing', e)}/> +
    +
    +
    +
    + {intl.getMessage('use_adguard_parental')} +
    +
    + {intl.getMessage('use_adguard_parental_hint')} +
    +
    + setFieldValue('parental', e)}/> +
    +
    +
    +
    + {intl.getMessage('enforce_safe_search')} +
    +
    + {intl.getMessage('enforce_save_search_hint')} +
    +
    + setFieldValue('safesearch', e)}/> +
    + {dirty && ( + + )} +
    + )} +
    + + ); +}); + +export default General; diff --git a/client2/src/components/App/Settings/GeneralSettings/components/QueryLog.tsx b/client2/src/components/App/Settings/GeneralSettings/components/QueryLog.tsx new file mode 100644 index 00000000..c13bf599 --- /dev/null +++ b/client2/src/components/App/Settings/GeneralSettings/components/QueryLog.tsx @@ -0,0 +1,124 @@ +import React, { FC, useContext, useState } from 'react'; +import { Radio, Button, Switch } from 'antd'; +import { Formik, FormikHelpers } from 'formik'; +import { observer } from 'mobx-react-lite'; + +import { notifySuccess, ConfirmModalLayout } from 'Common/ui'; +import { IQueryLogConfig } from 'Entities/QueryLogConfig'; +import Store from 'Store'; + +import { s } from '.'; + +const { Group } = Radio; + +const QueryLog: FC = observer(() => { + const store = useContext(Store); + const [showConfirm, setShowConfirm] = useState(false); + const { ui: { intl }, generalSettings } = store; + const { + queryLogConfig, + } = generalSettings; + + const onSubmit = async (values: IQueryLogConfig, helpers: FormikHelpers) => { + await generalSettings.updateQueryLogConfig(values); + helpers.setSubmitting(false); + }; + + const onReset = async () => { + const result = await generalSettings.querylogClear(); + if (result) { + notifySuccess(intl.getMessage('query_log_cleared')); + } + }; + + return ( + <> +
    + {intl.getMessage('query_log_configuration')} + +
    + setShowConfirm(false)} + title={intl.getMessage('query_log_clear')} + buttonText={intl.getMessage('query_log_clear')} + > + {intl.getMessage('query_log_confirm_clear')} + + + {({ + handleSubmit, + values, + setFieldValue, + isSubmitting, + dirty, + }) => ( +
    +
    +
    +
    + {intl.getMessage('query_log_enable')} +
    +
    + setFieldValue('enabled', e)}/> +
    +
    +
    +
    + {intl.getMessage('anonymize_client_ip')} +
    +
    + {intl.getMessage('anonymize_client_ip_desc')} +
    +
    + setFieldValue('anonymize_client_ip', e)}/> +
    +
    +
    +
    + {intl.getMessage('query_log_retention')} +
    +
    + {intl.getMessage('query_log_retention_confirm')} +
    +
    +
    + setFieldValue('interval', e.target.value)}> + + {intl.getMessage('interval_24_hour')} + + + {intl.getPlural('interval_days', 7, { count: 7 })} + + + {intl.getPlural('interval_days', 30, { count: 30 })} + + + {intl.getPlural('interval_days', 90, { count: 90 })} + + + {dirty && ( + + )} +
    + )} +
    + + ); +}); + +export default QueryLog; diff --git a/client2/src/components/App/Settings/GeneralSettings/components/Statistics.tsx b/client2/src/components/App/Settings/GeneralSettings/components/Statistics.tsx new file mode 100644 index 00000000..7b6e2112 --- /dev/null +++ b/client2/src/components/App/Settings/GeneralSettings/components/Statistics.tsx @@ -0,0 +1,105 @@ +import React, { FC, useContext, useState } from 'react'; +import { Radio, Button } from 'antd'; +import { Formik, FormikHelpers } from 'formik'; +import { observer } from 'mobx-react-lite'; + +import { notifySuccess, ConfirmModalLayout } from 'Common/ui'; +import { IStatsConfig } from 'Entities/StatsConfig'; +import Store from 'Store'; + +import { s } from '.'; + +const { Group } = Radio; + +const Statistics: FC = observer(() => { + const store = useContext(Store); + const [showConfirm, setShowConfirm] = useState(false); + const { ui: { intl }, generalSettings } = store; + const { + statsConfig, + } = generalSettings; + + const onSubmit = async (values: IStatsConfig, helpers: FormikHelpers) => { + await generalSettings.updateStatsConfig(values); + helpers.setSubmitting(false); + }; + + const onReset = async () => { + const result = await generalSettings.statsReset(); + if (result) { + notifySuccess(intl.getMessage('stats_reset')); + } + }; + + return ( + <> +
    + {intl.getMessage('statistics_configuration')} + +
    + setShowConfirm(false)} + title={intl.getMessage('statistics_clear')} + buttonText={intl.getMessage('statistics_clear')} + > + {intl.getMessage('statistics_clear_confirm')} + + + {({ + handleSubmit, + values, + setFieldValue, + isSubmitting, + dirty, + }) => ( +
    +
    +
    +
    + {intl.getMessage('statistics_retention')} +
    +
    + {intl.getMessage('statistics_retention_desc')} +
    +
    +
    + setFieldValue('interval', e.target.value)}> + + {intl.getMessage('interval_24_hour')} + + + {intl.getPlural('interval_days', 7, { count: 7 })} + + + {intl.getPlural('interval_days', 30, { count: 30 })} + + + {intl.getPlural('interval_days', 90, { count: 90 })} + + + {dirty && ( + + )} +
    + )} +
    + + ); +}); + +export default Statistics; diff --git a/client2/src/components/App/Settings/GeneralSettings/components/index.ts b/client2/src/components/App/Settings/GeneralSettings/components/index.ts new file mode 100644 index 00000000..872b4789 --- /dev/null +++ b/client2/src/components/App/Settings/GeneralSettings/components/index.ts @@ -0,0 +1,9 @@ +export { default as General } from './General'; +export { default as QueryLog } from './QueryLog'; +export { default as Statistics } from './Statistics'; +export enum TAB_KEY { + GENERAL = 'GENERAL', + QUERY_LOG = 'QUERY_LOG', + STATISTICS = 'STATISTICS', +} +export { default as s } from './Common.module.pcss'; diff --git a/client2/src/components/App/Settings/GeneralSettings/index.ts b/client2/src/components/App/Settings/GeneralSettings/index.ts new file mode 100644 index 00000000..103dbe79 --- /dev/null +++ b/client2/src/components/App/Settings/GeneralSettings/index.ts @@ -0,0 +1 @@ +export { default as GeneralSettings } from './GeneralSettings'; diff --git a/client2/src/components/App/Settings/index.ts b/client2/src/components/App/Settings/index.ts new file mode 100644 index 00000000..dfb1ece8 --- /dev/null +++ b/client2/src/components/App/Settings/index.ts @@ -0,0 +1 @@ +export { GeneralSettings } from './GeneralSettings'; diff --git a/client2/src/components/App/SetupGuide/SetupGuide.module.pcss b/client2/src/components/App/SetupGuide/SetupGuide.module.pcss new file mode 100644 index 00000000..c4ac60c5 --- /dev/null +++ b/client2/src/components/App/SetupGuide/SetupGuide.module.pcss @@ -0,0 +1,31 @@ +.title { + margin-bottom: 16px; + font-size: 20px; + font-weight: 500; + + @media (--m-viewport) { + margin-bottom: 24px; + } +} + +.text { + margin-bottom: 32px; + font-size: 14px; + color: var(--gray900); + + p { + margin: 0 0 5px; + } +} + +.addresses { + margin-top: 16px; +} + +.address { + font-family: var(--font-family-monospace); + font-size: 16px; + font-weight: 600; + word-break: break-all; + color: var(--green400); +} diff --git a/client2/src/components/App/SetupGuide/SetupGuide.tsx b/client2/src/components/App/SetupGuide/SetupGuide.tsx new file mode 100644 index 00000000..f4c5a760 --- /dev/null +++ b/client2/src/components/App/SetupGuide/SetupGuide.tsx @@ -0,0 +1,92 @@ +import React, { FC, useContext } from 'react'; +import { Tabs, Grid } from 'antd'; + +import { InnerLayout } from 'Common/ui'; +import { externalLink, p } from 'Common/formating'; +import { DHCP_LINK } from 'Consts/common'; +import Store from 'Store'; + +import s from './SetupGuide.module.pcss'; + +const { useBreakpoint } = Grid; +const { TabPane } = Tabs; + +const SetupGuide: FC = () => { + const store = useContext(Store); + const { ui: { intl }, system } = store; + const screens = useBreakpoint(); + const tabsPosition = screens.lg ? 'left' : 'top'; + + const { status } = system; + + const tabs = [ + { + key: intl.getMessage('router'), + text: intl.getMessage('install_configure_router', { p }), + }, + { + key: 'Windows', + text: intl.getMessage('install_configure_windows', { p }), + }, + { + key: 'macOS', + text: intl.getMessage('install_configure_macos', { p }), + }, + { + key: 'Linux', + text: intl.getMessage('install_configure_router', { p }), + }, + { + key: 'Android', + text: intl.getMessage('install_configure_android', { p }), + }, + { + key: 'iOS', + text: intl.getMessage('install_configure_ios', { p }), + }, + ]; + + const addresses = ( + <> +
    + {intl.getMessage('install_configure_adresses')} + {status?.dnsAddresses && ( +
    + {status.dnsAddresses.map((address) => ( +
    + {address} +
    + ))} +
    + )} +
    +
    + {intl.getMessage('install_configure_dhcp', { dhcp: externalLink(DHCP_LINK) })} +
    + + ); + + return ( + + + {tabs.map((tab) => ( + +
    + {intl.getMessage('install_configure_how_to_title', { value: tab.key })} +
    +
    + {tab.text} +
    + {addresses} +
    + ))} +
    +
    + ); +}; + +export default SetupGuide; diff --git a/client2/src/components/App/SetupGuide/index.tsx b/client2/src/components/App/SetupGuide/index.tsx new file mode 100644 index 00000000..97a2bb20 --- /dev/null +++ b/client2/src/components/App/SetupGuide/index.tsx @@ -0,0 +1 @@ +export { default } from './SetupGuide'; diff --git a/client2/src/components/App/Sidebar/Sidebar.module.pcss b/client2/src/components/App/Sidebar/Sidebar.module.pcss new file mode 100644 index 00000000..6942d66a --- /dev/null +++ b/client2/src/components/App/Sidebar/Sidebar.module.pcss @@ -0,0 +1,23 @@ +.logo { + width: 118px; + height: 31px; + margin: 20px; +} + +.icon { + width: 16px; + height: 16px; + margin-right: 10px; +} + +.menu { + display: flex; + flex-direction: column; + min-height: calc(100% - 71px); +} + +.logout { + @media (--m-viewport) { + margin-top: auto!important; + } +} diff --git a/client2/src/components/App/Sidebar/Sidebar.tsx b/client2/src/components/App/Sidebar/Sidebar.tsx new file mode 100644 index 00000000..f7485bc7 --- /dev/null +++ b/client2/src/components/App/Sidebar/Sidebar.tsx @@ -0,0 +1,116 @@ +import React, { FC, useContext } from 'react'; +import { Layout, Menu, Grid } from 'antd'; +import { observer } from 'mobx-react-lite'; +import { PieChartOutlined, FormOutlined, TableOutlined, ProfileOutlined, SettingOutlined } from '@ant-design/icons'; + +import Store from 'Store'; +import { Link, Icon, Mask } from 'Common/ui'; +import { RoutePath, linkPathBuilder } from 'Components/App/Routes/Paths'; + +import s from './Sidebar.module.pcss'; + +const { Sider } = Layout; +const { Item: MenuItem, SubMenu } = Menu; +const { useBreakpoint } = Grid; + +const Sidebar: FC = observer(() => { + const store = useContext(Store); + const screens = useBreakpoint(); + const { ui: { intl, sidebarOpen, toggleSidebar } } = store; + + if (!Object.keys(screens).length) { + return null; + } + + const handleSidebar = () => { + if (!screens.xl) { + toggleSidebar(); + } + }; + + return ( + <> + + + + + + + {intl.getMessage('dashboard')} + + + + + + {intl.getMessage('filters')} + + + + + + {intl.getMessage('query_log')} + + + + + + {intl.getMessage('setup_guide')} + + + } + title={intl.getMessage('settings')} + > + + + {intl.getMessage('general_settings')} + + + + + {intl.getMessage('dns_settings')} + + + + + {intl.getMessage('encryption_settings')} + + + + + {intl.getMessage('client_settings')} + + + + + {intl.getMessage('dhcp_settings')} + + + + + + + {intl.getMessage('sign_out')} + + + + + + + ); +}); + +export default Sidebar; diff --git a/client2/src/components/App/Sidebar/index.ts b/client2/src/components/App/Sidebar/index.ts new file mode 100644 index 00000000..e842a859 --- /dev/null +++ b/client2/src/components/App/Sidebar/index.ts @@ -0,0 +1 @@ +export { default } from './Sidebar'; diff --git a/client2/src/components/App/index.ts b/client2/src/components/App/index.ts new file mode 100644 index 00000000..9122fa1a --- /dev/null +++ b/client2/src/components/App/index.ts @@ -0,0 +1 @@ +export { default } from './App'; diff --git a/client2/src/components/Install/Install.tsx b/client2/src/components/Install/Install.tsx index 701ce626..3e7d797e 100644 --- a/client2/src/components/Install/Install.tsx +++ b/client2/src/components/Install/Install.tsx @@ -2,9 +2,10 @@ import React, { FC } from 'react'; import { Layout } from 'antd'; import { Formik, FormikHelpers } from 'formik'; import { observer } from 'mobx-react-lite'; +import cn from 'classnames'; import { IInitialConfigurationBeta } from 'Entities/InitialConfigurationBeta'; -import Icons from 'Lib/theme/Icons'; +import Icons from 'Common/ui/Icons'; import { DEFAULT_DNS_ADDRESS, DEFAULT_DNS_PORT, @@ -109,8 +110,8 @@ const InstallForm: FC = observer(() => { const Install: FC = () => { return ( - - + + diff --git a/client2/src/components/Install/components/ConfigureDevices/ConfigureDevices.tsx b/client2/src/components/Install/components/ConfigureDevices/ConfigureDevices.tsx index 8fbcb933..fc305adf 100644 --- a/client2/src/components/Install/components/ConfigureDevices/ConfigureDevices.tsx +++ b/client2/src/components/Install/components/ConfigureDevices/ConfigureDevices.tsx @@ -3,10 +3,11 @@ import { Tabs, Grid } from 'antd'; import cn from 'classnames'; import { FormikHelpers } from 'formik'; +import { DHCP_LINK } from 'Consts/common'; +import { danger, externalLink, p } from 'Common/formating'; +import { DEFAULT_DNS_PORT, DEFAULT_IP_ADDRESS, DEFAULT_IP_PORT } from 'Consts/install'; import Store from 'Store/installStore'; import theme from 'Lib/theme'; -import { danger, p } from 'Common/formating'; -import { DEFAULT_DNS_PORT, DEFAULT_IP_ADDRESS, DEFAULT_IP_PORT } from 'Consts/install'; import { FormValues } from '../../Install'; import StepButtons from '../StepButtons'; @@ -26,17 +27,6 @@ const ConfigureDevices: FC = ({ const screens = useBreakpoint(); const tabsPosition = screens.md ? 'left' : 'top'; - const dhcp = (e: string) => ( - - {e} - - ); - const allIps = addresses?.interfaces.reduce((all, data) => { const { ipAddresses } = data; if (ipAddresses) { @@ -138,7 +128,7 @@ const ConfigureDevices: FC = ({
    - {intl.getMessage('install_configure_dhcp', { dhcp })} + {intl.getMessage('install_configure_dhcp', { dhcp: externalLink(DHCP_LINK) })}
    ; + onBlur?: (e: FocusEvent) => void; + shape?: ButtonShape; + size?: ButtonSize; + type: ButtonType; + block?: boolean; +} + +const Button: FC = ({ + children, + className, + danger, + dataAttrs, + disabled, + htmlType, + // icon, + id, + onClick, + onBlur, + shape, +}) => { + const buttonClass = cn( + className, + ); + + return ( + + // : )} + id={id} + onClick={onClick} + onBlur={onBlur} + shape={shape} + > + {children} + + ); +}; + +export default Button; diff --git a/client2/src/components/common/controls/Button/index.ts b/client2/src/components/common/controls/Button/index.ts new file mode 100644 index 00000000..efe8c800 --- /dev/null +++ b/client2/src/components/common/controls/Button/index.ts @@ -0,0 +1 @@ +export { default } from './Button'; diff --git a/client2/src/components/common/controls/Radio/Radio.tsx b/client2/src/components/common/controls/Radio/Radio.tsx index dc67100a..5b816858 100644 --- a/client2/src/components/common/controls/Radio/Radio.tsx +++ b/client2/src/components/common/controls/Radio/Radio.tsx @@ -6,7 +6,7 @@ import s from './Radio.module.pcss'; const { Group } = Radio; -interface AdminInterfaceProps { +interface RadioProps { options: { label: string; desc?: string; @@ -16,7 +16,7 @@ interface AdminInterfaceProps { value: string | number; } -const RadioComponent: FC = observer(({ +const RadioComponent: FC = observer(({ options, onSelect, value, }) => { if (options.length === 0) { diff --git a/client2/src/components/common/controls/index.ts b/client2/src/components/common/controls/index.ts index 6ff1fe13..3746e6fe 100644 --- a/client2/src/components/common/controls/index.ts +++ b/client2/src/components/common/controls/index.ts @@ -1,3 +1,4 @@ export { default as Radio } from './Radio'; export { Input } from './Input'; export { Switch } from './Switch'; +export { default as Button } from './Button'; diff --git a/client2/src/components/common/formating/code.tsx b/client2/src/components/common/formating/code.tsx new file mode 100644 index 00000000..60de648c --- /dev/null +++ b/client2/src/components/common/formating/code.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import theme from 'Lib/theme'; + +const code = (e: string) => { + return ( + + {e} + + ); +}; + +export default code; diff --git a/client2/src/components/common/formating/externalLink.tsx b/client2/src/components/common/formating/externalLink.tsx new file mode 100644 index 00000000..14e0cad5 --- /dev/null +++ b/client2/src/components/common/formating/externalLink.tsx @@ -0,0 +1,13 @@ +import React from 'react'; +import theme from 'Lib/theme'; + +export const externalLink = (link: string) => (e: string) => ( + + {e} + +); diff --git a/client2/src/components/common/formating/index.ts b/client2/src/components/common/formating/index.ts index 6bed9429..7b1322b1 100644 --- a/client2/src/components/common/formating/index.ts +++ b/client2/src/components/common/formating/index.ts @@ -1,2 +1,4 @@ export { default as danger } from './danger'; export { default as p } from './p'; +export { default as code } from './code'; +export { externalLink } from './externalLink'; diff --git a/client2/src/components/common/ui/Icon/Icon.tsx b/client2/src/components/common/ui/Icon/Icon.tsx index 55f5b027..261852b3 100644 --- a/client2/src/components/common/ui/Icon/Icon.tsx +++ b/client2/src/components/common/ui/Icon/Icon.tsx @@ -1,6 +1,6 @@ import React, { FC } from 'react'; import cn from 'classnames'; -import { IconType } from 'Lib/theme/Icons'; +import { IconType } from 'Common/ui/Icons'; import s from './Icon.module.pcss'; @@ -22,4 +22,4 @@ const Icon: FC = ({ icon, color, className, onClick }) => { }; export default Icon; -export { IconType } from 'Lib/theme/Icons'; +export { IconType } from 'Common/ui/Icons'; diff --git a/client2/src/lib/theme/Icons/Icon.pcss b/client2/src/components/common/ui/Icons/Icon.pcss similarity index 100% rename from client2/src/lib/theme/Icons/Icon.pcss rename to client2/src/components/common/ui/Icons/Icon.pcss diff --git a/client2/src/lib/theme/Icons/index.tsx b/client2/src/components/common/ui/Icons/index.tsx similarity index 50% rename from client2/src/lib/theme/Icons/index.tsx rename to client2/src/components/common/ui/Icons/index.tsx index a94c2c5c..dcf0e338 100644 --- a/client2/src/lib/theme/Icons/index.tsx +++ b/client2/src/components/common/ui/Icons/index.tsx @@ -4,7 +4,13 @@ import './Icon.pcss'; export type IconType = 'logo' | 'visibility_disable' | - 'visibility_enable'; + 'visibility_enable' | + 'logo_shield' | + 'logo_light' | + 'sign_out' | + 'user' | + 'language' | + 'close_big'; const Icons: FC = () => ( @@ -18,6 +24,16 @@ const Icons: FC = () => ( + + + + + + + + + + @@ -26,6 +42,42 @@ const Icons: FC = () => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); diff --git a/client2/src/components/common/ui/LangSelect/LangSelect.module.pcss b/client2/src/components/common/ui/LangSelect/LangSelect.module.pcss new file mode 100644 index 00000000..1342ea34 --- /dev/null +++ b/client2/src/components/common/ui/LangSelect/LangSelect.module.pcss @@ -0,0 +1,10 @@ +.wrap { + display: inline-flex; + align-items: center; +} + +.icon { + font-size: 22px; + margin-right: 10px; + color: var(--gray700); +} diff --git a/client2/src/components/common/ui/LangSelect/LangSelect.tsx b/client2/src/components/common/ui/LangSelect/LangSelect.tsx new file mode 100644 index 00000000..24bc15b2 --- /dev/null +++ b/client2/src/components/common/ui/LangSelect/LangSelect.tsx @@ -0,0 +1,23 @@ +import React, { FC, useContext } from 'react'; + +import { Icon } from 'Common/ui'; +import Store from 'Store'; +import { LANGUAGES } from 'Localization'; + +import s from './LangSelect.module.pcss'; + +const LangSelector: FC = () => { + const store = useContext(Store); + const { ui: { currentLang } } = store; + + const lang = LANGUAGES.find((e) => e.code === currentLang)!; + + return ( +
    + + {lang.name} +
    + ); +}; + +export default LangSelector; diff --git a/client2/src/components/common/ui/LangSelect/index.tsx b/client2/src/components/common/ui/LangSelect/index.tsx new file mode 100644 index 00000000..25f35f87 --- /dev/null +++ b/client2/src/components/common/ui/LangSelect/index.tsx @@ -0,0 +1 @@ +export { default } from './LangSelect'; diff --git a/client2/src/components/common/ui/Link.tsx b/client2/src/components/common/ui/Link.tsx new file mode 100644 index 00000000..5abbc0a1 --- /dev/null +++ b/client2/src/components/common/ui/Link.tsx @@ -0,0 +1,63 @@ +import React, { FC, MouseEvent } from 'react'; +import { Link as L, LinkProps as LProps } from 'react-router-dom'; +import cn from 'classnames'; + +import { linkPathBuilder, RoutePath, LinkParams, LinkParamsKeys } from 'Paths'; + +interface LinkProps { + to: RoutePath; + props?: LinkParams; + className?: string; + type?: LProps['type']; + stop?: boolean; + disabled?: boolean; + onClick?: () => void; + id?: string; +} + +const Link: FC = ({ + to, children, className, props, type, stop, disabled, onClick, id, +}) => { + if (props) { + Object.keys(props).forEach((key: unknown) => { + if (!props[key as LinkParamsKeys]) { + throw new Error(`Got wrong ${key} propKey: ${props[key as LinkParamsKeys]} in Link`); + } + }); + } + + const handleClick = (e: MouseEvent) => { + if (stop) { + e.stopPropagation(); + } + if (onClick) { + onClick(); + } + }; + + if (disabled) { + return ( +
    + {children} +
    + ); + } + + return ( + + {children} + + ); +}; + +export default Link; diff --git a/client2/src/components/common/ui/Mask/Mask.module.pcss b/client2/src/components/common/ui/Mask/Mask.module.pcss new file mode 100644 index 00000000..7c7e1930 --- /dev/null +++ b/client2/src/components/common/ui/Mask/Mask.module.pcss @@ -0,0 +1,26 @@ +.mask { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1040; + height: 100%; + background-color: rgba(0, 0, 0, 0.45); + opacity: 0; + visibility: hidden; + transition: opacity var(--transition); + cursor: pointer; + + &_visible { + opacity: 1; + visibility: visible; + } + + @media (--l-viewport) { + &_visible { + opacity: 0; + visibility: hidden; + } + } +} diff --git a/client2/src/components/common/ui/Mask/Mask.tsx b/client2/src/components/common/ui/Mask/Mask.tsx new file mode 100644 index 00000000..162b2349 --- /dev/null +++ b/client2/src/components/common/ui/Mask/Mask.tsx @@ -0,0 +1,23 @@ +import React, { FC } from 'react'; +import cn from 'classnames'; + +import s from './Mask.module.pcss'; + +interface MaskProps { + open: boolean; + handle: () => void; +} + +const Mask: FC = ({ open, handle }) => { + return ( +
    + ); +}; + +export default Mask; diff --git a/client2/src/components/common/ui/Mask/index.ts b/client2/src/components/common/ui/Mask/index.ts new file mode 100644 index 00000000..7933fa49 --- /dev/null +++ b/client2/src/components/common/ui/Mask/index.ts @@ -0,0 +1 @@ +export { default } from './Mask'; diff --git a/client2/src/components/common/ui/index.ts b/client2/src/components/common/ui/index.ts index f725111d..c505ed48 100644 --- a/client2/src/components/common/ui/index.ts +++ b/client2/src/components/common/ui/index.ts @@ -1,2 +1,6 @@ export { default as Icon } from './Icon'; export { notifyError, notifySuccess } from './Notifications'; +export { default as Link } from './Link'; +export { default as LangSelect } from './LangSelect'; +export { default as Mask } from './Mask'; +export { CommonLayout, InnerLayout, CommonModalLayout, ConfirmModalLayout } from './layouts'; diff --git a/client2/src/components/common/ui/layouts/CommonLayout.tsx b/client2/src/components/common/ui/layouts/CommonLayout.tsx new file mode 100644 index 00000000..e0c73cb3 --- /dev/null +++ b/client2/src/components/common/ui/layouts/CommonLayout.tsx @@ -0,0 +1,16 @@ +import { Layout } from 'antd'; +import React, { FC } from 'react'; + +interface CommonLayoutProps { + className?: string; +} + +const CommonLayout: FC = ({ children, className }) => { + return ( + + {children} + + ); +}; + +export default CommonLayout; diff --git a/client2/src/components/common/ui/layouts/CommonModalLayout.tsx b/client2/src/components/common/ui/layouts/CommonModalLayout.tsx new file mode 100644 index 00000000..8e62049e --- /dev/null +++ b/client2/src/components/common/ui/layouts/CommonModalLayout.tsx @@ -0,0 +1,87 @@ +import React, { FC, useContext, useEffect } from 'react'; +import { Modal, Button } from 'antd'; +import cn from 'classnames'; + +import { Icon } from 'Common/ui'; +import Store from 'Store'; + +interface CommonModalLayoutProps { + visible: boolean; + title: string; + buttonText?: string; + className?: string; + width?: number; + onClose: () => void; + onSubmit?: () => void; + noFooter?: boolean; + disabled?: boolean; + centered?: boolean; +} + +const CommonModalLayout: FC = ({ + visible, + children, + title, + buttonText, + className, + width, + onClose, + onSubmit, + noFooter, + disabled, + centered, +}) => { + const store = useContext(Store); + const { ui: { intl } } = store; + + useEffect(() => { + const onEnter = (e: KeyboardEvent) => { + if (e.key === 'Enter' && onSubmit) { + onSubmit(); + } + }; + if (onSubmit) { + window.addEventListener('keyup', onEnter); + } + return () => { + window.removeEventListener('keyup', onEnter); + }; + }, [onSubmit]); + const footer = noFooter ? null : [ + , + , + ]; + + return ( + } + width={width || 480} + centered={centered} + > + {children} + + ); +}; + +export default CommonModalLayout; diff --git a/client2/src/components/common/ui/layouts/ConfirmModalLayout.tsx b/client2/src/components/common/ui/layouts/ConfirmModalLayout.tsx new file mode 100644 index 00000000..503b858c --- /dev/null +++ b/client2/src/components/common/ui/layouts/ConfirmModalLayout.tsx @@ -0,0 +1,34 @@ +import React, { FC } from 'react'; + +import CommonModalLayout from './CommonModalLayout'; + +interface DeleteModalLayoutProps { + visible: boolean; + title: string; + buttonText: string; + onClose: () => void; + onConfirm?: () => void; +} + +const DeleteModalLayout: FC = ({ + visible, + children, + title, + buttonText, + onClose, + onConfirm, +}) => { + return ( + + {children} + + ); +}; + +export default DeleteModalLayout; diff --git a/client2/src/components/common/ui/layouts/InnerLayout.tsx b/client2/src/components/common/ui/layouts/InnerLayout.tsx new file mode 100644 index 00000000..4ecab818 --- /dev/null +++ b/client2/src/components/common/ui/layouts/InnerLayout.tsx @@ -0,0 +1,41 @@ +import { Layout } from 'antd'; +import React, { FC } from 'react'; +import cn from 'classnames'; + +import theme from 'Lib/theme'; + +interface InnerLayoutProps { + title: string; + className?: string; + containerClassName?: string; +} + +const InnerLayout: FC = ({ + children, title, className, containerClassName, +}) => { + return ( + +
    +
    +
    + {title} +
    +
    + {children} +
    +
    + ); +}; + +export default InnerLayout; diff --git a/client2/src/components/common/ui/layouts/index.ts b/client2/src/components/common/ui/layouts/index.ts new file mode 100644 index 00000000..3751f879 --- /dev/null +++ b/client2/src/components/common/ui/layouts/index.ts @@ -0,0 +1,4 @@ +export { default as CommonLayout } from './CommonLayout'; +export { default as InnerLayout } from './InnerLayout'; +export { default as ConfirmModalLayout } from './ConfirmModalLayout'; +export { default as CommonModalLayout } from './CommonModalLayout'; diff --git a/client2/src/lib/ant/Modal.pcss b/client2/src/lib/ant/Modal.pcss new file mode 100644 index 00000000..c0c98671 --- /dev/null +++ b/client2/src/lib/ant/Modal.pcss @@ -0,0 +1,47 @@ +.modal { + & .ant-modal-close-x { + display: flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + color: var(--black); + border-radius: 2px; + background-color: var(--white); + transition: background-color 0.3s; + + &:hover, + &:focus { + background-color: var(--cloud); + } + + &:active { + background-color: var(--borders-white); + } + + & svg { + width: 20px; + height: 20px; + } + + @media (--s-viewport) { + width: 40px; + height: 40px; + + & svg { + width: 24px; + height: 24px; + } + } + } + + & .ant-modal-close { + top: 11px; + right: 8px; + + @media (--s-viewport) { + top: 15px; + right: 15px; + } + } +} diff --git a/client2/src/lib/ant/Sidebar.pcss b/client2/src/lib/ant/Sidebar.pcss new file mode 100644 index 00000000..a0d55dfe --- /dev/null +++ b/client2/src/lib/ant/Sidebar.pcss @@ -0,0 +1,26 @@ +.sidebar { + position: fixed; + top: 0; + height: 100vh; + font-weight: 500; + overflow: auto; + z-index: 1041; + scrollbar-width: none; + + &::-webkit-scrollbar { + display: none; + } + + @media (--l-viewport) { + position: sticky; + z-index: 1040; + } + + & .ant-menu-item-group { + @media (--m-viewport) { + &:last-child { + margin-top: auto; + } + } + } +} \ No newline at end of file diff --git a/client2/src/lib/ant/Tabs.pcss b/client2/src/lib/ant/Tabs.pcss new file mode 100644 index 00000000..0b110210 --- /dev/null +++ b/client2/src/lib/ant/Tabs.pcss @@ -0,0 +1,45 @@ +.tabs { + border-radius: 2px; + background-color: var(--white); + + & .ant-tabs-tab { + padding: 10px 16px; + margin-right: 10px; + color: var(--gray900); + transition: color var(--transition), background var(--transition); + + &.ant-tabs-tab-active { + background-color: #E6F4EA; + } + } + + &.ant-tabs-left > .ant-tabs-nav .ant-tabs-tab { + @media (--l-viewport) { + min-width: 230px; + margin-bottom: 7px; + padding: 10px 24px; + } + } + + &.ant-tabs-left > .ant-tabs-content-holder > .ant-tabs-content > .ant-tabs-tabpane { + @media (--l-viewport) { + padding: 24px 40px; + } + } + + & .ant-tabs-nav { + margin-bottom: 0; + } + + & .ant-tabs-tabpane { + padding: 24px 16px; + } + + & .ant-tabs-nav-list { + padding: 0 16px; + + @media (--l-viewport) { + padding: 24px 0; + } + } +} diff --git a/client2/src/lib/ant/ant-overrides.less b/client2/src/lib/ant/ant-overrides.less index 1f683335..1de4b6e5 100644 --- a/client2/src/lib/ant/ant-overrides.less +++ b/client2/src/lib/ant/ant-overrides.less @@ -3,6 +3,10 @@ @text-color: #000; @link-hover-color: #4d995f; @link-active-color: #4d995f; +@text-selection-bg: #e7efff; +@layout-body-background: #f3f3f3; +@layout-header-background: #131313; +@menu-dark-submenu-bg: #131313; @font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Arial, sans-serif; @font-size-base: 14px; diff --git a/client2/src/lib/ant/ant.less b/client2/src/lib/ant/ant.less index bc24c3bc..a4e7d2ec 100644 --- a/client2/src/lib/ant/ant.less +++ b/client2/src/lib/ant/ant.less @@ -1,2 +1,6 @@ @import '~antd/dist/antd.less'; @import './ant-overrides.less'; + +::selection { + color: #000; +} diff --git a/client2/src/lib/ant/index.ts b/client2/src/lib/ant/index.ts index 7eba91c1..9fd16b8d 100644 --- a/client2/src/lib/ant/index.ts +++ b/client2/src/lib/ant/index.ts @@ -1,4 +1,7 @@ import './Radio.pcss'; +import './Sidebar.pcss'; +import './Tabs.pcss'; +import './Modal.pcss'; const insertStyles = true; export default insertStyles; diff --git a/client2/src/lib/apis/clients.ts b/client2/src/lib/apis/clients.ts index faaf22fb..187bcc9c 100644 --- a/client2/src/lib/apis/clients.ts +++ b/client2/src/lib/apis/clients.ts @@ -1,4 +1,6 @@ import qs from 'qs'; +import AccessListResponse, { IAccessListResponse } from 'Entities/AccessListResponse'; +import AccessSetRequest, { IAccessSetRequest } from 'Entities/AccessSetRequest'; import Client, { IClient } from 'Entities/Client'; import ClientDelete, { IClientDelete } from 'Entities/ClientDelete'; import ClientUpdate, { IClientUpdate } from 'Entities/ClientUpdate'; @@ -8,6 +10,40 @@ import ClientsFindEntry, { IClientsFindEntry } from 'Entities/ClientsFindEntry'; // This file was autogenerated. Please do not change. // All changes will be overwrited on commit. export default class ClientsApi { + static async accessList(): Promise { + return await fetch(`/control/access/list`, { + method: 'GET', + }).then(async (res) => { + if (res.status === 200) { + return res.json(); + } else { + return new Error(String(res.status)); + } + }) + } + + static async accessSet(accesssetrequest: IAccessSetRequest): Promise { + const haveError: string[] = []; + const accesssetrequestValid = new AccessSetRequest(accesssetrequest); + haveError.push(...accesssetrequestValid.validate()); + if (haveError.length > 0) { + return Promise.resolve(haveError); + } + return await fetch(`/control/access/set`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(accesssetrequestValid.serialize()), + }).then(async (res) => { + if (res.status === 200) { + return res.status; + } else { + return new Error(String(res.status)); + } + }) + } + static async clientsAdd(client: IClient): Promise { const haveError: string[] = []; const clientValid = new Client(client); diff --git a/client2/src/lib/apis/dhcp.ts b/client2/src/lib/apis/dhcp.ts index 99eef065..2d2df29f 100644 --- a/client2/src/lib/apis/dhcp.ts +++ b/client2/src/lib/apis/dhcp.ts @@ -2,6 +2,7 @@ import DhcpConfig, { IDhcpConfig } from 'Entities/DhcpConfig'; import DhcpSearchResult, { IDhcpSearchResult } from 'Entities/DhcpSearchResult'; import DhcpStaticLease, { IDhcpStaticLease } from 'Entities/DhcpStaticLease'; import DhcpStatus, { IDhcpStatus } from 'Entities/DhcpStatus'; +import NetInterfaces, { INetInterfaces } from 'Entities/NetInterfaces'; // This file was autogenerated. Please do not change. // All changes will be overwrited on commit. @@ -40,6 +41,18 @@ export default class DhcpApi { }) } + static async dhcpInterfaces(): Promise { + return await fetch(`/control/dhcp/interfaces`, { + method: 'GET', + }).then(async (res) => { + if (res.status === 200) { + return res.json(); + } else { + return new Error(String(res.status)); + } + }) + } + static async dhcpRemoveStaticLease(dhcpstaticlease: IDhcpStaticLease): Promise { const haveError: string[] = []; const dhcpstaticleaseValid = new DhcpStaticLease(dhcpstaticlease); diff --git a/client2/src/lib/apis/mobileconfig.ts b/client2/src/lib/apis/mobileconfig.ts index 0d4603c6..5abeffa0 100644 --- a/client2/src/lib/apis/mobileconfig.ts +++ b/client2/src/lib/apis/mobileconfig.ts @@ -3,9 +3,10 @@ import qs from 'qs'; // This file was autogenerated. Please do not change. // All changes will be overwrited on commit. export default class MobileconfigApi { - static async mobileConfigDoH(host?: string): Promise { + static async mobileConfigDoH(host?: string, client_id?: string): Promise { const queryParams = { host: host, + client_id: client_id, } return await fetch(`/control/apple/doh.mobileconfig?${qs.stringify(queryParams, { arrayFormat: 'comma' })}`, { method: 'GET', @@ -18,9 +19,10 @@ export default class MobileconfigApi { }) } - static async mobileConfigDoT(host?: string): Promise { + static async mobileConfigDoT(host?: string, client_id?: string): Promise { const queryParams = { host: host, + client_id: client_id, } return await fetch(`/control/apple/dot.mobileconfig?${qs.stringify(queryParams, { arrayFormat: 'comma' })}`, { method: 'GET', diff --git a/client2/src/lib/consts/common.ts b/client2/src/lib/consts/common.ts index 77ad1796..668536b0 100644 --- a/client2/src/lib/consts/common.ts +++ b/client2/src/lib/consts/common.ts @@ -1 +1,3 @@ export const DEFAULT_NOTIFICATION_DURATION = 5; + +export const DHCP_LINK = 'https://github.com/AdguardTeam/AdGuardHome/wiki/DHCP'; diff --git a/client2/src/lib/entities/AccessList.ts b/client2/src/lib/entities/AccessList.ts new file mode 100644 index 00000000..7d127465 --- /dev/null +++ b/client2/src/lib/entities/AccessList.ts @@ -0,0 +1,76 @@ +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface IAccessList { + allowed_clients?: string[]; + blocked_hosts?: string[]; + disallowed_clients?: string[]; +} + +export default class AccessList { + readonly _allowed_clients: string[] | undefined; + + /** */ + get allowedClients(): string[] | undefined { + return this._allowed_clients; + } + + readonly _blocked_hosts: string[] | undefined; + + /** */ + get blockedHosts(): string[] | undefined { + return this._blocked_hosts; + } + + readonly _disallowed_clients: string[] | undefined; + + /** */ + get disallowedClients(): string[] | undefined { + return this._disallowed_clients; + } + + constructor(props: IAccessList) { + if (props.allowed_clients) { + this._allowed_clients = props.allowed_clients; + } + if (props.blocked_hosts) { + this._blocked_hosts = props.blocked_hosts; + } + if (props.disallowed_clients) { + this._disallowed_clients = props.disallowed_clients; + } + } + + serialize(): IAccessList { + const data: IAccessList = { + }; + if (typeof this._allowed_clients !== 'undefined') { + data.allowed_clients = this._allowed_clients; + } + if (typeof this._blocked_hosts !== 'undefined') { + data.blocked_hosts = this._blocked_hosts; + } + if (typeof this._disallowed_clients !== 'undefined') { + data.disallowed_clients = this._disallowed_clients; + } + return data; + } + + validate(): string[] { + const validate = { + allowed_clients: !this._allowed_clients ? true : this._allowed_clients.reduce((result, p) => result && typeof p === 'string', true), + disallowed_clients: !this._disallowed_clients ? true : this._disallowed_clients.reduce((result, p) => result && typeof p === 'string', true), + blocked_hosts: !this._blocked_hosts ? true : this._blocked_hosts.reduce((result, p) => result && typeof p === 'string', true), + }; + const isError: string[] = []; + Object.keys(validate).forEach((key) => { + if (!(validate as any)[key]) { + isError.push(key); + } + }); + return isError; + } + + update(props: Partial): AccessList { + return new AccessList({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/AccessListResponse.ts b/client2/src/lib/entities/AccessListResponse.ts new file mode 100644 index 00000000..c4443f0a --- /dev/null +++ b/client2/src/lib/entities/AccessListResponse.ts @@ -0,0 +1,6 @@ +import AccessList, { IAccessList } from './AccessList'; + +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export type IAccessListResponse = IAccessList; +export default AccessList; diff --git a/client2/src/lib/entities/AccessSetRequest.ts b/client2/src/lib/entities/AccessSetRequest.ts new file mode 100644 index 00000000..45ebbab1 --- /dev/null +++ b/client2/src/lib/entities/AccessSetRequest.ts @@ -0,0 +1,6 @@ +import AccessList, { IAccessList } from './AccessList'; + +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export type IAccessSetRequest = IAccessList; +export default AccessList; diff --git a/client2/src/lib/entities/AddressesInfo.ts b/client2/src/lib/entities/AddressesInfo.ts index dc814eeb..af864b17 100644 --- a/client2/src/lib/entities/AddressesInfo.ts +++ b/client2/src/lib/entities/AddressesInfo.ts @@ -1,10 +1,10 @@ -import NetInterface, { INetInterface } from './NetInterface'; +import NetInterfaces, { INetInterfaces } from './NetInterfaces'; // This file was autogenerated. Please do not change. // All changes will be overwrited on commit. export interface IAddressesInfo { dns_port: number; - interfaces: { [key: string]: INetInterface }; + interfaces: INetInterfaces; web_port: number; } @@ -23,10 +23,9 @@ export default class AddressesInfo { return typeof dnsPort === 'number'; } - readonly _interfaces: { [key: string]: NetInterface }; + readonly _interfaces: NetInterfaces; - /** */ - get interfaces(): { [key: string]: NetInterface } { + get interfaces(): NetInterfaces { return this._interfaces; } @@ -46,16 +45,14 @@ export default class AddressesInfo { constructor(props: IAddressesInfo) { this._dns_port = props.dns_port; - this._interfaces = Object.keys(props.interfaces).reduce((prev, key) => { - return { ...prev, [key]: new NetInterface(props.interfaces[key])}; - },{}) + this._interfaces = new NetInterfaces(props.interfaces); this._web_port = props.web_port; } serialize(): IAddressesInfo { const data: IAddressesInfo = { dns_port: this._dns_port, - interfaces: Object.keys(this._interfaces).reduce>((prev, key) => ({ ...prev, [key]: this._interfaces[key].serialize() }), {}), + interfaces: this._interfaces.serialize(), web_port: this._web_port, }; return data; @@ -65,6 +62,7 @@ export default class AddressesInfo { const validate = { dns_port: typeof this._dns_port === 'number', web_port: typeof this._web_port === 'number', + interfaces: this._interfaces.validate().length === 0, }; const isError: string[] = []; Object.keys(validate).forEach((key) => { diff --git a/client2/src/lib/entities/ClientFindSubEntry.ts b/client2/src/lib/entities/ClientFindSubEntry.ts index 670d2da3..8d067549 100644 --- a/client2/src/lib/entities/ClientFindSubEntry.ts +++ b/client2/src/lib/entities/ClientFindSubEntry.ts @@ -15,7 +15,7 @@ export interface IClientFindSubEntry { upstreams?: string[]; use_global_blocked_services?: boolean; use_global_settings?: boolean; - whois_info?: IWhoisInfo[]; + whois_info?: IWhoisInfo; } export default class ClientFindSubEntry { @@ -98,9 +98,9 @@ export default class ClientFindSubEntry { return this._use_global_settings; } - readonly _whois_info: WhoisInfo[] | undefined; + readonly _whois_info: WhoisInfo | undefined; - get whoisInfo(): WhoisInfo[] | undefined { + get whoisInfo(): WhoisInfo | undefined { return this._whois_info; } @@ -142,7 +142,7 @@ export default class ClientFindSubEntry { this._use_global_settings = props.use_global_settings; } if (props.whois_info) { - this._whois_info = props.whois_info.map((p) => new WhoisInfo(p)); + this._whois_info = new WhoisInfo(props.whois_info); } } @@ -186,7 +186,7 @@ export default class ClientFindSubEntry { data.use_global_settings = this._use_global_settings; } if (typeof this._whois_info !== 'undefined') { - data.whois_info = this._whois_info.map((p) => p.serialize()); + data.whois_info = this._whois_info.serialize(); } return data; } @@ -203,7 +203,7 @@ export default class ClientFindSubEntry { use_global_blocked_services: !this._use_global_blocked_services ? true : typeof this._use_global_blocked_services === 'boolean', blocked_services: !this._blocked_services ? true : this._blocked_services.reduce((result, p) => result && typeof p === 'string', true), upstreams: !this._upstreams ? true : this._upstreams.reduce((result, p) => result && typeof p === 'string', true), - whois_info: !this._whois_info ? true : this._whois_info.reduce((result, p) => result && p.validate().length === 0, true), + whois_info: !this._whois_info ? true : this._whois_info.validate().length === 0, disallowed: !this._disallowed ? true : typeof this._disallowed === 'boolean', disallowed_rule: !this._disallowed_rule ? true : typeof this._disallowed_rule === 'string' && !this._disallowed_rule ? true : this._disallowed_rule, }; diff --git a/client2/src/lib/entities/ClientsFindEntry.ts b/client2/src/lib/entities/ClientsFindEntry.ts index 9dd3099c..8b220a78 100644 --- a/client2/src/lib/entities/ClientsFindEntry.ts +++ b/client2/src/lib/entities/ClientsFindEntry.ts @@ -1,31 +1,33 @@ +import ClientFindSubEntry, { IClientFindSubEntry } from './ClientFindSubEntry'; + // This file was autogenerated. Please do not change. // All changes will be overwrited on commit. export interface IClientsFindEntry { + [key: string]: IClientFindSubEntry; } export default class ClientsFindEntry { + readonly data: Record; + constructor(props: IClientsFindEntry) { + this.data = Object.entries(props).reduce>((prev, [key, value]) => { + prev[key] = new ClientFindSubEntry(value!); + return prev; + }, {}) } serialize(): IClientsFindEntry { - const data: IClientsFindEntry = { - }; - return data; + return Object.entries(this.data).reduce>((prev, [key, value]) => { + prev[key] = value.serialize(); + return prev; + }, {}) } validate(): string[] { - const validate = { - }; - const isError: string[] = []; - Object.keys(validate).forEach((key) => { - if (!(validate as any)[key]) { - isError.push(key); - } - }); - return isError; + return [] } - update(props: Partial): ClientsFindEntry { + update(props: IClientsFindEntry): ClientsFindEntry { return new ClientsFindEntry({ ...this.serialize(), ...props }); } } diff --git a/client2/src/lib/entities/DhcpSearchResultOtherServer.ts b/client2/src/lib/entities/DhcpSearchResultOtherServer.ts index 36f2bc64..be50bbdd 100644 --- a/client2/src/lib/entities/DhcpSearchResultOtherServer.ts +++ b/client2/src/lib/entities/DhcpSearchResultOtherServer.ts @@ -16,7 +16,8 @@ export default class DhcpSearchResultOtherServer { readonly _found: string | undefined; /** - * Description: yes|no|error + * Description: The result of searching the other DHCP server. + * * Example: no */ get found(): string | undefined { diff --git a/client2/src/lib/entities/DhcpSearchResultStaticIP.ts b/client2/src/lib/entities/DhcpSearchResultStaticIP.ts index 7b27411a..f3fc2155 100644 --- a/client2/src/lib/entities/DhcpSearchResultStaticIP.ts +++ b/client2/src/lib/entities/DhcpSearchResultStaticIP.ts @@ -16,7 +16,8 @@ export default class DhcpSearchResultStaticIP { readonly _static: string | undefined; /** - * Description: yes|no|error + * Description: The result of determining static IP address. + * * Example: yes */ get static(): string | undefined { diff --git a/client2/src/lib/entities/Filter.ts b/client2/src/lib/entities/Filter.ts index 71255c8c..c7f3ff87 100644 --- a/client2/src/lib/entities/Filter.ts +++ b/client2/src/lib/entities/Filter.ts @@ -3,9 +3,9 @@ export interface IFilter { enabled: boolean; id: number; - lastUpdated: string; + last_updated: string; name: string; - rulesCount: number; + rules_count: number; url: string; } @@ -34,14 +34,14 @@ export default class Filter { return typeof id === 'number'; } - readonly _lastUpdated: string; + readonly _last_updated: string; /** * Description: undefined * Example: 2018-10-30T12:18:57+03:00 */ get lastUpdated(): string { - return this._lastUpdated; + return this._last_updated; } static lastUpdatedValidate(lastUpdated: string): boolean { @@ -62,14 +62,14 @@ export default class Filter { return typeof name === 'string' && !!name.trim(); } - readonly _rulesCount: number; + readonly _rules_count: number; /** * Description: undefined * Example: 5912 */ get rulesCount(): number { - return this._rulesCount; + return this._rules_count; } static rulesCountValidate(rulesCount: number): boolean { @@ -94,9 +94,9 @@ export default class Filter { constructor(props: IFilter) { this._enabled = props.enabled; this._id = props.id; - this._lastUpdated = props.lastUpdated.trim(); + this._last_updated = props.last_updated.trim(); this._name = props.name.trim(); - this._rulesCount = props.rulesCount; + this._rules_count = props.rules_count; this._url = props.url.trim(); } @@ -104,9 +104,9 @@ export default class Filter { const data: IFilter = { enabled: this._enabled, id: this._id, - lastUpdated: this._lastUpdated, + last_updated: this._last_updated, name: this._name, - rulesCount: this._rulesCount, + rules_count: this._rules_count, url: this._url, }; return data; @@ -116,9 +116,9 @@ export default class Filter { const validate = { enabled: typeof this._enabled === 'boolean', id: typeof this._id === 'number', - lastUpdated: typeof this._lastUpdated === 'string' && !this._lastUpdated ? true : this._lastUpdated, + last_updated: typeof this._last_updated === 'string' && !this._last_updated ? true : this._last_updated, name: typeof this._name === 'string' && !this._name ? true : this._name, - rulesCount: typeof this._rulesCount === 'number', + rules_count: typeof this._rules_count === 'number', url: typeof this._url === 'string' && !this._url ? true : this._url, }; const isError: string[] = []; diff --git a/client2/src/lib/entities/Login.ts b/client2/src/lib/entities/Login.ts index 9b642f45..808c6f6e 100644 --- a/client2/src/lib/entities/Login.ts +++ b/client2/src/lib/entities/Login.ts @@ -1,11 +1,18 @@ // This file was autogenerated. Please do not change. // All changes will be overwrited on commit. export interface ILogin { + name?: string; password?: string; - username?: string; } export default class Login { + readonly _name: string | undefined; + + /** */ + get name(): string | undefined { + return this._name; + } + readonly _password: string | undefined; /** */ @@ -13,37 +20,30 @@ export default class Login { return this._password; } - readonly _username: string | undefined; - - /** */ - get username(): string | undefined { - return this._username; - } - constructor(props: ILogin) { + if (typeof props.name === 'string') { + this._name = props.name.trim(); + } if (typeof props.password === 'string') { this._password = props.password.trim(); } - if (typeof props.username === 'string') { - this._username = props.username.trim(); - } } serialize(): ILogin { const data: ILogin = { }; + if (typeof this._name !== 'undefined') { + data.name = this._name; + } if (typeof this._password !== 'undefined') { data.password = this._password; } - if (typeof this._username !== 'undefined') { - data.username = this._username; - } return data; } validate(): string[] { const validate = { - username: !this._username ? true : typeof this._username === 'string' && !this._username ? true : this._username, + name: !this._name ? true : typeof this._name === 'string' && !this._name ? true : this._name, password: !this._password ? true : typeof this._password === 'string' && !this._password ? true : this._password, }; const isError: string[] = []; diff --git a/client2/src/lib/entities/NetInterface.ts b/client2/src/lib/entities/NetInterface.ts index 3f3d6e3e..c11d7a2c 100644 --- a/client2/src/lib/entities/NetInterface.ts +++ b/client2/src/lib/entities/NetInterface.ts @@ -12,7 +12,8 @@ export default class NetInterface { readonly _flags: string; /** - * Description: undefined + * Description: Flags could be any combination of the following values, divided by the "|" character: "up", "broadcast", "loopback", "pointtopoint" and "multicast". + * * Example: up|broadcast|multicast */ get flags(): string { diff --git a/client2/src/lib/entities/NetInterfaces.ts b/client2/src/lib/entities/NetInterfaces.ts new file mode 100644 index 00000000..29a33304 --- /dev/null +++ b/client2/src/lib/entities/NetInterfaces.ts @@ -0,0 +1,33 @@ +import NetInterface, { INetInterface } from './NetInterface'; + +// This file was autogenerated. Please do not change. +// All changes will be overwrited on commit. +export interface INetInterfaces { + [key: string]: INetInterface; +} + +export default class NetInterfaces { + readonly data: Record; + + constructor(props: INetInterfaces) { + this.data = Object.entries(props).reduce>((prev, [key, value]) => { + prev[key] = new NetInterface(value!); + return prev; + }, {}) + } + + serialize(): INetInterfaces { + return Object.entries(this.data).reduce>((prev, [key, value]) => { + prev[key] = value.serialize(); + return prev; + }, {}) + } + + validate(): string[] { + return [] + } + + update(props: INetInterfaces): NetInterfaces { + return new NetInterfaces({ ...this.serialize(), ...props }); + } +} diff --git a/client2/src/lib/entities/QueryLogItem.ts b/client2/src/lib/entities/QueryLogItem.ts index caf7819c..6dffead3 100644 --- a/client2/src/lib/entities/QueryLogItem.ts +++ b/client2/src/lib/entities/QueryLogItem.ts @@ -8,6 +8,7 @@ export interface IQueryLogItem { answer?: IDnsAnswer[]; answer_dnssec?: boolean; client?: string; + client_id?: string; client_proto?: any; elapsedMs?: string; filterId?: number; @@ -38,13 +39,25 @@ export default class QueryLogItem { readonly _client: string | undefined; /** - * Description: undefined + * Description: The client's IP address. + * * Example: 192.168.0.1 */ get client(): string | undefined { return this._client; } + readonly _client_id: string | undefined; + + /** + * Description: The client ID, if provided in DOH, DOQ, or DOT. + * + * Example: cli123 + */ + get clientId(): string | undefined { + return this._client_id; + } + readonly _client_proto: any | undefined; get clientProto(): any | undefined { @@ -156,6 +169,9 @@ export default class QueryLogItem { if (typeof props.client === 'string') { this._client = props.client.trim(); } + if (typeof props.client_id === 'string') { + this._client_id = props.client_id.trim(); + } if (props.client_proto) { this._client_proto = props.client_proto; } @@ -206,6 +222,9 @@ export default class QueryLogItem { if (typeof this._client !== 'undefined') { data.client = this._client; } + if (typeof this._client_id !== 'undefined') { + data.client_id = this._client_id; + } if (typeof this._client_proto !== 'undefined') { data.client_proto = this._client_proto; } @@ -252,6 +271,7 @@ export default class QueryLogItem { upstream: !this._upstream ? true : typeof this._upstream === 'string' && !this._upstream ? true : this._upstream, answer_dnssec: !this._answer_dnssec ? true : typeof this._answer_dnssec === 'boolean', client: !this._client ? true : typeof this._client === 'string' && !this._client ? true : this._client, + client_id: !this._client_id ? true : typeof this._client_id === 'string' && !this._client_id ? true : this._client_id, elapsedMs: !this._elapsedMs ? true : typeof this._elapsedMs === 'string' && !this._elapsedMs ? true : this._elapsedMs, question: !this._question ? true : this._question.validate().length === 0, filterId: !this._filterId ? true : typeof this._filterId === 'number', diff --git a/client2/src/lib/entities/ServerStatus.ts b/client2/src/lib/entities/ServerStatus.ts index a17df251..d70f0adf 100644 --- a/client2/src/lib/entities/ServerStatus.ts +++ b/client2/src/lib/entities/ServerStatus.ts @@ -2,11 +2,11 @@ // All changes will be overwrited on commit. export interface IServerStatus { dhcp_available?: boolean; - dns_address: string; + dns_addresses: string[]; dns_port: number; + http_port: number; language: string; protection_enabled: boolean; - querylog_enabled: boolean; running: boolean; version: string; } @@ -18,18 +18,18 @@ export default class ServerStatus { return this._dhcp_available; } - readonly _dns_address: string; + readonly _dns_addresses: string[]; /** * Description: undefined * Example: 127.0.0.1 */ - get dnsAddress(): string { - return this._dns_address; + get dnsAddresses(): string[] { + return this._dns_addresses; } - static dnsAddressValidate(dnsAddress: string): boolean { - return typeof dnsAddress === 'string' && !!dnsAddress.trim(); + static dnsAddressesValidate(dnsAddresses: string[]): boolean { + return dnsAddresses.reduce((result, p) => result && (typeof p === 'string' && !!p.trim()), true); } readonly _dns_port: number; @@ -54,6 +54,28 @@ export default class ServerStatus { return dnsPort >= 1 && dnsPort <= 65535; } + readonly _http_port: number; + + /** + * Description: undefined + * Example: 80 + */ + get httpPort(): number { + return this._http_port; + } + + static get httpPortMinValue() { + return 1; + } + + static get httpPortMaxValue() { + return 65535; + } + + static httpPortValidate(httpPort: number): boolean { + return httpPort >= 1 && httpPort <= 65535; + } + readonly _language: string; /** @@ -78,16 +100,6 @@ export default class ServerStatus { return typeof protectionEnabled === 'boolean'; } - readonly _querylog_enabled: boolean; - - get querylogEnabled(): boolean { - return this._querylog_enabled; - } - - static querylogEnabledValidate(querylogEnabled: boolean): boolean { - return typeof querylogEnabled === 'boolean'; - } - readonly _running: boolean; get running(): boolean { @@ -116,22 +128,22 @@ export default class ServerStatus { if (typeof props.dhcp_available === 'boolean') { this._dhcp_available = props.dhcp_available; } - this._dns_address = props.dns_address.trim(); + this._dns_addresses = props.dns_addresses; this._dns_port = props.dns_port; + this._http_port = props.http_port; this._language = props.language.trim(); this._protection_enabled = props.protection_enabled; - this._querylog_enabled = props.querylog_enabled; this._running = props.running; this._version = props.version.trim(); } serialize(): IServerStatus { const data: IServerStatus = { - dns_address: this._dns_address, + dns_addresses: this._dns_addresses, dns_port: this._dns_port, + http_port: this._http_port, language: this._language, protection_enabled: this._protection_enabled, - querylog_enabled: this._querylog_enabled, running: this._running, version: this._version, }; @@ -143,11 +155,11 @@ export default class ServerStatus { validate(): string[] { const validate = { - dns_address: typeof this._dns_address === 'string' && !this._dns_address ? true : this._dns_address, + dns_addresses: this._dns_addresses.reduce((result, p) => result && typeof p === 'string', true), dns_port: this._dns_port >= 1 && this._dns_port <= 65535, + http_port: this._http_port >= 1 && this._http_port <= 65535, protection_enabled: typeof this._protection_enabled === 'boolean', dhcp_available: !this._dhcp_available ? true : typeof this._dhcp_available === 'boolean', - querylog_enabled: typeof this._querylog_enabled === 'boolean', running: typeof this._running === 'boolean', version: typeof this._version === 'string' && !this._version ? true : this._version, language: typeof this._language === 'string' && !this._language ? true : this._language, diff --git a/client2/src/lib/entities/Stats.ts b/client2/src/lib/entities/Stats.ts index 6f01139d..bae0e1b5 100644 --- a/client2/src/lib/entities/Stats.ts +++ b/client2/src/lib/entities/Stats.ts @@ -107,7 +107,7 @@ export default class Stats { readonly _time_units: string | undefined; /** - * Description: Time units (hours | days) + * Description: Time units * Example: hours */ get timeUnits(): string | undefined { diff --git a/client2/src/lib/entities/TopArrayEntry.ts b/client2/src/lib/entities/TopArrayEntry.ts index 52916fd2..2720683a 100644 --- a/client2/src/lib/entities/TopArrayEntry.ts +++ b/client2/src/lib/entities/TopArrayEntry.ts @@ -2,6 +2,7 @@ // All changes will be overwrited on commit. export interface ITopArrayEntry { domain_or_ip?: number; + [key: string]: number | undefined; } export default class TopArrayEntry { @@ -11,19 +12,20 @@ export default class TopArrayEntry { return this._domain_or_ip; } + readonly numberData: Record; + constructor(props: ITopArrayEntry) { - if (typeof props.domain_or_ip === 'number') { - this._domain_or_ip = props.domain_or_ip; - } + this.numberData = Object.entries(props).reduce>((prev, [key, value]) => { + prev[key] = value!; + return prev; + }, {}) } serialize(): ITopArrayEntry { - const data: ITopArrayEntry = { - }; - if (typeof this._domain_or_ip !== 'undefined') { - data.domain_or_ip = this._domain_or_ip; - } - return data; + return Object.entries(this.numberData).reduce>((prev, [key, value]) => { + prev[key] = value; + return prev; + }, {}) } validate(): string[] { diff --git a/client2/src/lib/entities/UpstreamsConfigResponse.ts b/client2/src/lib/entities/UpstreamsConfigResponse.ts index 9b342400..5d827a7e 100644 --- a/client2/src/lib/entities/UpstreamsConfigResponse.ts +++ b/client2/src/lib/entities/UpstreamsConfigResponse.ts @@ -1,31 +1,31 @@ // This file was autogenerated. Please do not change. // All changes will be overwrited on commit. export interface IUpstreamsConfigResponse { + [key: string]: string; } export default class UpstreamsConfigResponse { + readonly data: Record; + constructor(props: IUpstreamsConfigResponse) { + this.data = Object.entries(props).reduce>((prev, [key, value]) => { + prev[key] = value!; + return prev; + }, {}) } serialize(): IUpstreamsConfigResponse { - const data: IUpstreamsConfigResponse = { - }; - return data; + return Object.entries(this.data).reduce>((prev, [key, value]) => { + prev[key] = value; + return prev; + }, {}) } validate(): string[] { - const validate = { - }; - const isError: string[] = []; - Object.keys(validate).forEach((key) => { - if (!(validate as any)[key]) { - isError.push(key); - } - }); - return isError; + return [] } - update(props: Partial): UpstreamsConfigResponse { + update(props: IUpstreamsConfigResponse): UpstreamsConfigResponse { return new UpstreamsConfigResponse({ ...this.serialize(), ...props }); } } diff --git a/client2/src/lib/entities/VersionInfo.ts b/client2/src/lib/entities/VersionInfo.ts index 7df508ea..59d4b937 100644 --- a/client2/src/lib/entities/VersionInfo.ts +++ b/client2/src/lib/entities/VersionInfo.ts @@ -4,6 +4,7 @@ export interface IVersionInfo { announcement?: string; announcement_url?: string; can_autoupdate?: boolean; + disabled: boolean; new_version?: string; } @@ -35,6 +36,17 @@ export default class VersionInfo { return this._can_autoupdate; } + readonly _disabled: boolean; + + /** */ + get disabled(): boolean { + return this._disabled; + } + + static disabledValidate(disabled: boolean): boolean { + return typeof disabled === 'boolean'; + } + readonly _new_version: string | undefined; /** @@ -55,6 +67,7 @@ export default class VersionInfo { if (typeof props.can_autoupdate === 'boolean') { this._can_autoupdate = props.can_autoupdate; } + this._disabled = props.disabled; if (typeof props.new_version === 'string') { this._new_version = props.new_version.trim(); } @@ -62,6 +75,7 @@ export default class VersionInfo { serialize(): IVersionInfo { const data: IVersionInfo = { + disabled: this._disabled, }; if (typeof this._announcement !== 'undefined') { data.announcement = this._announcement; @@ -80,6 +94,7 @@ export default class VersionInfo { validate(): string[] { const validate = { + disabled: typeof this._disabled === 'boolean', new_version: !this._new_version ? true : typeof this._new_version === 'string' && !this._new_version ? true : this._new_version, announcement: !this._announcement ? true : typeof this._announcement === 'string' && !this._announcement ? true : this._announcement, announcement_url: !this._announcement_url ? true : typeof this._announcement_url === 'string' && !this._announcement_url ? true : this._announcement_url, diff --git a/client2/src/lib/entities/WhoisInfo.ts b/client2/src/lib/entities/WhoisInfo.ts index f0f2db74..aa5ee628 100644 --- a/client2/src/lib/entities/WhoisInfo.ts +++ b/client2/src/lib/entities/WhoisInfo.ts @@ -1,45 +1,31 @@ // This file was autogenerated. Please do not change. // All changes will be overwrited on commit. export interface IWhoisInfo { - key?: string; + [key: string]: string; } export default class WhoisInfo { - readonly _key: string | undefined; - - get key(): string | undefined { - return this._key; - } + readonly data: Record; constructor(props: IWhoisInfo) { - if (typeof props.key === 'string') { - this._key = props.key.trim(); - } + this.data = Object.entries(props).reduce>((prev, [key, value]) => { + prev[key] = value!; + return prev; + }, {}) } serialize(): IWhoisInfo { - const data: IWhoisInfo = { - }; - if (typeof this._key !== 'undefined') { - data.key = this._key; - } - return data; + return Object.entries(this.data).reduce>((prev, [key, value]) => { + prev[key] = value; + return prev; + }, {}) } validate(): string[] { - const validate = { - key: !this._key ? true : typeof this._key === 'string' && !this._key ? true : this._key, - }; - const isError: string[] = []; - Object.keys(validate).forEach((key) => { - if (!(validate as any)[key]) { - isError.push(key); - } - }); - return isError; + return [] } - update(props: Partial): WhoisInfo { + update(props: IWhoisInfo): WhoisInfo { return new WhoisInfo({ ...this.serialize(), ...props }); } } diff --git a/client2/src/lib/theme/Content.module.pcss b/client2/src/lib/theme/Content.module.pcss new file mode 100644 index 00000000..8f2246df --- /dev/null +++ b/client2/src/lib/theme/Content.module.pcss @@ -0,0 +1,51 @@ +.content { + min-height: 100vh; + + &_auth { + @media (--m-viewport) { + background-color: var(--gray100); + background-image: url('../../assets/img/install.png'); + background-position: center 20px; + background-repeat: no-repeat; + background-size: 100%; + } + } + + &_inner { + min-height: calc(100vh - var(--header-height)); + } +} + +.container { + width: 100%; + margin: 0 auto; + padding: 16px; + + @media (--l-viewport) { + padding: 24px; + } + + &_auth { + max-width: 432px; + padding: 24px 16px 40px; + + @media (--m-viewport) { + padding: 40px 16px; + } + } +} + +.header { + margin-bottom: 16px; + padding: 0 16px; + + @media (--m-viewport) { + margin-bottom: 24px; + padding: 0 24px; + } +} + +.title { + font-size: 16px; + font-weight: 600; +} diff --git a/client2/src/lib/theme/Install.module.pcss b/client2/src/lib/theme/Install.module.pcss index 5e000bbb..39b5d682 100644 --- a/client2/src/lib/theme/Install.module.pcss +++ b/client2/src/lib/theme/Install.module.pcss @@ -1,26 +1,3 @@ -.layout { - min-height: 100vh; - background-color: var(--gray100); - - @media (--m-viewport) { - background-image: url('../../assets/img/install.png'); - background-position: center 20px; - background-repeat: no-repeat; - background-size: 100%; - } -} - -.container { - width: 100%; - max-width: 432px; - margin: 0 auto; - padding: 24px 16px 40px; - - @media (--m-viewport) { - padding: 40px 16px; - } -} - .title, .subtitle, .text { diff --git a/client2/src/lib/theme/Link.module.pcss b/client2/src/lib/theme/Link.module.pcss index d3731b42..b0751d90 100644 --- a/client2/src/lib/theme/Link.module.pcss +++ b/client2/src/lib/theme/Link.module.pcss @@ -11,4 +11,17 @@ &:active { color: var(--green400); } + + &.gray { + color: var(--gray900); + + &:hover, + &:focus { + color: var(--gray700); + } + + &:active { + color: var(--gray700); + } + } } diff --git a/client2/src/lib/theme/Text.module.pcss b/client2/src/lib/theme/Text.module.pcss index 5ff8bf11..55a5f4e3 100644 --- a/client2/src/lib/theme/Text.module.pcss +++ b/client2/src/lib/theme/Text.module.pcss @@ -22,8 +22,20 @@ font-weight: 400; } +.code { + padding: 3px 5px; + font-size: 14px; + font-family: var(--font-family-monospace); + background-color: var(--gray300); + border-radius: 2px; +} + .danger { text-transform: uppercase; color: var(--red400); font-weight: bold; } + +.center { + text-align: center; +} diff --git a/client2/src/lib/theme/colors.ts b/client2/src/lib/theme/colors.ts new file mode 100644 index 00000000..09b157b8 --- /dev/null +++ b/client2/src/lib/theme/colors.ts @@ -0,0 +1,8 @@ +export const colors = { + red: '#c23814', + orange: '#eb9300', + purple: '#b267a0', + green: '#67b279', + gray300: '#d8d8d8', + gray700: '#888888', +}; diff --git a/client2/src/lib/theme/index.ts b/client2/src/lib/theme/index.ts index d60feaca..c215d1a0 100644 --- a/client2/src/lib/theme/index.ts +++ b/client2/src/lib/theme/index.ts @@ -2,12 +2,17 @@ import form from './Form.module.pcss'; import text from './Text.module.pcss'; import install from './Install.module.pcss'; import link from './Link.module.pcss'; +import content from './Content.module.pcss'; + +import { colors } from './colors'; const theme = { form, + chartColors: colors, text, install, link, + content, }; export default theme; diff --git a/client2/src/localization/Translator/Translator.ts b/client2/src/localization/Translator/Translator.ts deleted file mode 100644 index 06b50cd1..00000000 --- a/client2/src/localization/Translator/Translator.ts +++ /dev/null @@ -1,82 +0,0 @@ -import translator from './lib/translator'; -import { AllowedValues } from './lib/formatter'; -import { getForm, GenericLocales, AvailableLocales } from './lib/plural'; - -type ExternalFormater = (data: any) => any; - -class Translator { - private _currentLocale: Locale; - - private _formatter: ExternalFormater = (data: string[]) => data.join(''); - - get currentLocale() { - return this._currentLocale; - } - - defaultLocale: Locale; - - updateTranslator = (locale: Locale) => { - return new Translator( - this.defaultLocale, - this.messages, - locale, - this._formatter, - ); - }; - - messages: Record; - - constructor( - defaultLocale: Locale, - messages: Record, - currentLocale?: Locale, - formatter?: ExternalFormater, - ) { - this.defaultLocale = defaultLocale; - this._currentLocale = currentLocale ?? defaultLocale; - this.messages = messages; - - if (formatter) { - this._formatter = formatter; - } - } - - public getMessage( - id: string, - params: AllowedValues = {}, - ): string { - const str = this.messages[this._currentLocale][id] - || this.messages[this.defaultLocale][id] - || id; - - const tranlation = translator(str, params); - return this._formatter(tranlation); - } - - public getPlural( - id: string, - number: number, - params: AllowedValues = {}, - ): string { - let locale: Locale | null = null; - if (this.messages[this._currentLocale][id]) { - locale = this._currentLocale; - } else if (this.messages[this.defaultLocale][id]) { - locale = this.defaultLocale; - } - const str = this.messages[this._currentLocale][id] - || this.messages[this.defaultLocale][id] - || id; - - if (!locale) { - throw new Error(`No translation for id: ${id}, neither in current locale: ${this._currentLocale} nor defaulkt locale ${this.defaultLocale}`); - } - - return this._formatter(translator( - getForm(str, number, locale as AvailableLocales, id), - { count: number, ...params }, - )); - } -} - -export default Translator; diff --git a/client2/src/localization/Translator/index.ts b/client2/src/localization/Translator/index.ts deleted file mode 100644 index 87f17fbc..00000000 --- a/client2/src/localization/Translator/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { default } from './Translator'; -export { SupportedLangs, GenericLocales, AvailableLocales, checkFormsExternal as checkForms } from './lib/plural'; diff --git a/client2/src/localization/Translator/lib/formatter.ts b/client2/src/localization/Translator/lib/formatter.ts deleted file mode 100644 index 15735f6a..00000000 --- a/client2/src/localization/Translator/lib/formatter.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { - isTextNode, - isTagNode, - isPlaceholderNode, - isVoidTagNode, - NODE, -} from './nodes'; - -/** - * Checks if target is function - * @param target - * @returns {boolean} - */ -const isFunction = (target: any) => { - return typeof target === 'function'; -}; - -type FormatingFunc = (chunks: string) => T; -export type AllowedValues = Record>; - -/** - * This function accepts an AST (abstract syntax tree) which is a result - * of the parser function call, and converts tree nodes into array of strings replacing node - * values with provided values. - * Values is a map with functions or strings, where each key is related to placeholder value - * or tag value - * e.g. - * string "text tag text %placeholder%" is parsed into next AST - * - * [ - * { type: 'text', value: 'text ' }, - * { - * type: 'tag', - * value: 'tag', - * children: [{ type: 'text', value: 'tag text' }], - * }, - * { type: 'text', value: ' ' }, - * { type: 'placeholder', value: 'placeholder' } - * ]; - * - * this AST after format and next values - * - * { - * // here used template strings, but it can be react components as well - * tag: (chunks) => `${chunks}`, - * placeholder: 'placeholder text' - * } - * - * will return next array - * - * [ 'text ', 'tag text', ' ', 'placeholder text' ] - * - * as you can see, was replaced by , and placeholder was replaced by placeholder text - * - * @param ast - AST (abstract syntax tree) - * @param values - * @returns {[]} - */ -const format = (ast: NODE[], values: AllowedValues) => { - const result: (string | T)[] = []; - let i = 0; - while (i < ast.length) { - const currentNode = ast[i]; - // if current node is text node, there is nothing to change, append value to the result - if (isTextNode(currentNode)) { - result.push(currentNode.value); - } else if (isTagNode(currentNode)) { - const children = [...format(currentNode.children ? currentNode.children : [], values)].join(''); - const value = values[currentNode.value]; - if (typeof value === 'string' || typeof value === 'number' || typeof value === 'function') { - if (isFunction(value)) { - result.push((value as FormatingFunc)(children)); - } else { - result.push(value.toString()); - } - } else { - throw new Error(`Value ${currentNode.value} wasn't provided`); - } - } else if (isVoidTagNode(currentNode)) { - const value = values[currentNode.value]; - if (typeof value === 'string' || typeof value === 'number') { - result.push(value.toString()); - } else { - throw new Error(`Value ${currentNode.value} wasn't provided`); - } - } else if (isPlaceholderNode(currentNode)) { - const value = values[currentNode.value]; - if (typeof value === 'string' || typeof value === 'number') { - result.push(value.toString()); - } else { - throw new Error(`Value ${currentNode.value} wasn't provided`); - } - } - i += 1; - } - - return result; -}; - -export default format; diff --git a/client2/src/localization/Translator/lib/nodes.ts b/client2/src/localization/Translator/lib/nodes.ts deleted file mode 100644 index 20f3835a..00000000 --- a/client2/src/localization/Translator/lib/nodes.ts +++ /dev/null @@ -1,50 +0,0 @@ -export enum NODE_TYPES { - PLACEHOLDER = 'placeholder', - TEXT = 'text', - TAG = 'tag', - VOID_TAG = 'void_tag', -} - -export interface NODE { - type: NODE_TYPES; - value: string | keyof HTMLElementTagNameMap; - children?: NODE[]; -} - -export const isTextNode = (node: NODE) => { - return node?.type === NODE_TYPES.TEXT; -}; - -export const isTagNode = (node: NODE) => { - return node?.type === NODE_TYPES.TAG; -}; - -export const isPlaceholderNode = (node: NODE) => { - return node?.type === NODE_TYPES.PLACEHOLDER; -}; - -export const isVoidTagNode = (node: NODE) => { - return node?.type === NODE_TYPES.VOID_TAG; -}; - -export const placeholderNode = (value: string) => { - return { type: NODE_TYPES.PLACEHOLDER, value }; -}; - -export const textNode = (str: string) => { - return { type: NODE_TYPES.TEXT, value: str }; -}; - -export const tagNode = (tagName: keyof HTMLElementTagNameMap, children: NODE[]) => { - const value = tagName.trim(); - return { type: NODE_TYPES.TAG, value, children }; -}; - -export const voidTagNode = (tagName: keyof HTMLElementTagNameMap) => { - const value = tagName.trim(); - return { type: NODE_TYPES.VOID_TAG, value }; -}; - -export const isNode = (checked: any) => { - return !!checked?.type; -}; diff --git a/client2/src/localization/Translator/lib/parser.ts b/client2/src/localization/Translator/lib/parser.ts deleted file mode 100644 index 0c2134f0..00000000 --- a/client2/src/localization/Translator/lib/parser.ts +++ /dev/null @@ -1,335 +0,0 @@ -/* eslint-disable no-param-reassign */ -import { - tagNode, - textNode, - isNode, - placeholderNode, - voidTagNode, - NODE, -} from './nodes'; - -enum STATE { - /** - * parser function switches to the text state when parses simple text, - * or content between open and close tags - */ - TEXT = 'text', - - /** - * parser function switches to the tag state when meets open tag brace ("<"), and switches back, - * when meets closing tag brace (">") - */ - TAG = 'tag', - - /** - * Parser function switches to the placeholder state when meets in the text - * open placeholders brace ("{") and switches back to the text state, - * when meets close placeholder brace ("}") - */ - PLACEHOLDER = 'placeholder', -} - -enum CONTROL_CHARS { - TAG_OPEN_BRACE = '<', - TAG_CLOSE_BRACE = '>', - CLOSING_TAG_MARK = '/', - PLACEHOLDER_MARK = '%', -} - -interface Context { - /** - * Stack is used to keep and search nested tag nodes - * @type {*[]} - */ - stack: (NODE | keyof HTMLElementTagNameMap)[]; - /** - * Result is stack where function allocates nodes - * @type {*[]} - */ - result: NODE[]; - /** - * Current char index - * @type {number} - */ - currIdx: number; - /** - * Saves index of the last state change from the text state, - * used to restore parsed text if we moved into other state wrongly - */ - lastTextStateChangeIdx: number; - /** - * Accumulated tag value - */ - tag: string; - /** - * Accumulated text value - */ - text: string; - /** - * Accumulated placeholder value - */ - placeholder: string; - /** - * Parsed string - */ - str: string; - /** - * Currently parsed char - */ - currChar: string; -} - -/** - * Checks if text length is enough to create text node - * If text node created, then if stack is not empty it is pushed into stack, - * otherwise into result - * @param context - */ -const createTextNodeIfPossible = (context: Context) => { - const { text } = context; - - if (text.length > 0) { - const node = textNode(text); - if (context.stack.length > 0) { - context.stack.push(node); - } else { - context.result.push(node); - } - } - - context.text = ''; -}; - -/** - * Handles text state - * @returns {function} - */ -const textStateHandler = (context: Context) => { - const { currChar, currIdx } = context; - - // switches to the tag state - if (currChar === CONTROL_CHARS.TAG_OPEN_BRACE) { - context.lastTextStateChangeIdx = currIdx; - return STATE.TAG; - } - - // switches to the placeholder state - if (currChar === CONTROL_CHARS.PLACEHOLDER_MARK) { - context.lastTextStateChangeIdx = currIdx; - return STATE.PLACEHOLDER; - } - - // remains in the text state - context.text += currChar; - return STATE.TEXT; -}; - -/** - * Handles placeholder state - * @param context - * @returns {string} - */ -const placeholderStateHandler = (context: Context) => { - const { - currChar, - currIdx, - lastTextStateChangeIdx, - placeholder, - stack, - result, - str, - } = context; - - if (currChar === CONTROL_CHARS.PLACEHOLDER_MARK) { - // if distance between current index and last state change equal to 1, - // it means that placeholder mark was escaped by itself e.g. "%%", - // so we return to the text state - if (currIdx - lastTextStateChangeIdx === 1) { - context.text += str.substring(lastTextStateChangeIdx, currIdx); - return STATE.TEXT; - } - - createTextNodeIfPossible(context); - const node = placeholderNode(placeholder); - - // push node to the appropriate stack - if (stack.length > 0) { - stack.push(node); - } else { - result.push(node); - } - - context.placeholder = ''; - return STATE.TEXT; - } - - context.placeholder += currChar; - return STATE.PLACEHOLDER; -}; - -/** - * Switches current state to the tag state and returns tag state handler - * @returns {function} - */ -const tagStateHandler = (context: Context) => { - const { - currChar, - text, - stack, - result, - lastTextStateChangeIdx, - currIdx, - str, - } = context; - - let { tag } = context; - - // if found tag end ">" - if (currChar === CONTROL_CHARS.TAG_CLOSE_BRACE) { - // if the tag is close tag e.g. - if (tag.indexOf(CONTROL_CHARS.CLOSING_TAG_MARK) === 0) { - // remove slash from tag - tag = tag.substring(1); - - let children: NODE[] = []; - if (text.length > 0) { - children.push(textNode(text)); - context.text = ''; - } - - let pairTagFound = false; - // looking for the pair to the close tag - while (!pairTagFound && stack.length > 0) { - const lastFromStack = stack.pop(); - // if tag from stack equal to close tag - if (lastFromStack === tag) { - // create tag node - const node = tagNode(tag as keyof HTMLElementTagNameMap, children); - // and add it to the appropriate stack - if (stack.length > 0) { - stack.push(node); - } else { - result.push(node); - } - children = []; - pairTagFound = true; - } else if (isNode(lastFromStack)) { - // add nodes between close tag and open tag to the children - children.unshift(lastFromStack as NODE); - } else { - throw new Error(`String has unbalanced tags: ${str}`); - } - if (stack.length === 0 && children.length > 0) { - throw new Error(`String has unbalanced tags: ${str}`); - } - } - context.tag = ''; - return STATE.TEXT; - } - - // if the tag is void tag e.g. - if (tag.lastIndexOf(CONTROL_CHARS.CLOSING_TAG_MARK) === tag.length - 1) { - tag = tag.substring(0, tag.length - 1); - createTextNodeIfPossible(context); - const node = voidTagNode(tag as keyof HTMLElementTagNameMap); - // add node to the appropriate stack - if (stack.length > 0) { - stack.push(node); - } else { - result.push(node); - } - context.tag = ''; - return STATE.TEXT; - } - - createTextNodeIfPossible(context); - stack.push(tag as keyof HTMLElementTagNameMap); - context.tag = ''; - return STATE.TEXT; - } - - // If we meet open tag "<" it means that we wrongly moved into tag state - if (currChar === CONTROL_CHARS.TAG_OPEN_BRACE) { - context.text += str.substring(lastTextStateChangeIdx, currIdx); - context.lastTextStateChangeIdx = currIdx; - context.tag = ''; - return STATE.TAG; - } - - context.tag += currChar; - return STATE.TAG; -}; - -/** - * Parses string into AST (abstract syntax tree) and returns it - * e.g. - * parse("String to translate") -> - * ``` - * [ - * { type: 'text', value: 'String to ' }, - * { type: 'tag', value: 'a', children: [{ type: 'text', value: 'translate' }] } - * ]; - * ``` - * Empty string is parsed into empty AST (abstract syntax tree): "[]" - * If founds unbalanced tags, it throws error about it - * - * @param {string} str - message in simplified ICU like syntax without plural support - * @returns {[]} - */ -const parser = (str = '') => { - const context: Context = { - str, - stack: [], - result: [], - currIdx: 0, - lastTextStateChangeIdx: 0, - tag: '', - text: '', - placeholder: '', - currChar: '', - }; - - const STATE_HANDLERS = { - [STATE.TEXT]: textStateHandler, - [STATE.PLACEHOLDER]: placeholderStateHandler, - [STATE.TAG]: tagStateHandler, - }; - - // Start from text state - let currentState = STATE.TEXT; - - while (context.currIdx < str.length) { - context.currChar = str[context.currIdx]; - const currentStateHandler: (c: Context) => STATE = STATE_HANDLERS[currentState]; - currentState = currentStateHandler(context); - context.currIdx += 1; - } - - const { - result, - text, - stack, - lastTextStateChangeIdx, - } = context; - - // Means that tag or placeholder nodes were not closed, so we consider them as text - if (currentState !== STATE.TEXT) { - const restText = str.substring(lastTextStateChangeIdx); - if ((restText + text).length > 0) { - result.push(textNode(text + restText)); - } - } else { - // eslint-disable-next-line no-lonely-if - if (text.length > 0) { - result.push(textNode(text)); - } - } - - if (stack.length > 0) { - throw new Error(`String has unbalanced tags ${context.str}`); - } - - return result; -}; - -export default parser; diff --git a/client2/src/localization/Translator/lib/plural.ts b/client2/src/localization/Translator/lib/plural.ts deleted file mode 100644 index a705fcca..00000000 --- a/client2/src/localization/Translator/lib/plural.ts +++ /dev/null @@ -1,394 +0,0 @@ -/* eslint-disable no-nested-ternary */ -export type SupportedLangs = 'az' | 'bo' | 'dz' | 'id' | 'ja' | 'jv' | 'ka' | 'km' | 'kn' | 'ko' | 'ms' | 'th' | 'tr' | 'vi' | 'zh' | 'af' | 'bn' | 'bg' | 'ca' | 'da' | 'de' | 'el' | 'en' | 'eo' | 'es' | 'et' | 'eu' | 'fa' | 'fi' | 'fo' | 'fur' | 'fy' | 'gl' | 'gu' | 'ha' | 'he' | 'hu' | 'is' | 'it' | 'ku' | 'lb' | 'ml' | 'mn' | 'mr' | 'nah' | 'nb' | 'ne' | 'nl' | 'nn' | 'no' | 'oc' | 'om' | 'or' | 'pa' | 'pap' | 'ps' | 'pt' | 'so' | 'sq' | 'sv' | 'sw' | 'ta' | 'te' | 'tk' | 'ur' | 'zu' | 'am' | 'bh' | 'fil' | 'fr' | 'gun' | 'hi' | 'hy' | 'ln' | 'mg' | 'nso' | 'xbr' | 'ti' | 'wa' | 'be' | 'bs' | 'hr' | 'ru' | 'sr' | 'uk' | 'cs' | 'sk' | 'ga' | 'lt' | 'sl' | 'mk' | 'mt' | 'lv' | 'pl' | 'cy' | 'ro' | 'ar'; - -export type GenericLocales = { - [key in SupportedLangs]: SupportedLangs; -}; - -export enum AvailableLocales { - az = 'az', - bo = 'bo', - dz = 'dz', - id = 'id', - ja = 'ja', - jv = 'jv', - ka = 'ka', - km = 'km', - kn = 'kn', - ko = 'ko', - ms = 'ms', - th = 'th', - tr = 'tr', - vi = 'vi', - zh = 'zh', - af = 'af', - bn = 'bn', - bg = 'bg', - ca = 'ca', - da = 'da', - de = 'de', - el = 'el', - en = 'en', - eo = 'eo', - es = 'es', - et = 'et', - eu = 'eu', - fa = 'fa', - fi = 'fi', - fo = 'fo', - fur = 'fur', - fy = 'fy', - gl = 'gl', - gu = 'gu', - ha = 'ha', - he = 'he', - hu = 'hu', - is = 'is', - it = 'it', - ku = 'ku', - lb = 'lb', - ml = 'ml', - mn = 'mn', - mr = 'mr', - nah = 'nah', - nb = 'nb', - ne = 'ne', - nl = 'nl', - nn = 'nn', - no = 'no', - oc = 'oc', - om = 'om', - or = 'or', - pa = 'pa', - pap = 'pap', - ps = 'ps', - pt = 'pt', - so = 'so', - sq = 'sq', - sv = 'sv', - sw = 'sw', - ta = 'ta', - te = 'te', - tk = 'tk', - ur = 'ur', - zu = 'zu', - am = 'am', - bh = 'bh', - fil = 'fil', - fr = 'fr', - gun = 'gun', - hi = 'hi', - hy = 'hy', - ln = 'ln', - mg = 'mg', - nso = 'nso', - xbr = 'xbr', - ti = 'ti', - wa = 'wa', - be = 'be', - bs = 'bs', - hr = 'hr', - ru = 'ru', - sr = 'sr', - uk = 'uk', - cs = 'cs', - sk = 'sk', - ga = 'ga', - lt = 'lt', - sl = 'sl', - mk = 'mk', - mt = 'mt', - lv = 'lv', - pl = 'pl', - cy = 'cy', - ro = 'ro', - ar = 'ar', -} -export const getPluralFormId = (locale: AvailableLocales, number: number) => { - if (number === 0) { - return 0; - } - const slavNum = ((number % 10 === 1) && (number % 100 !== 11)) - ? 1 - : ( - ((number % 10 >= 2) && (number % 10 <= 4) && ((number % 100 < 10) - || (number % 100 >= 20)) - ) - ? 2 - : 3); - const supportedForms: Record = { - [AvailableLocales.az]: 1, - [AvailableLocales.bo]: 1, - [AvailableLocales.dz]: 1, - [AvailableLocales.id]: 1, - [AvailableLocales.ja]: 1, - [AvailableLocales.jv]: 1, - [AvailableLocales.ka]: 1, - [AvailableLocales.km]: 1, - [AvailableLocales.kn]: 1, - [AvailableLocales.ko]: 1, - [AvailableLocales.ms]: 1, - [AvailableLocales.th]: 1, - [AvailableLocales.tr]: 1, - [AvailableLocales.vi]: 1, - [AvailableLocales.zh]: 1, - - [AvailableLocales.af]: (number === 1) ? 1 : 2, - [AvailableLocales.bn]: (number === 1) ? 1 : 2, - [AvailableLocales.bg]: (number === 1) ? 1 : 2, - [AvailableLocales.ca]: (number === 1) ? 1 : 2, - [AvailableLocales.da]: (number === 1) ? 1 : 2, - [AvailableLocales.de]: (number === 1) ? 1 : 2, - [AvailableLocales.el]: (number === 1) ? 1 : 2, - [AvailableLocales.en]: (number === 1) ? 1 : 2, - [AvailableLocales.eo]: (number === 1) ? 1 : 2, - [AvailableLocales.es]: (number === 1) ? 1 : 2, - [AvailableLocales.et]: (number === 1) ? 1 : 2, - [AvailableLocales.eu]: (number === 1) ? 1 : 2, - [AvailableLocales.fa]: (number === 1) ? 1 : 2, - [AvailableLocales.fi]: (number === 1) ? 1 : 2, - [AvailableLocales.fo]: (number === 1) ? 1 : 2, - [AvailableLocales.fur]: (number === 1) ? 1 : 2, - [AvailableLocales.fy]: (number === 1) ? 1 : 2, - [AvailableLocales.gl]: (number === 1) ? 1 : 2, - [AvailableLocales.gu]: (number === 1) ? 1 : 2, - [AvailableLocales.ha]: (number === 1) ? 1 : 2, - [AvailableLocales.he]: (number === 1) ? 1 : 2, - [AvailableLocales.hu]: (number === 1) ? 1 : 2, - [AvailableLocales.is]: (number === 1) ? 1 : 2, - [AvailableLocales.it]: (number === 1) ? 1 : 2, - [AvailableLocales.ku]: (number === 1) ? 1 : 2, - [AvailableLocales.lb]: (number === 1) ? 1 : 2, - [AvailableLocales.ml]: (number === 1) ? 1 : 2, - [AvailableLocales.mn]: (number === 1) ? 1 : 2, - [AvailableLocales.mr]: (number === 1) ? 1 : 2, - [AvailableLocales.nah]: (number === 1) ? 1 : 2, - [AvailableLocales.nb]: (number === 1) ? 1 : 2, - [AvailableLocales.ne]: (number === 1) ? 1 : 2, - [AvailableLocales.nl]: (number === 1) ? 1 : 2, - [AvailableLocales.nn]: (number === 1) ? 1 : 2, - [AvailableLocales.no]: (number === 1) ? 1 : 2, - [AvailableLocales.oc]: (number === 1) ? 1 : 2, - [AvailableLocales.om]: (number === 1) ? 1 : 2, - [AvailableLocales.or]: (number === 1) ? 1 : 2, - [AvailableLocales.pa]: (number === 1) ? 1 : 2, - [AvailableLocales.pap]: (number === 1) ? 1 : 2, - [AvailableLocales.ps]: (number === 1) ? 1 : 2, - [AvailableLocales.pt]: (number === 1) ? 1 : 2, - [AvailableLocales.so]: (number === 1) ? 1 : 2, - [AvailableLocales.sq]: (number === 1) ? 1 : 2, - [AvailableLocales.sv]: (number === 1) ? 1 : 2, - [AvailableLocales.sw]: (number === 1) ? 1 : 2, - [AvailableLocales.ta]: (number === 1) ? 1 : 2, - [AvailableLocales.te]: (number === 1) ? 1 : 2, - [AvailableLocales.tk]: (number === 1) ? 1 : 2, - [AvailableLocales.ur]: (number === 1) ? 1 : 2, - [AvailableLocales.zu]: (number === 1) ? 1 : 2, - - // how it works with 0? - [AvailableLocales.am]: ((number === 0) || (number === 1)) ? 0 : 1, - [AvailableLocales.bh]: ((number === 0) || (number === 1)) ? 0 : 1, - [AvailableLocales.fil]: ((number === 0) || (number === 1)) ? 0 : 1, - [AvailableLocales.fr]: ((number === 0) || (number === 1)) ? 0 : 1, - [AvailableLocales.gun]: ((number === 0) || (number === 1)) ? 0 : 1, - [AvailableLocales.hi]: ((number === 0) || (number === 1)) ? 0 : 1, - [AvailableLocales.hy]: ((number === 0) || (number === 1)) ? 0 : 1, - [AvailableLocales.ln]: ((number === 0) || (number === 1)) ? 0 : 1, - [AvailableLocales.mg]: ((number === 0) || (number === 1)) ? 0 : 1, - [AvailableLocales.nso]: ((number === 0) || (number === 1)) ? 0 : 1, - [AvailableLocales.xbr]: ((number === 0) || (number === 1)) ? 0 : 1, - [AvailableLocales.ti]: ((number === 0) || (number === 1)) ? 0 : 1, - [AvailableLocales.wa]: ((number === 0) || (number === 1)) ? 0 : 1, - - [AvailableLocales.be]: slavNum, - [AvailableLocales.bs]: slavNum, - [AvailableLocales.hr]: slavNum, - [AvailableLocales.ru]: slavNum, - [AvailableLocales.sr]: slavNum, - [AvailableLocales.uk]: slavNum, - - [AvailableLocales.cs]: (number === 1) ? 1 : (((number >= 2) && (number <= 4)) ? 2 : 3), - [AvailableLocales.sk]: (number === 1) ? 1 : (((number >= 2) && (number <= 4)) ? 2 : 3), - [AvailableLocales.ga]: (number === 1) ? 1 : ((number === 2) ? 2 : 3), - [AvailableLocales.lt]: ((number % 10 === 1) && (number % 100 !== 11)) - ? 1 - : (((number % 10 >= 2) && ((number % 100 < 10) || (number % 100 >= 20))) - ? 2 - : 3), - [AvailableLocales.sl]: (number % 100 === 1) - ? 1 - : ((number % 100 === 2) - ? 2 - : (((number % 100 === 3) || (number % 100 === 4)) - ? 3 - : 4)), - [AvailableLocales.mk]: (number % 10 === 1) ? 1 : 2, - [AvailableLocales.mt]: (number === 1) - ? 1 - : (((number === 0) || ((number % 100 > 1) && (number % 100 < 11))) - ? 2 - : (((number % 100 > 10) && (number % 100 < 20)) - ? 3 - : 4)), - [AvailableLocales.lv]: (number === 0) - ? 0 - : (((number % 10 === 1) && (number % 100 !== 11)) - ? 1 - : 2), - [AvailableLocales.pl]: (number === 1) - ? 1 - : ( - ((number % 10 >= 2) && (number % 10 <= 4) && ((number % 100 < 12) - || (number % 100 > 14)) - ) - ? 2 - : 3), - [AvailableLocales.cy]: (number === 1) - ? 0 - : ((number === 2) - ? 1 - : (((number === 8) || (number === 11)) - ? 2 - : 3)), - [AvailableLocales.ro]: (number === 1) - ? 1 - : (((number === 1) || ((number % 100 > 0) && (number % 100 < 20))) - ? 2 - : 3), - [AvailableLocales.ar]: (number === 0) - ? 0 - : ((number === 1) - ? 1 - : ((number === 2) - ? 2 - : (((number % 100 >= 3) && (number % 100 <= 10)) - ? 3 - : (((number % 100 >= 11) && (number % 100 <= 99)) - ? 4 - : 5)))), - - }; - return supportedForms[locale]; -}; -export const pluraFormsCount: Record = { - [AvailableLocales.az]: 2, - [AvailableLocales.bo]: 2, - [AvailableLocales.dz]: 2, - [AvailableLocales.id]: 2, - [AvailableLocales.ja]: 2, - [AvailableLocales.jv]: 2, - [AvailableLocales.ka]: 2, - [AvailableLocales.km]: 2, - [AvailableLocales.kn]: 2, - [AvailableLocales.ko]: 2, - [AvailableLocales.ms]: 2, - [AvailableLocales.th]: 2, - [AvailableLocales.tr]: 2, - [AvailableLocales.vi]: 2, - [AvailableLocales.zh]: 2, - [AvailableLocales.af]: 3, - [AvailableLocales.bn]: 3, - [AvailableLocales.bg]: 3, - [AvailableLocales.ca]: 3, - [AvailableLocales.da]: 3, - [AvailableLocales.de]: 3, - [AvailableLocales.el]: 3, - [AvailableLocales.en]: 3, - [AvailableLocales.eo]: 3, - [AvailableLocales.es]: 3, - [AvailableLocales.et]: 3, - [AvailableLocales.eu]: 3, - [AvailableLocales.fa]: 3, - [AvailableLocales.fi]: 3, - [AvailableLocales.fo]: 3, - [AvailableLocales.fur]: 3, - [AvailableLocales.fy]: 3, - [AvailableLocales.gl]: 3, - [AvailableLocales.gu]: 3, - [AvailableLocales.ha]: 3, - [AvailableLocales.he]: 3, - [AvailableLocales.hu]: 3, - [AvailableLocales.is]: 3, - [AvailableLocales.it]: 3, - [AvailableLocales.ku]: 3, - [AvailableLocales.lb]: 3, - [AvailableLocales.ml]: 3, - [AvailableLocales.mn]: 3, - [AvailableLocales.mr]: 3, - [AvailableLocales.nah]: 3, - [AvailableLocales.nb]: 3, - [AvailableLocales.ne]: 3, - [AvailableLocales.nl]: 3, - [AvailableLocales.nn]: 3, - [AvailableLocales.no]: 3, - [AvailableLocales.oc]: 3, - [AvailableLocales.om]: 3, - [AvailableLocales.or]: 3, - [AvailableLocales.pa]: 3, - [AvailableLocales.pap]: 3, - [AvailableLocales.ps]: 3, - [AvailableLocales.pt]: 3, - [AvailableLocales.so]: 3, - [AvailableLocales.sq]: 3, - [AvailableLocales.sv]: 3, - [AvailableLocales.sw]: 3, - [AvailableLocales.ta]: 3, - [AvailableLocales.te]: 3, - [AvailableLocales.tk]: 3, - [AvailableLocales.ur]: 3, - [AvailableLocales.zu]: 3, - [AvailableLocales.am]: 2, - [AvailableLocales.bh]: 2, - [AvailableLocales.fil]: 2, - [AvailableLocales.fr]: 2, - [AvailableLocales.gun]: 2, - [AvailableLocales.hi]: 2, - [AvailableLocales.hy]: 2, - [AvailableLocales.ln]: 2, - [AvailableLocales.mg]: 2, - [AvailableLocales.nso]: 2, - [AvailableLocales.xbr]: 2, - [AvailableLocales.ti]: 2, - [AvailableLocales.wa]: 2, - [AvailableLocales.be]: 4, - [AvailableLocales.bs]: 4, - [AvailableLocales.hr]: 4, - [AvailableLocales.ru]: 4, - [AvailableLocales.sr]: 4, - [AvailableLocales.uk]: 4, - [AvailableLocales.cs]: 4, - [AvailableLocales.sk]: 4, - [AvailableLocales.ga]: 4, - [AvailableLocales.lt]: 4, - [AvailableLocales.sl]: 5, - [AvailableLocales.mk]: 3, - [AvailableLocales.mt]: 5, - [AvailableLocales.lv]: 3, - [AvailableLocales.pl]: 4, - [AvailableLocales.cy]: 4, - [AvailableLocales.ro]: 4, - [AvailableLocales.ar]: 6, -}; - -const PLURAL_STRING_DELIMITER = '|'; - -export const checkForms = (str: string, locale: AvailableLocales, id: string) => { - const forms = str.split(PLURAL_STRING_DELIMITER); - if (forms.length !== pluraFormsCount[locale]) { - throw new Error(`Invalid plural string "${id}" for locale ${locale}: ${forms.length} given; need: ${pluraFormsCount[locale]}`); - } -}; -export const checkFormsExternal = (str: string, locale: AvailableLocales, id: string) => { - try { - checkForms(str, locale, id); - return true; - } catch (error) { - return false; - } -}; -export const getForm = (str: string, number: number, locale: AvailableLocales, id: string) => { - checkForms(str, locale, id); - const forms = str.split(PLURAL_STRING_DELIMITER); - const currentForm = getPluralFormId(locale, number); - return forms[currentForm].trim(); -}; diff --git a/client2/src/localization/Translator/lib/translator.ts b/client2/src/localization/Translator/lib/translator.ts deleted file mode 100644 index e791d065..00000000 --- a/client2/src/localization/Translator/lib/translator.ts +++ /dev/null @@ -1,9 +0,0 @@ -import parser from './parser'; -import format, { AllowedValues } from './formatter'; - -const translator = (message: string, values: AllowedValues) => { - const astMessage = parser(message); - const formatted = format(astMessage, values); - return formatted; -}; -export default translator; diff --git a/client2/src/localization/Translator/lib/validator.ts b/client2/src/localization/Translator/lib/validator.ts deleted file mode 100644 index 3ad7317d..00000000 --- a/client2/src/localization/Translator/lib/validator.ts +++ /dev/null @@ -1,60 +0,0 @@ -import parser from './parser'; -import { isTextNode, NODE } from './nodes'; - -/** - * Compares two AST (abstract syntax tree) structures, - * view tests for examples - * @param baseAst - * @param targetAst - * @returns {boolean} - */ -const areAstStructuresSame = (baseAst: NODE[], targetAst: NODE[]) => { - const textNodeFilter = (node: NODE) => { - return !isTextNode(node); - }; - - const filteredBaseAst = baseAst.filter(textNodeFilter); - - const filteredTargetAst = targetAst.filter(textNodeFilter); - - // if AST structures have different lengths, they are not equal - if (filteredBaseAst.length !== filteredTargetAst.length) { - return false; - } - - for (let i = 0; i < filteredBaseAst.length; i += 1) { - const baseNode = filteredBaseAst[i]; - - const targetNode = filteredTargetAst.find((node) => { - return node.type === baseNode.type && node.value === baseNode.value; - }); - - if (!targetNode) { - return false; - } - - if (targetNode.children && baseNode.children) { - const areChildrenSame = areAstStructuresSame(baseNode.children, targetNode.children); - if (!areChildrenSame) { - return false; - } - } - } - - return true; -}; - -/** - * Validates translation against base string by AST (abstract syntax tree) structure - * @param baseStr - * @param targetStr - * @returns {boolean} - */ -export const isTargetStrValid = (baseStr: string, targetStr: string) => { - const baseAst = parser(baseStr); - const targetAst = parser(targetStr); - - const result = areAstStructuresSame(baseAst, targetAst); - - return result; -}; diff --git a/client2/src/localization/index.ts b/client2/src/localization/index.ts index 1006227e..a5a02eae 100644 --- a/client2/src/localization/index.ts +++ b/client2/src/localization/index.ts @@ -1,6 +1,9 @@ -import T from './Translator'; -import { Locale } from './locales'; +import { Locale, DatePickerLocale, messages, DEFAULT_LOCALE, LANGUAGES } from './locales'; -export { messages, DatePickerLocale, Locale, DEFAULT_LOCALE, LANGUAGES, reactFormater } from './locales'; -export type Translator = T; -export default T; +export { Locale, DatePickerLocale, messages, DEFAULT_LOCALE, LANGUAGES }; +export const i18n = (lang: Locale) => ({ + getMessage: (key: string) => messages[lang][key], + getUILanguage: () => lang, + getBaseMessage: (key: string) => messages[DEFAULT_LOCALE][key] || key, + getBaseUILanguage: () => DEFAULT_LOCALE, +}); diff --git a/client2/src/localization/locales/en.json b/client2/src/localization/locales/en.json index f6801463..a0094f6a 100644 --- a/client2/src/localization/locales/en.json +++ b/client2/src/localization/locales/en.json @@ -7,6 +7,34 @@ "next": "Next", "port": "Port", "router": "Router", + "username": "Username", + "sign_in": "Sign in", + "sign_out": "Sign out", + "dashboard": "Dashboard", + "setup_guide": "Setup guide", + "query_log": "Query Log", + "filters": "Filters", + "settings": "Settings", + "general_settings": "General settings", + "dns_settings": "DNS settings", + "encryption_settings": "Encryption settings", + "client_settings": "Client settings", + "dhcp_settings": "DHCP settings", + "disable": "Disable", + "disabled": "Disabled", + "enable": "Enable", + "clear": "Clear", + "cancel": "Cancel", + + "login_password_title": "Reset Password", + "login_password_link": "Forgot password?", + "login_password_hash": "AdGuard Home stores passwords as a BCrypt-encoded hash. Here's what you need to do to change the password:", + "login_password_step_1": "Stop AdGuard Home", + "login_password_step_2": "Edit AdGuardHome.yaml", + "login_password_step_3": "Find password field there", + "login_password_step_4": "Replace it with the new value. You can use .htpasswd password generator tool or any online BCrypt generator tool (there are many available online).", + "login_password_step_5": "Start AdGuard Home", + "login_password_result": "Now you'll be able to log in to web interface using your new password.", "install_admin_interface_port_desc": "Now it is working at 3000 port, just in case, but we recomended to use 80 port. Using this ports allow to access to Web interface like to common site", "install_admin_interface_port": "Which port will be used", @@ -44,5 +72,66 @@ "install_configure_android": "

    From the Android Menu home screen, tap Settings.

    Tap Wi-Fi on the menu. The screen listing all of the available networks will be shown (it is impossible to set custom DNS for mobile connection).

    Long press the network you're connected to, and tap Modify Network.

    On some devices, you may need to check the box for Advanced to see further settings. To adjust your Android DNS settings, you will need to switch the IP settings from DHCP to Static.

    Change set DNS 1 and DNS 2 values to your AdGuard Home server addresses.

    ", "install_configure_ios": "

    From the home screen, tap Settings.

    Choose Wi-Fi in the left menu (it is impossible to configure DNS for mobile networks).

    Tap on the name of the currently active network.

    In the DNS field enter your AdGuard Home server addresses.

    ", "install_configure_adresses": "AdGuard Home addresses:", - "install_configure_dhcp": "You can't set a custom DNS server on some types of routers. In this case it may help if you set up AdGuard Home as a DHCP server. Otherwise, you should search for the manual on how to customize DNS servers for your particular router model." + "install_configure_dhcp": "You can't set a custom DNS server on some types of routers. In this case it may help if you set up AdGuard Home as a DHCP server. Otherwise, you should search for the manual on how to customize DNS servers for your particular router model.", + + "header_adguard_status_enabled": "AdGuard Home is enabled", + "header_adguard_status_disabled": "AdGuard Home is disabled", + "header_server_uptime": "Server uptime is %value%", + + "top_clients": "Top clients", + "client_table_header": "Client", + "requests": "Requests", + "show_blocked_responses": "Blocked", + + "filter_category_general": "General", + "query_log_configuration": "Logs configuration", + "statistics_configuration": "Statistics configuration", + "statistics_clear": " Clear statistics", + "interval_24_hour": "24 hours", + "interval_days": "| %count% day | %count% days", + "interval_hours": "| %count% hour | %count% hours", + "save_btn": "Save", + "stats_reset": "Statistics reseted succesfully", + "statistics_retention": "Statistics retention", + "statistics_retention_desc": "If you decrease the interval value, some data will be lost", + "query_log_enable": "Enable log", + "query_log_clear": "Clear query logs", + "query_log_retention": "Query logs retention", + "query_log_cleared": "The query log has been successfully cleared", + "anonymize_client_ip": "Anonymize client IP", + "anonymize_client_ip_desc": "Don't save the full IP address of the client in logs and statistics", + "query_log_retention_confirm": "If you decrease the interval value, some data will be lost", + "query_log_confirm_clear": "Are you sure you want to clear the entire query log?", + "query_log_reset": "Query log cleared succesfully", + "statistics_clear_confirm": "Are you sure you want to clear statistics?", + + "stats_query_domain": "Top queried domains", + "top_blocked_domains": "Top blocked domains", + "domain": "Domain", + "all_queries": "All queries", + + "block_domain_use_filters_and_hosts": "Block domains using filters and hosts files", + "filters_interval": "Filters update interval", + "filters_block_toggle_hint": "You can setup blocking rules in the Filters settings.", + "use_adguard_browsing_sec": "Use AdGuard browsing security web service", + "use_adguard_browsing_sec_hint": "AdGuard Home will check if domain is blacklisted by the browsing security web service. It will use privacy-friendly lookup API to perform the check: only a short prefix of the domain name SHA256 hash is sent to the server.", + "use_adguard_parental": "Use AdGuard parental control web service", + "use_adguard_parental_hint": "AdGuard Home will check if domain contains adult materials. It uses the same privacy-friendly API as the browsing security web service.", + "enforce_safe_search": "Enforce safe search", + "enforce_save_search_hint": "AdGuard Home can enforce safe search in the following search engines: Google, Youtube, Bing, DuckDuckGo, Yandex, Pixabay.", + + "dashboard_blocked_ads": "Blocked Ads", + "dashboard_blocked_trackers": "Blocked trackers", + "dashboard_filter_rules": "Count of filter rules", + "dashboard_blocked_queries": "Blocked queries", + "dashboard_filter_rules_count": "%enabled% of %all% filters", + "dashboard_server_statistics": "Internal server statistic", + "other" : "Other", + "ads" : "Ads", + "trackers" : "Trackers", + "stats_adult": "Blocked adult websites", + "stats_malware_phishing": "Blocked malware/phishing", + "average_processing_time": "Average processing time", + "milliseconds_abbreviation": "ms" + } diff --git a/client2/src/localization/locales/index.ts b/client2/src/localization/locales/index.ts index ec51ba71..98266112 100644 --- a/client2/src/localization/locales/index.ts +++ b/client2/src/localization/locales/index.ts @@ -1,4 +1,3 @@ -import React from 'react'; import 'dayjs/locale/ru'; import { PickerLocale } from 'antd/es/date-picker/generatePicker'; @@ -22,15 +21,9 @@ export const messages: Record> = { [Locale.en]: enLang, }; +// TODO get languages and default locale from .twosky file export const DEFAULT_LOCALE = Locale.en; -export const reactFormater = (data: (JSX.Element | string)[]) => { - if (data.every((d) => typeof d === 'string')) { - return data.join(''); - } - return React.Children.toArray(data); -}; - export const LANGUAGES: { code: Locale; name: string }[] = [ { code: Locale.en, diff --git a/client2/src/main.pcss b/client2/src/main.pcss index 852b3e20..9d7573ba 100644 --- a/client2/src/main.pcss +++ b/client2/src/main.pcss @@ -5,20 +5,27 @@ --gray400: #a4a4a4; --gray700: #888888; --gray900: #4d4d4d; - --black: #000000; + --black: #131313; --green400: #67b279; --green700: #4d995f; --red400: #c23814; + --text-selection: #e7efff; + --header-height: 48px; --transition: 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); --font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Arial, sans-serif; --font-family-monospace: Monaco, Menlo, "Ubuntu Mono", Consolas, source-code-pro, monospace; } body { - font-size: 14px; + font-size: 16px; color: var(--gray900); } +::selection { + background: var(--text-selection); + color: var(--black); +} + @custom-media --m-viewport (min-width: 768px); @custom-media --l-viewport (min-width: 992px); @custom-media --xl-viewport (min-width: 1200px); diff --git a/client2/src/store/store.ts b/client2/src/store/store.ts index 24edb778..4debd161 100644 --- a/client2/src/store/store.ts +++ b/client2/src/store/store.ts @@ -1,11 +1,32 @@ import { createContext } from 'react'; import UI from './stores/ui'; +import Login from './stores/Login'; +import Dashboard from './stores/Dasnboard'; +import System from './stores/System'; +import GeneralSettings from './stores/GeneralSettings'; export class Store { ui: UI; + login: Login; + + dashboard: Dashboard; + + system: System; + + generalSettings: GeneralSettings; + constructor() { this.ui = new UI(this); + this.login = new Login(this); + this.dashboard = new Dashboard(this); + this.system = new System(this); + this.generalSettings = new GeneralSettings(this); + } + + init() { + this.dashboard.init(); + this.system.init(); } } diff --git a/client2/src/store/stores/Dasnboard.ts b/client2/src/store/stores/Dasnboard.ts new file mode 100644 index 00000000..c4aa3062 --- /dev/null +++ b/client2/src/store/stores/Dasnboard.ts @@ -0,0 +1,120 @@ +import { flow, makeAutoObservable, observable } from 'mobx'; + +import clientsApi from 'Apis/clients'; +import statsApi from 'Apis/stats'; +import filteringApi from 'Apis/filtering'; +import tlsApi from 'Apis/tls'; + +import { errorChecker } from 'Helpers/apiErrors'; +import { Store } from 'Store'; +import Stats, { IStats } from 'Entities/Stats'; +import StatsConfig, { IStatsConfig } from 'Entities/StatsConfig'; +import TlsConfig, { ITlsConfig } from 'Entities/TlsConfig'; +import { IClientsFindEntry } from 'Entities/ClientsFindEntry'; +import ClientFindSubEntry from 'Entities/ClientFindSubEntry'; +import FilterStatus, { IFilterStatus } from 'Entities/FilterStatus'; + +import { IStore } from './utils'; + +export default class Dashboard implements IStore { + rootStore: Store; + + inited = false; + + stats: Stats | undefined; + + statsConfig: StatsConfig | undefined; + + clientsInfo: Map; + + tlsConfig: TlsConfig | undefined; + + filteringConfig: FilterStatus | undefined; + + constructor(rootStore: Store) { + this.rootStore = rootStore; + makeAutoObservable(this, { + rootStore: false, + inited: observable, + init: flow, + + getStatsConfig: flow, + getTlsConfig: flow, + getClient: flow, + filteringStatus: flow, + + stats: observable.ref, + statsConfig: observable.ref, + clientsInfo: observable.ref, + tlsConfig: observable.ref, + filteringConfig: observable.ref, + }); + this.clientsInfo = new Map(); + if (this.rootStore.login.loggedIn) { + this.init(); + } + } + + * init() { + yield this.getStatsConfig(); + yield this.getTlsConfig(); + yield this.getStats(); + yield this.filteringStatus(); + this.inited = true; + } + + * getStats() { + const response = yield statsApi.stats(); + const { result } = errorChecker(response); + if (result) { + this.stats = new Stats(result); + if (this.stats.topClients) { + // TODO: fix bycicle + const topClients = this.stats.topClients.map((e) => { + return Object.keys(e.numberData)[0]; + }); + let firstClient = topClients.shift(); + firstClient += '&'; + const topClientsReq = firstClient + topClients.map((ip, index) => `ip${index + 1}=${ip}`).join('&'); + yield this.getClient(topClientsReq); + } + } + } + + * getClient(ip: string) { + // if & is encoding set in clientsFind qs options - encode: false + const response = yield clientsApi.clientsFind(ip); + const { result } = errorChecker(response); + if (result) { + this.clientsInfo = new Map(); + result.forEach((client) => { + const [clientIp, data] = Object.entries(client)[0]; + this.clientsInfo.set(clientIp, new ClientFindSubEntry(data)); + }); + } + } + + * getStatsConfig() { + const response = yield statsApi.statsInfo(); + const { result } = errorChecker(response); + if (result) { + this.statsConfig = new StatsConfig(result); + } + } + + * getTlsConfig() { + const response = yield tlsApi.tlsStatus(); + const { result } = errorChecker(response); + if (result) { + this.tlsConfig = new TlsConfig(result); + } + } + + * filteringStatus() { + const response = yield filteringApi.filteringStatus(); + const { result } = errorChecker(response); + if (result) { + this.filteringConfig = new FilterStatus(result); + } + } +} diff --git a/client2/src/store/stores/GeneralSettings.ts b/client2/src/store/stores/GeneralSettings.ts new file mode 100644 index 00000000..6b5e8287 --- /dev/null +++ b/client2/src/store/stores/GeneralSettings.ts @@ -0,0 +1,218 @@ +import { flow, makeAutoObservable, observable } from 'mobx'; +import { Store } from 'Store'; + +import statsApi from 'Apis/stats'; +import queryApi from 'Apis/log'; +import safeBrowsingApi from 'Apis/safebrowsing'; +import filteringApi from 'Apis/filtering'; +import parentalApi from 'Apis/parental'; +import safesearchApi from 'Apis/safesearch'; + +import StatsConfig, { IStatsConfig } from 'Entities/StatsConfig'; +import QueryLogConfig, { IQueryLogConfig } from 'Entities/QueryLogConfig'; +import FilterConfig, { IFilterConfig } from 'Entities/FilterConfig'; +import FilterStatus, { IFilterStatus } from 'Entities/FilterStatus'; + +import { errorChecker } from 'Helpers/apiErrors'; + +import { IStore } from './utils'; + +export default class SomeStore implements IStore { + rootStore: Store; + + inited = false; + + statsConfig: StatsConfig | undefined; + + queryLogConfig: QueryLogConfig | undefined; + + safebrowsing: boolean | undefined; + + filteringConfig: FilterConfig | undefined; + + parental: boolean | undefined; + + safesearch: boolean | undefined; + + constructor(rootStore: Store) { + this.rootStore = rootStore; + makeAutoObservable(this, { + rootStore: false, + inited: observable, + init: flow, + + statsConfig: observable.ref, + queryLogConfig: observable.ref, + safebrowsing: observable, + filteringConfig: observable.ref, + parental: observable, + safesearch: observable, + + updateStatsConfig: flow, + statsInfo: flow, + statsReset: flow, + updateQueryLogConfig: flow, + queryLogInfo: flow, + querylogClear: flow, + safebrowsingDisable: flow, + safebrowsingEnable: flow, + safebrowsingStatus: flow, + updateFilteringConfig: flow, + filteringStatus: flow, + parentalDisable: flow, + parentalEnable: flow, + parentalStatus: flow, + safesearchDisable: flow, + safesearchEnable: flow, + safesearchStatus: flow, + }); + } + + * init() { + yield this.statsInfo(); + yield this.queryLogInfo(); + yield this.safebrowsingStatus(); + yield this.filteringStatus(); + yield this.parentalStatus(); + yield this.safesearchStatus(); + this.inited = yield true; + } + + * updateStatsConfig(statsconfig: IStatsConfig) { + const response = yield statsApi.statsConfig(statsconfig); + const { result } = errorChecker(response); + if (result) { + yield this.statsInfo(); + } + } + + * statsInfo() { + const response = yield statsApi.statsInfo(); + const { result } = errorChecker(response); + if (result) { + this.statsConfig = new StatsConfig(result); + } + } + + * statsReset() { + const response = yield statsApi.statsReset(); + const { result } = errorChecker(response); + if (result) { + yield this.statsInfo(); + return true; + } + } + + * updateQueryLogConfig(querylogconfig: IQueryLogConfig) { + const response = yield queryApi.queryLogConfig(querylogconfig); + const { result } = errorChecker(response); + if (result) { + yield this.queryLogInfo(); + } + } + + * queryLogInfo() { + const response = yield queryApi.queryLogInfo(); + const { result } = errorChecker(response); + if (result) { + this.queryLogConfig = new QueryLogConfig(result); + } + } + + * querylogClear() { + const response = yield queryApi.querylogClear(); + const { result } = errorChecker(response); + if (result) { + yield this.queryLogInfo(); + } + } + + * safebrowsingDisable() { + const response = yield safeBrowsingApi.safebrowsingDisable(); + const { result } = errorChecker(response); + if (result) { + this.safebrowsing = false; + } + } + + * safebrowsingEnable() { + const response = yield safeBrowsingApi.safebrowsingEnable(); + const { result } = errorChecker(response); + if (result) { + this.safebrowsing = true; + } + } + + * safebrowsingStatus() { + const response = yield safeBrowsingApi.safebrowsingStatus(); + const { result } = errorChecker(response); + if (result) { + this.safebrowsing = result.enabled; + } + } + + * updateFilteringConfig(filterconfig: IFilterConfig) { + const response = yield filteringApi.filteringConfig(filterconfig); + const { result } = errorChecker(response); + if (result) { + yield this.filteringStatus(); + } + } + + * filteringStatus() { + const response = yield filteringApi.filteringStatus(); + const { result } = errorChecker(response); + if (result) { + this.filteringConfig = new FilterStatus(result); + } + } + + * parentalDisable() { + const response = yield parentalApi.parentalDisable(); + const { result } = errorChecker(response); + if (result) { + this.parental = false; + } + } + + * parentalEnable() { + // TODO: remove magic; + const response = yield parentalApi.parentalEnable('sensitivity=TEEN'); + const { result } = errorChecker(response); + if (result) { + this.parental = true; + } + } + + * parentalStatus() { + const response = yield parentalApi.parentalStatus(); + const { result } = errorChecker(response); + if (result) { + this.parental = result.enabled; + } + } + + * safesearchDisable() { + const response = yield safesearchApi.safesearchDisable(); + const { result } = errorChecker(response); + if (result) { + this.safesearch = false; + } + } + + * safesearchEnable() { + const response = yield safesearchApi.safesearchEnable(); + const { result } = errorChecker(response); + if (result) { + this.safesearch = true; + } + } + + * safesearchStatus() { + const response = yield safesearchApi.safesearchStatus(); + const { result } = errorChecker(response); + if (result) { + this.safesearch = result.enabled; + } + } +} diff --git a/client2/src/store/stores/Install.ts b/client2/src/store/stores/Install.ts index bd861ef2..92b2ff06 100644 --- a/client2/src/store/stores/Install.ts +++ b/client2/src/store/stores/Install.ts @@ -6,7 +6,7 @@ import { IInitialConfigurationBeta } from 'Entities/InitialConfigurationBeta'; import { errorChecker } from 'Helpers/apiErrors'; import { flow, makeAutoObservable } from 'mobx'; -import { Store } from 'Store'; +import { Store } from 'Store/installStore'; export default class Install { rootStore: Store; diff --git a/client2/src/store/stores/Login.ts b/client2/src/store/stores/Login.ts new file mode 100644 index 00000000..b1534731 --- /dev/null +++ b/client2/src/store/stores/Login.ts @@ -0,0 +1,45 @@ +import { flow, makeAutoObservable, observable } from 'mobx'; +import globalApi from 'Apis/global'; + +import { Store } from 'Store'; +import { errorChecker } from 'Helpers/apiErrors'; +import ProfileInfo, { IProfileInfo } from 'Entities/ProfileInfo'; +import { ILogin } from 'Entities/Login'; + +export default class Login { + rootStore: Store; + + loggedIn = false; + + constructor(rootStore: Store) { + this.rootStore = rootStore; + makeAutoObservable(this, { + loggedIn: observable, + rootStore: false, + checkLoggedIn: flow, + login: flow, + }); + this.checkLoggedIn(); + } + + * checkLoggedIn() { + const response = yield globalApi.getProfile(); + const { result } = errorChecker(response); + if (result) { + this.loggedIn = true; + this.rootStore.system.setProfile(new ProfileInfo(result)); + this.rootStore.init(); + } + // TODO: make smth with result, to not duplicate the request; + } + + * login(login: ILogin) { + const response = yield globalApi.login(login); + const { result, error } = errorChecker(response); + if (result === 200) { + this.loggedIn = true; + return; + } + return error; + } +} diff --git a/client2/src/store/stores/System.ts b/client2/src/store/stores/System.ts new file mode 100644 index 00000000..41652998 --- /dev/null +++ b/client2/src/store/stores/System.ts @@ -0,0 +1,75 @@ +import { flow, makeAutoObservable, observable, action } from 'mobx'; +import globalApi from 'Apis/global'; + +import { Store } from 'Store'; +import { errorChecker } from 'Helpers/apiErrors'; +import ProfileInfo, { IProfileInfo } from 'Entities/ProfileInfo'; +import ServerStatus, { IServerStatus } from 'Entities/ServerStatus'; + +import { IStore } from './utils'; + +export default class System implements IStore { + rootStore: Store; + + inited = false; + + status: ServerStatus | undefined; + + profile: ProfileInfo | undefined; + + constructor(rootStore: Store) { + this.rootStore = rootStore; + makeAutoObservable(this, { + rootStore: false, + inited: observable, + getServerStatus: flow, + init: flow, + setProfile: action, + switchServerStatus: flow, + getProfile: flow, + status: observable, + profile: observable, + }); + if (this.rootStore.login.loggedIn) { + this.init(); + } + } + + * init() { + yield this.getServerStatus(); + if (!this.profile) { + yield this.getProfile(); + } + this.inited = true; + } + + setProfile(profile: ProfileInfo) { + this.profile = profile; + } + + * getProfile() { + const response = yield globalApi.getProfile(); + const { result } = errorChecker(response); + if (result) { + this.profile = new ProfileInfo(result); + } + } + + * getServerStatus() { + const response = yield globalApi.status(); + const { result } = errorChecker(response); + if (result) { + this.status = new ServerStatus(result); + } + } + + * switchServerStatus(enable: boolean) { + const response = yield globalApi.dnsConfig({ + protection_enabled: enable, + }); + const { result } = errorChecker(response); + if (result) { + yield this.getServerStatus(); + } + } +} diff --git a/client2/src/store/stores/ui.ts b/client2/src/store/stores/ui.ts index 648127a7..c0d0377e 100644 --- a/client2/src/store/stores/ui.ts +++ b/client2/src/store/stores/ui.ts @@ -1,25 +1,36 @@ -import { makeAutoObservable, observable } from 'mobx'; +import React from 'react'; +import { makeAutoObservable, observable, action } from 'mobx'; +import { translate } from '@adguard/translate'; -import Translator, { DEFAULT_LOCALE, messages, Locale, reactFormater } from 'Localization'; +import { Locale, DEFAULT_LOCALE, i18n } from 'Localization'; import { Store } from 'Store'; +import { Store as InstallStore } from 'Store/installStore'; export default class UI { - rootStore: Store; + rootStore: Store | InstallStore; currentLang = DEFAULT_LOCALE; - intl = new Translator(Locale.en, messages, DEFAULT_LOCALE, reactFormater); + intl = translate.createReactTranslator(i18n(this.currentLang), React); - constructor(rootStore: Store) { + sidebarOpen = false; + + constructor(rootStore: Store | InstallStore) { this.rootStore = rootStore; makeAutoObservable(this, { intl: observable.struct, rootStore: false, + sidebarOpen: observable, + toggleSidebar: action, }); } updateLang = (lang: Locale) => { this.currentLang = lang; - this.intl = this.intl.updateTranslator(lang); + this.intl = translate.createReactTranslator(i18n(this.currentLang), React); + }; + + toggleSidebar = () => { + this.sidebarOpen = !this.sidebarOpen; }; } diff --git a/client2/src/store/stores/utils.ts b/client2/src/store/stores/utils.ts new file mode 100644 index 00000000..df679d58 --- /dev/null +++ b/client2/src/store/stores/utils.ts @@ -0,0 +1,38 @@ +import { Store } from 'Store'; + +export interface IStore { + rootStore: Store; + init: () => void; + inited: boolean; +} +/* +Each store should implement IStore to work properly if user not loggged in +and after log in like: + +import { flow, makeAutoObservable, observable } from 'mobx'; +import { Store } from 'Store'; +import { IStore } from './utils'; + +export default class SomeStore implements IStore { + rootStore: Store; + + inited = false; + + constructor(rootStore: Store) { + this.rootStore = rootStore; + makeAutoObservable(this, { + rootStore: false, + inited: observable, + init: flow, + }); + if (this.rootStore.login.loggedIn) { + this.init(); + } + } + + * init() { + this.inited = true; + } +} + +*/ diff --git a/client2/tsconfig.json b/client2/tsconfig.json index 5c70b792..fa3398ad 100644 --- a/client2/tsconfig.json +++ b/client2/tsconfig.json @@ -24,7 +24,7 @@ "Helpers/*": ["lib/helpers/*"], "Lib/*": ["lib/*"], "Localization": ["localization/index"], - "Routes": ["lib/routes"], + "Paths": ["components/App/Routes/Paths"], "Store": ["store/store"], "Store/*": ["store/*"] }, @@ -32,7 +32,8 @@ "react", "react-dom", "node" - ] + ], + "useDefineForClassFields": true }, "include": ["src/**/*", "./declaration.d.ts"], "exclude": ["./node_modules/**/*", "./scripts/generator/**/*"] diff --git a/client2/yarn.lock b/client2/yarn.lock index a0891ba1..d171bb54 100644 --- a/client2/yarn.lock +++ b/client2/yarn.lock @@ -2,46 +2,39 @@ # yarn lockfile v1 -"@ant-design/colors@^3.1.0": - version "3.2.2" - resolved "https://registry.yarnpkg.com/@ant-design/colors/-/colors-3.2.2.tgz#5ad43d619e911f3488ebac303d606e66a8423903" - integrity sha512-YKgNbG2dlzqMhA9NtI3/pbY16m3Yl/EeWBRa+lB1X1YaYxHrxNexiQYCLTWO/uDvAjLFMEDU+zR901waBtMtjQ== - dependencies: - tinycolor2 "^1.4.1" +"@adguard/translate@^0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@adguard/translate/-/translate-0.2.0.tgz#6b74b037167ec0cae32e6b7423cb35eb1b08a1b3" + integrity sha512-zvpaEKMABcCCuEr7WpGRGgfdzp8L0OMuM0KUI7uwWapX+/i75ifffbWnKxi/LUdZDJu8kJPnmLN/DD5hngZuQQ== -"@ant-design/colors@^4.0.5": - version "4.0.5" - resolved "https://registry.yarnpkg.com/@ant-design/colors/-/colors-4.0.5.tgz#d7d100d7545cca8f624954604a6892fc48ba5aae" - integrity sha512-3mnuX2prnWOWvpFTS2WH2LoouWlOgtnIpc6IarWN6GOzzLF8dW/U8UctuvIPhoboETehZfJ61XP+CGakBEPJ3Q== +"@ant-design/colors@^5.0.0": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@ant-design/colors/-/colors-5.0.1.tgz#09670f2f44a7473d7bc01be901c48ec10f12c7a4" + integrity sha512-x1TUaRILaqy3zgFNo+kIqOa3eTYPt81H1/3E4dCjDP4Qvk/xaPEizLDFdRUcIx0cWwyu2LklwfyLHWpbYK8v6A== dependencies: - tinycolor2 "^1.4.1" - -"@ant-design/css-animation@^1.7.2": - version "1.7.3" - resolved "https://registry.yarnpkg.com/@ant-design/css-animation/-/css-animation-1.7.3.tgz#60a1c970014e86b28f940510d69e503e428f1136" - integrity sha512-LrX0OGZtW+W6iLnTAqnTaoIsRelYeuLZWsrmBJFUXDALQphPsN8cE5DCsmoSlL0QYb94BQxINiuS70Ar/8BNgA== + "@ctrl/tinycolor" "^3.3.1" "@ant-design/icons-svg@^4.0.0": version "4.1.0" resolved "https://registry.yarnpkg.com/@ant-design/icons-svg/-/icons-svg-4.1.0.tgz#480b025f4b20ef7fe8f47d4a4846e4fee84ea06c" integrity sha512-Fi03PfuUqRs76aI3UWYpP864lkrfPo0hluwGqh7NJdLhvH4iRDc3jbJqZIvRDLHKbXrvAfPPV3+zjUccfFvWOQ== -"@ant-design/icons@^4.2.1", "@ant-design/icons@^4.2.2": - version "4.2.2" - resolved "https://registry.yarnpkg.com/@ant-design/icons/-/icons-4.2.2.tgz#6533c5a02aec49238ec4748074845ad7d85a4f5e" - integrity sha512-DrVV+wcupnHS7PehJ6KiTcJtAR5c25UMgjGECCc6pUT9rsvw0AuYG+a4HDjfxEQuDqKTHwW+oX/nIvCymyLE8Q== +"@ant-design/icons@^4.4.0": + version "4.4.0" + resolved "https://registry.yarnpkg.com/@ant-design/icons/-/icons-4.4.0.tgz#d4e4ba5910454e1d3f67a802d2aad9ee75a51dea" + integrity sha512-+X44IouK56JbP3r7zM+Zoykv5wQlXBlxY0NTaFXGpiyYSS/Bh6HIo9aTF62QkSuDTqA3UpeNVTRFioKKRmkWDQ== dependencies: - "@ant-design/colors" "^3.1.0" + "@ant-design/colors" "^5.0.0" "@ant-design/icons-svg" "^4.0.0" - "@babel/runtime" "^7.10.4" + "@babel/runtime" "^7.11.2" classnames "^2.2.6" insert-css "^2.0.0" rc-util "^5.0.1" -"@ant-design/react-slick@~0.27.0": - version "0.27.11" - resolved "https://registry.yarnpkg.com/@ant-design/react-slick/-/react-slick-0.27.11.tgz#ce788312ed8e64fcba2f7bb4556f47486b407c6e" - integrity sha512-KPJ1lleHW11bameFauI77Lb9N7O/4ulT1kplVdRQykWLv3oKVSGKVaekC3DM/Z0MYmKfCXCucpFnfgGMEHNM+w== +"@ant-design/react-slick@~0.28.1": + version "0.28.1" + resolved "https://registry.yarnpkg.com/@ant-design/react-slick/-/react-slick-0.28.1.tgz#2e0720838cb57ab8818384dcc96b2a8c61fcd01e" + integrity sha512-Uk+GNexHOmiK3BMk/xvliNsNt+LYnN49u5o4lqeuMKXJlNqE9kGpEF03KpxDqu/zybO0/0yAJALha8oPtR5iHA== dependencies: "@babel/runtime" "^7.10.4" classnames "^2.2.5" @@ -49,73 +42,72 @@ lodash "^4.17.15" resize-observer-polyfill "^1.5.0" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a" - integrity sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg== +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.11": + version "7.12.11" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f" + integrity sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw== dependencies: "@babel/highlight" "^7.10.4" "@babel/core@>=7.9.0": - version "7.12.3" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.12.3.tgz#1b436884e1e3bff6fb1328dc02b208759de92ad8" - integrity sha512-0qXcZYKZp3/6N2jKYVxZv0aNCsxTSVCiK72DTiTYZAu7sjg73W0/aynWjMbiGd87EQL4WyA8reiJVh92AVla9g== + version "7.12.10" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.12.10.tgz#b79a2e1b9f70ed3d84bbfb6d8c4ef825f606bccd" + integrity sha512-eTAlQKq65zHfkHZV0sIVODCPGVgoo1HdBlbSLi9CqOzuZanMv2ihzY+4paiKr1mH+XmYESMAmJ/dpZ68eN6d8w== dependencies: "@babel/code-frame" "^7.10.4" - "@babel/generator" "^7.12.1" + "@babel/generator" "^7.12.10" "@babel/helper-module-transforms" "^7.12.1" - "@babel/helpers" "^7.12.1" - "@babel/parser" "^7.12.3" - "@babel/template" "^7.10.4" - "@babel/traverse" "^7.12.1" - "@babel/types" "^7.12.1" + "@babel/helpers" "^7.12.5" + "@babel/parser" "^7.12.10" + "@babel/template" "^7.12.7" + "@babel/traverse" "^7.12.10" + "@babel/types" "^7.12.10" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.1" json5 "^2.1.2" lodash "^4.17.19" - resolve "^1.3.2" semver "^5.4.1" source-map "^0.5.0" -"@babel/generator@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.12.1.tgz#0d70be32bdaa03d7c51c8597dda76e0df1f15468" - integrity sha512-DB+6rafIdc9o72Yc3/Ph5h+6hUjeOp66pF0naQBgUFFuPqzQwIlPTm3xZR7YNvduIMtkDIj2t21LSQwnbCrXvg== +"@babel/generator@^7.12.10", "@babel/generator@^7.12.11": + version "7.12.11" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.12.11.tgz#98a7df7b8c358c9a37ab07a24056853016aba3af" + integrity sha512-Ggg6WPOJtSi8yYQvLVjG8F/TlpWDlKx0OpS4Kt+xMQPs5OaGYWy+v1A+1TvxI6sAMGZpKWWoAQ1DaeQbImlItA== dependencies: - "@babel/types" "^7.12.1" + "@babel/types" "^7.12.11" jsesc "^2.5.1" source-map "^0.5.0" -"@babel/helper-function-name@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz#d2d3b20c59ad8c47112fa7d2a94bc09d5ef82f1a" - integrity sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ== +"@babel/helper-function-name@^7.12.11": + version "7.12.11" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.12.11.tgz#1fd7738aee5dcf53c3ecff24f1da9c511ec47b42" + integrity sha512-AtQKjtYNolKNi6nNNVLQ27CP6D9oFR6bq/HPYSizlzbp7uC1M59XJe8L+0uXjbIaZaUJF99ruHqVGiKXU/7ybA== dependencies: - "@babel/helper-get-function-arity" "^7.10.4" - "@babel/template" "^7.10.4" - "@babel/types" "^7.10.4" + "@babel/helper-get-function-arity" "^7.12.10" + "@babel/template" "^7.12.7" + "@babel/types" "^7.12.11" -"@babel/helper-get-function-arity@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz#98c1cbea0e2332f33f9a4661b8ce1505b2c19ba2" - integrity sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A== +"@babel/helper-get-function-arity@^7.12.10": + version "7.12.10" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.10.tgz#b158817a3165b5faa2047825dfa61970ddcc16cf" + integrity sha512-mm0n5BPjR06wh9mPQaDdXWDoll/j5UpCAPl1x8fS71GHm7HA6Ua2V4ylG1Ju8lvcTOietbPNNPaSilKj+pj+Ag== dependencies: - "@babel/types" "^7.10.4" + "@babel/types" "^7.12.10" -"@babel/helper-member-expression-to-functions@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.12.1.tgz#fba0f2fcff3fba00e6ecb664bb5e6e26e2d6165c" - integrity sha512-k0CIe3tXUKTRSoEx1LQEPFU9vRQfqHtl+kf8eNnDqb4AUJEy5pz6aIiog+YWtVm2jpggjS1laH68bPsR+KWWPQ== +"@babel/helper-member-expression-to-functions@^7.12.7": + version "7.12.7" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.12.7.tgz#aa77bd0396ec8114e5e30787efa78599d874a855" + integrity sha512-DCsuPyeWxeHgh1Dus7APn7iza42i/qXqiFPWyBDdOFtvS581JQePsc1F/nD+fHrcswhLlRc2UpYS1NwERxZhHw== dependencies: - "@babel/types" "^7.12.1" + "@babel/types" "^7.12.7" "@babel/helper-module-imports@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.12.1.tgz#1644c01591a15a2f084dd6d092d9430eb1d1216c" - integrity sha512-ZeC1TlMSvikvJNy1v/wPIazCu3NdOwgYZLIkmIyAsGhqkNpiDoQQRmaCK8YP4Pq3GPTLPV9WXaPCJKvx06JxKA== + version "7.12.5" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.12.5.tgz#1bfc0229f794988f76ed0a4d4e90860850b54dfb" + integrity sha512-SR713Ogqg6++uexFRORf/+nPXMmWIn80TALu0uaFb+iQIUoR7bOC7zBWyzBs5b3tBBJXuyD0cRu1F15GyzjOWA== dependencies: - "@babel/types" "^7.12.1" + "@babel/types" "^7.12.5" "@babel/helper-module-transforms@^7.12.1": version "7.12.1" @@ -132,22 +124,22 @@ "@babel/types" "^7.12.1" lodash "^4.17.19" -"@babel/helper-optimise-call-expression@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz#50dc96413d594f995a77905905b05893cd779673" - integrity sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg== +"@babel/helper-optimise-call-expression@^7.12.10": + version "7.12.10" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.10.tgz#94ca4e306ee11a7dd6e9f42823e2ac6b49881e2d" + integrity sha512-4tpbU0SrSTjjt65UMWSrUOPZTsgvPgGG4S8QSTNHacKzpS51IVWGDj0yCwyeZND/i+LSN2g/O63jEXEWm49sYQ== dependencies: - "@babel/types" "^7.10.4" + "@babel/types" "^7.12.10" "@babel/helper-replace-supers@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.12.1.tgz#f15c9cc897439281891e11d5ce12562ac0cf3fa9" - integrity sha512-zJjTvtNJnCFsCXVi5rUInstLd/EIVNmIKA1Q9ynESmMBWPWd+7sdR+G4/wdu+Mppfep0XLyG2m7EBPvjCeFyrw== + version "7.12.11" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.12.11.tgz#ea511658fc66c7908f923106dd88e08d1997d60d" + integrity sha512-q+w1cqmhL7R0FNzth/PLLp2N+scXEK/L2AHbXUyydxp828F4FEa5WcVoqui9vFRiHDQErj9Zof8azP32uGVTRA== dependencies: - "@babel/helper-member-expression-to-functions" "^7.12.1" - "@babel/helper-optimise-call-expression" "^7.10.4" - "@babel/traverse" "^7.12.1" - "@babel/types" "^7.12.1" + "@babel/helper-member-expression-to-functions" "^7.12.7" + "@babel/helper-optimise-call-expression" "^7.12.10" + "@babel/traverse" "^7.12.10" + "@babel/types" "^7.12.11" "@babel/helper-simple-access@^7.12.1": version "7.12.1" @@ -156,26 +148,26 @@ dependencies: "@babel/types" "^7.12.1" -"@babel/helper-split-export-declaration@^7.11.0": - version "7.11.0" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz#f8a491244acf6a676158ac42072911ba83ad099f" - integrity sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg== +"@babel/helper-split-export-declaration@^7.11.0", "@babel/helper-split-export-declaration@^7.12.11": + version "7.12.11" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.11.tgz#1b4cc424458643c47d37022223da33d76ea4603a" + integrity sha512-LsIVN8j48gHgwzfocYUSkO/hjYAOJqlpJEc7tGXcIm4cubjVUf8LGW6eWRyxEu7gA25q02p0rQUWoCI33HNS5g== dependencies: - "@babel/types" "^7.11.0" + "@babel/types" "^7.12.11" -"@babel/helper-validator-identifier@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz#a78c7a7251e01f616512d31b10adcf52ada5e0d2" - integrity sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw== +"@babel/helper-validator-identifier@^7.10.4", "@babel/helper-validator-identifier@^7.12.11": + version "7.12.11" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz#c9a1f021917dcb5ccf0d4e453e399022981fc9ed" + integrity sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw== -"@babel/helpers@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.12.1.tgz#8a8261c1d438ec18cb890434df4ec768734c1e79" - integrity sha512-9JoDSBGoWtmbay98efmT2+mySkwjzeFeAL9BuWNoVQpkPFQF8SIIFUfY5os9u8wVzglzoiPRSW7cuJmBDUt43g== +"@babel/helpers@^7.12.5": + version "7.12.5" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.12.5.tgz#1a1ba4a768d9b58310eda516c449913fe647116e" + integrity sha512-lgKGMQlKqA8meJqKsW6rUnc4MdUk35Ln0ATDqdM1a/UpARODdI4j5Y5lVfUScnSNkJcdCRAaWkspykNoFg9sJA== dependencies: "@babel/template" "^7.10.4" - "@babel/traverse" "^7.12.1" - "@babel/types" "^7.12.1" + "@babel/traverse" "^7.12.5" + "@babel/types" "^7.12.5" "@babel/highlight@^7.10.4": version "7.10.4" @@ -186,48 +178,48 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.10.4", "@babel/parser@^7.12.1", "@babel/parser@^7.12.3": - version "7.12.3" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.12.3.tgz#a305415ebe7a6c7023b40b5122a0662d928334cd" - integrity sha512-kFsOS0IbsuhO5ojF8Hc8z/8vEIOkylVBrjiZUbLTE3XFe0Qi+uu6HjzQixkFaqr0ZPAMZcBVxEwmsnsLPZ2Xsw== +"@babel/parser@^7.12.10", "@babel/parser@^7.12.11", "@babel/parser@^7.12.7": + version "7.12.11" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.12.11.tgz#9ce3595bcd74bc5c466905e86c535b8b25011e79" + integrity sha512-N3UxG+uuF4CMYoNj8AhnbAcJF0PiuJ9KHuy1lQmkYsxTer/MAH9UBNHsBoAX/4s6NvlDD047No8mYVGGzLL4hg== -"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.1", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.4", "@babel/runtime@^7.11.1", "@babel/runtime@^7.11.2", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.1.tgz#b4116a6b6711d010b2dad3b7b6e43bf1b9954740" - integrity sha512-J5AIf3vPj3UwXaAzb5j1xM4WAQDX3EMgemF8rjCP3SoW09LfRKAXQKt6CoVYl230P6iWdRcBbnLDDdnqWxZSCA== +"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.1", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.4", "@babel/runtime@^7.11.1", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.8.4": + version "7.12.5" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.5.tgz#410e7e487441e1b360c29be715d870d9b985882e" + integrity sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg== dependencies: regenerator-runtime "^0.13.4" -"@babel/template@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.4.tgz#3251996c4200ebc71d1a8fc405fba940f36ba278" - integrity sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA== +"@babel/template@^7.10.4", "@babel/template@^7.12.7": + version "7.12.7" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.12.7.tgz#c817233696018e39fbb6c491d2fb684e05ed43bc" + integrity sha512-GkDzmHS6GV7ZeXfJZ0tLRBhZcMcY0/Lnb+eEbXDBfCAcZCjrZKe6p3J4we/D24O9Y8enxWAg1cWwof59yLh2ow== dependencies: "@babel/code-frame" "^7.10.4" - "@babel/parser" "^7.10.4" - "@babel/types" "^7.10.4" + "@babel/parser" "^7.12.7" + "@babel/types" "^7.12.7" -"@babel/traverse@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.12.1.tgz#941395e0c5cc86d5d3e75caa095d3924526f0c1e" - integrity sha512-MA3WPoRt1ZHo2ZmoGKNqi20YnPt0B1S0GTZEPhhd+hw2KGUzBlHuVunj6K4sNuK+reEvyiPwtp0cpaqLzJDmAw== +"@babel/traverse@^7.12.1", "@babel/traverse@^7.12.10", "@babel/traverse@^7.12.5": + version "7.12.12" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.12.12.tgz#d0cd87892704edd8da002d674bc811ce64743376" + integrity sha512-s88i0X0lPy45RrLM8b9mz8RPH5FqO9G9p7ti59cToE44xFm1Q+Pjh5Gq4SXBbtb88X7Uy7pexeqRIQDDMNkL0w== dependencies: - "@babel/code-frame" "^7.10.4" - "@babel/generator" "^7.12.1" - "@babel/helper-function-name" "^7.10.4" - "@babel/helper-split-export-declaration" "^7.11.0" - "@babel/parser" "^7.12.1" - "@babel/types" "^7.12.1" + "@babel/code-frame" "^7.12.11" + "@babel/generator" "^7.12.11" + "@babel/helper-function-name" "^7.12.11" + "@babel/helper-split-export-declaration" "^7.12.11" + "@babel/parser" "^7.12.11" + "@babel/types" "^7.12.12" debug "^4.1.0" globals "^11.1.0" lodash "^4.17.19" -"@babel/types@^7.10.4", "@babel/types@^7.11.0", "@babel/types@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.12.1.tgz#e109d9ab99a8de735be287ee3d6a9947a190c4ae" - integrity sha512-BzSY3NJBKM4kyatSOWh3D/JJ2O3CVzBybHWxtgxnggaxEuaSTTDqeiSb/xk9lrkw2Tbqyivw5ZU4rT+EfznQsA== +"@babel/types@^7.12.1", "@babel/types@^7.12.10", "@babel/types@^7.12.11", "@babel/types@^7.12.12", "@babel/types@^7.12.5", "@babel/types@^7.12.7": + version "7.12.12" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.12.12.tgz#4608a6ec313abbd87afa55004d373ad04a96c299" + integrity sha512-lnIX7piTxOH22xE7fDXDbSHg9MM1/6ORnafpJmov5rs0kX5g4BZxeXNJLXsMRiO0U5Rb8/FvMS6xlTnTHvxonQ== dependencies: - "@babel/helper-validator-identifier" "^7.10.4" + "@babel/helper-validator-identifier" "^7.12.11" lodash "^4.17.19" to-fast-properties "^2.0.0" @@ -236,6 +228,16 @@ resolved "https://registry.yarnpkg.com/@csstools/convert-colors/-/convert-colors-1.4.0.tgz#ad495dc41b12e75d588c6db8b9834f08fa131eb7" integrity sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw== +"@ctrl/tinycolor@^3.3.1": + version "3.3.3" + resolved "https://registry.yarnpkg.com/@ctrl/tinycolor/-/tinycolor-3.3.3.tgz#980487763bc7c9238d6d88d1ac0dee2d4df3df68" + integrity sha512-v75yutF4BDMv9weDQVM+K5XEfjiODhugSV729pnoxtBDO61ij2CsDnQa4N4E9xGaH3/FX5ASZjnajljT2F71tA== + +"@discoveryjs/json-ext@^0.5.0": + version "0.5.2" + resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.2.tgz#8f03a22a04de437254e8ce8cc84ba39689288752" + integrity sha512-HyYEUDeIj5rRQU2Hk5HTB2uHsbRQpF70nvMhVzi+VJR0X+xNEhjPui4/kBf3VeH/wqD28PT4sVOm8qqLjBrSZg== + "@dsherret/to-absolute-glob@^2.0.2": version "2.0.2" resolved "https://registry.yarnpkg.com/@dsherret/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz#1f6475dc8bd974cea07a2daf3864b317b1dd332c" @@ -244,10 +246,10 @@ is-absolute "^1.0.0" is-negated-glob "^1.0.0" -"@eslint/eslintrc@^0.1.3": - version "0.1.3" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.1.3.tgz#7d1a2b2358552cc04834c0979bd4275362e37085" - integrity sha512-4YVwPkANLeNtRjMekzux1ci8hIaH5eGKktGqR0d3LWsKNn5B2X/1Z6Trxy7jQXl9EBGE6Yj02O+t09FMeRllaA== +"@eslint/eslintrc@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.3.0.tgz#d736d6963d7003b6514e6324bec9c602ac340318" + integrity sha512-1JTKgrOKAHVivSvOYw+sJOunkBjUOvjqWk1DPja7ZFhIS2mX/4EgTT8M7eTK9jrKhL/FvXXEbQwIs3pg1xp3dg== dependencies: ajv "^6.12.4" debug "^4.1.1" @@ -256,100 +258,101 @@ ignore "^4.0.6" import-fresh "^3.2.1" js-yaml "^3.13.1" - lodash "^4.17.19" + lodash "^4.17.20" minimatch "^3.0.4" strip-json-comments "^3.1.1" -"@nodelib/fs.scandir@2.1.3": - version "2.1.3" - resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz#3a582bdb53804c6ba6d146579c46e52130cf4a3b" - integrity sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw== +"@nodelib/fs.scandir@2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz#d4b3549a5db5de2683e0c1071ab4f140904bbf69" + integrity sha512-33g3pMJk3bg5nXbL/+CY6I2eJDzZAni49PfJnL5fghPTggPvBd/pFNSgJsdAgWptuFu7qq/ERvOYFlhvsLTCKA== dependencies: - "@nodelib/fs.stat" "2.0.3" + "@nodelib/fs.stat" "2.0.4" run-parallel "^1.1.9" -"@nodelib/fs.stat@2.0.3", "@nodelib/fs.stat@^2.0.2": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz#34dc5f4cabbc720f4e60f75a747e7ecd6c175bd3" - integrity sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA== +"@nodelib/fs.stat@2.0.4", "@nodelib/fs.stat@^2.0.2": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz#a3f2dd61bab43b8db8fa108a121cfffe4c676655" + integrity sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q== "@nodelib/fs.walk@^1.2.3": - version "1.2.4" - resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz#011b9202a70a6366e436ca5c065844528ab04976" - integrity sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ== + version "1.2.6" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.6.tgz#cce9396b30aa5afe9e3756608f5831adcb53d063" + integrity sha512-8Broas6vTtW4GIXTAHDoE32hnN2M5ykgCpWGbuXHQ15vEMqr23pB76e/GZcYsZCHALv50ktd24qhEyKr6wBtow== dependencies: - "@nodelib/fs.scandir" "2.1.3" + "@nodelib/fs.scandir" "2.1.4" fastq "^1.6.0" "@npmcli/move-file@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-1.0.1.tgz#de103070dac0f48ce49cf6693c23af59c0f70464" - integrity sha512-Uv6h1sT+0DrblvIrolFtbvM1FgWm+/sy4B3pvLp67Zys+thcukzS5ekn7HsZFGpWP4Q3fYJCljbWQE/XivMRLw== + version "1.1.0" + resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-1.1.0.tgz#4ef8a53d727b9e43facf35404caf55ebf92cfec8" + integrity sha512-Iv2iq0JuyYjKeFkSR4LPaCdDZwlGK9X2cP/01nJcp3yMJ1FjNd9vpiEYvLUgzBxKPg2SFmaOhizoQsPc0LWeOQ== dependencies: mkdirp "^1.0.4" + rimraf "^2.7.1" -"@sentry/browser@5.27.0": - version "5.27.0" - resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-5.27.0.tgz#35b77f076fb5f08c91eff23f3c067ee15df0ab90" - integrity sha512-AByUVSy5YctTxWGA6HaeTkZXDCmHbeurqLkR6U9h4HzEHZq3laxrYQ1HiWcaW1IgFDqZcEmD14kDOVY4GhF3QQ== +"@sentry/browser@5.30.0": + version "5.30.0" + resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-5.30.0.tgz#c28f49d551db3172080caef9f18791a7fd39e3b3" + integrity sha512-rOb58ZNVJWh1VuMuBG1mL9r54nZqKeaIlwSlvzJfc89vyfd7n6tQ1UXMN383QBz/MS5H5z44Hy5eE+7pCrYAfw== dependencies: - "@sentry/core" "5.27.0" - "@sentry/types" "5.27.0" - "@sentry/utils" "5.27.0" + "@sentry/core" "5.30.0" + "@sentry/types" "5.30.0" + "@sentry/utils" "5.30.0" tslib "^1.9.3" -"@sentry/core@5.27.0": - version "5.27.0" - resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.27.0.tgz#661b2fd1beecaa37c013a6c364330fa29c847b3c" - integrity sha512-ddvAxVszsHzFzGedii1NxfKU3GxAEGJV5eXNlA2hqS0/OKl+IOjuI6aJjg55LMTEEejqr9djXqDMk6y5av6UKg== +"@sentry/core@5.30.0": + version "5.30.0" + resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.30.0.tgz#6b203664f69e75106ee8b5a2fe1d717379b331f3" + integrity sha512-TmfrII8w1PQZSZgPpUESqjB+jC6MvZJZdLtE/0hZ+SrnKhW3x5WlYLvTXZpcWePYBku7rl2wn1RZu6uT0qCTeg== dependencies: - "@sentry/hub" "5.27.0" - "@sentry/minimal" "5.27.0" - "@sentry/types" "5.27.0" - "@sentry/utils" "5.27.0" + "@sentry/hub" "5.30.0" + "@sentry/minimal" "5.30.0" + "@sentry/types" "5.30.0" + "@sentry/utils" "5.30.0" tslib "^1.9.3" -"@sentry/hub@5.27.0": - version "5.27.0" - resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-5.27.0.tgz#dcd7b36d216997f0283bd3334cbce8004d56ef89" - integrity sha512-Qe4nndgDEY8n3kKEWJTw5M201dgsoB9ZQ10483cVpGCtOfZZuzXEr4EaLG3BefH8YFvlgUP3YlxD7XFoJioRjg== +"@sentry/hub@5.30.0": + version "5.30.0" + resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-5.30.0.tgz#2453be9b9cb903404366e198bd30c7ca74cdc100" + integrity sha512-2tYrGnzb1gKz2EkMDQcfLrDTvmGcQPuWxLnJKXJvYTQDGLlEvi2tWz1VIHjunmOvJrB5aIQLhm+dcMRwFZDCqQ== dependencies: - "@sentry/types" "5.27.0" - "@sentry/utils" "5.27.0" + "@sentry/types" "5.30.0" + "@sentry/utils" "5.30.0" tslib "^1.9.3" -"@sentry/minimal@5.27.0": - version "5.27.0" - resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-5.27.0.tgz#8c2fdcf9cd1e59d8ff1848a7905bac304f8e206b" - integrity sha512-KidWjo2jNd8IwPhEIDC0YddjwuIdVxTEsmpRkZ6afuiR5rMQsiqA0EwsndWiAjs67qxQRj/VD/1Xghxe0nHzXQ== +"@sentry/minimal@5.30.0": + version "5.30.0" + resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-5.30.0.tgz#ce3d3a6a273428e0084adcb800bc12e72d34637b" + integrity sha512-BwWb/owZKtkDX+Sc4zCSTNcvZUq7YcH3uAVlmh/gtR9rmUvbzAA3ewLuB3myi4wWRAMEtny6+J/FN/x+2wn9Xw== dependencies: - "@sentry/hub" "5.27.0" - "@sentry/types" "5.27.0" + "@sentry/hub" "5.30.0" + "@sentry/types" "5.30.0" tslib "^1.9.3" "@sentry/react@^5.27.0": - version "5.27.0" - resolved "https://registry.yarnpkg.com/@sentry/react/-/react-5.27.0.tgz#ef503b0c28b03088f1de7693f75276ccc96c5e14" - integrity sha512-7dYKhQ6tgDgAed1neXjID9mEviX9IzL/OkG+hU8zffUqcUtMziVHtvozf3ePz75ReRR/Bumc6fGnSMlnEcwbKg== + version "5.30.0" + resolved "https://registry.yarnpkg.com/@sentry/react/-/react-5.30.0.tgz#320e05f766b6a26faefa8d76d1101fd50c69f541" + integrity sha512-dvn4mqCgbeEuUXEGp5P9PaW5j4GWTFUSdx/yG8f9IxNZv5zM+7otjog9ukrubFZvlxVxD/PrIxK0MhadfFY/Dw== dependencies: - "@sentry/browser" "5.27.0" - "@sentry/minimal" "5.27.0" - "@sentry/types" "5.27.0" - "@sentry/utils" "5.27.0" + "@sentry/browser" "5.30.0" + "@sentry/minimal" "5.30.0" + "@sentry/types" "5.30.0" + "@sentry/utils" "5.30.0" hoist-non-react-statics "^3.3.2" tslib "^1.9.3" -"@sentry/types@5.27.0": - version "5.27.0" - resolved "https://registry.yarnpkg.com/@sentry/types/-/types-5.27.0.tgz#cea288d02c727ef83541768b8738e6a829dfc831" - integrity sha512-coB2bMDxmzTwIWcXbzbnE2JtEqDkvmK9+KyZZNI/Mk3wwabFYqL7hOnqXB45/+hx+6l9/siWmB1l5um3tzqdOw== +"@sentry/types@5.30.0": + version "5.30.0" + resolved "https://registry.yarnpkg.com/@sentry/types/-/types-5.30.0.tgz#19709bbe12a1a0115bc790b8942917da5636f402" + integrity sha512-R8xOqlSTZ+htqrfteCWU5Nk0CDN5ApUTvrlvBuiH1DyP6czDZ4ktbZB0hAgBlVcK0U+qpD3ag3Tqqpa5Q67rPw== -"@sentry/utils@5.27.0": - version "5.27.0" - resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-5.27.0.tgz#21c15401b43041b1208521465c09c64eafc2e0ff" - integrity sha512-XrdoxOsjqF9AVmeCefNgY0r3lvZBj34bzsG3TI8Z1bjQKB3iF/2yAI/bdo+sUqAiJiiPSk5p6SiPkyeTsSdBhg== +"@sentry/utils@5.30.0": + version "5.30.0" + resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-5.30.0.tgz#9a5bd7ccff85ccfe7856d493bffa64cabc41e980" + integrity sha512-zaYmoH0NWWtvnJjC9/CBseXMtKHm/tm40sz3YfJRxeQjyzRqNQPgivpd9R/oDJCYj999mzdW382p/qi2ypjLww== dependencies: - "@sentry/types" "5.27.0" + "@sentry/types" "5.30.0" tslib "^1.9.3" "@stylelint/postcss-css-in-js@^0.37.2": @@ -359,13 +362,13 @@ dependencies: "@babel/core" ">=7.9.0" -"@stylelint/postcss-markdown@^0.36.1": - version "0.36.1" - resolved "https://registry.yarnpkg.com/@stylelint/postcss-markdown/-/postcss-markdown-0.36.1.tgz#829b87e6c0f108014533d9d7b987dc9efb6632e8" - integrity sha512-iDxMBWk9nB2BPi1VFQ+Dc5+XpvODBHw2n3tYpaBZuEAFQlbtF9If0Qh5LTTwSi/XwdbJ2jt+0dis3i8omyggpw== +"@stylelint/postcss-markdown@^0.36.2": + version "0.36.2" + resolved "https://registry.yarnpkg.com/@stylelint/postcss-markdown/-/postcss-markdown-0.36.2.tgz#0a540c4692f8dcdfc13c8e352c17e7bfee2bb391" + integrity sha512-2kGbqUVJUGE8dM+bMzXG/PYUWKkjLIkRLWNh39OaADkiabDRdw8ATFCgbMz5xdIcvwspPAluSL7uY+ZiTWdWmQ== dependencies: - remark "^12.0.0" - unist-util-find-all-after "^3.0.1" + remark "^13.0.0" + unist-util-find-all-after "^3.0.2" "@ts-morph/common@~0.6.0": version "0.6.0" @@ -385,9 +388,9 @@ integrity sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA== "@types/classnames@^2.2.10": - version "2.2.10" - resolved "https://registry.yarnpkg.com/@types/classnames/-/classnames-2.2.10.tgz#cc658ca319b6355399efc1f5b9e818f1a24bf999" - integrity sha512-1UzDldn9GfYYEsWWnn/P4wkTlkZDH7lDb0wBMGbtIQc9zXEQq7FlKBdZUn6OBqD8sKZZ2RQO2mAjGpXiDGoRmQ== + version "2.2.11" + resolved "https://registry.yarnpkg.com/@types/classnames/-/classnames-2.2.11.tgz#2521cc86f69d15c5b90664e4829d84566052c1cf" + integrity sha512-2koNhpWm3DgWRp5tpkiJ8JGc1xTn2q0l+jUNUE7oMKXUf5NpI9AIdC4kbjGNFBdHtcxBD18LAksoudAVhFKCjw== "@types/eslint-scope@^3.7.0": version "3.7.0" @@ -405,10 +408,10 @@ "@types/estree" "*" "@types/json-schema" "*" -"@types/estree@*", "@types/estree@^0.0.45": - version "0.0.45" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.45.tgz#e9387572998e5ecdac221950dab3e8c3b16af884" - integrity sha512-jnqIUKDUqJbDIUxm0Uj7bnlMnRm1T/eZ9N+AVMqhPgzrba2GhGG5o/jCTwmdPK709nEZsGoMzXEDUjcXHa3W0g== +"@types/estree@*", "@types/estree@^0.0.46": + version "0.0.46" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.46.tgz#0fb6bfbbeabd7a30880504993369c4bf1deab1fe" + integrity sha512-laIjwTQaD+5DukBZaygQ79K1Z0jb1bPEMRrkXSLjtCcZm+abyp5YbrqpSLzD42FwWW6gK/aS4NYpJ804nG2brg== "@types/glob@^7.1.1": version "7.1.3" @@ -437,36 +440,43 @@ integrity sha512-giAlZwstKbmvMk1OO7WXSj4OZ0keXAcl2TQq4LWHiiPH2ByaH7WeUzng+Qej8UPxxv+8lRTuouo0iaNDBuzIBA== "@types/http-proxy@^1.17.4": - version "1.17.4" - resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.4.tgz#e7c92e3dbe3e13aa799440ff42e6d3a17a9d045b" - integrity sha512-IrSHl2u6AWXduUaDLqYpt45tLVCtYv7o4Z0s1KghBCDgIIS9oW5K1H8mZG/A2CfeLdEa7rTd1ACOiHBc1EMT2Q== + version "1.17.5" + resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.5.tgz#c203c5e6e9dc6820d27a40eb1e511c70a220423d" + integrity sha512-GNkDE7bTv6Sf8JbV2GksknKOsk7OznNYHSdrtvPJXO0qJ9odZig6IZKUi5RFGi6d1bf6dgIAe4uXi3DBc7069Q== dependencies: "@types/node" "*" "@types/json-schema@*", "@types/json-schema@^7.0.3", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.6": - version "7.0.6" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.6.tgz#f4c7ec43e81b319a9815115031709f26987891f0" - integrity sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw== + version "7.0.7" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.7.tgz#98a993516c859eb0d5c4c8f098317a9ea68db9ad" + integrity sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA== "@types/json5@^0.0.29": version "0.0.29" resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= +"@types/mdast@^3.0.0": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.3.tgz#2d7d671b1cd1ea3deb306ea75036c2a0407d2deb" + integrity sha512-SXPBMnFVQg1s00dlMCc/jCdvPqdE4mXaMMCeRlxLDmTAEoegHT53xKtkDnzDTOcmMHUfcjyf36/YYZ6SxRdnsw== + dependencies: + "@types/unist" "*" + "@types/minimatch@*", "@types/minimatch@^3.0.3": version "3.0.3" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== "@types/minimist@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.0.tgz#69a23a3ad29caf0097f06eda59b361ee2f0639f6" - integrity sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY= + version "1.2.1" + resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.1.tgz#283f669ff76d7b8260df8ab7a4262cc83d988256" + integrity sha512-fZQQafSREFyuZcdWFAExYjBiCL7AUCdgsk80iO0q4yihYYdcIiH28CcuPTGFgLOCC8RlW49GSQxdHwZP+I7CNg== "@types/node@*": - version "14.14.0" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.0.tgz#f1091b6ad5de18e8e91bdbd43ec63f13de372538" - integrity sha512-BfbIHP9IapdupGhq/hc+jT5dyiBVZ2DdeC5WwJWQWDb0GijQlzUFAeIQn/2GtvZcd2HVUU7An8felIICFTC2qg== + version "14.14.22" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.22.tgz#0d29f382472c4ccf3bd96ff0ce47daf5b7b84b18" + integrity sha512-g+f/qj/cNcqKkc3tFqlXOYjrmZA+jNBiDzbP3kH+B+otKFqAdPgVTGP1IeKRdMml/aE69as5S4FqtxAbl+LaMw== "@types/normalize-package-data@^2.4.0": version "2.4.0" @@ -494,16 +504,16 @@ integrity sha512-/JHkVHtx/REVG0VVToGRGH2+23hsYLHdyG+GrvoUGlGAd0ErauXDyvHtRI/7H7mzLm+tBCKA7pfcpkQ1lf58iQ== "@types/react-dom@^16.9.8": - version "16.9.8" - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.8.tgz#fe4c1e11dfc67155733dfa6aa65108b4971cb423" - integrity sha512-ykkPQ+5nFknnlU6lDd947WbQ6TE3NNzbQAkInC2EKY1qeYdTKp7onFusmYZb+ityzx2YviqT6BXSu+LyWWJwcA== + version "16.9.10" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.10.tgz#4485b0bec3d41f856181b717f45fd7831101156f" + integrity sha512-ItatOrnXDMAYpv6G8UCk2VhbYVTjZT9aorLtA/OzDN9XJ2GKcfam68jutoAcILdRjsRUO8qb7AmyObF77Q8QFw== dependencies: - "@types/react" "*" + "@types/react" "^16" "@types/react-redux@^7.1.9": - version "7.1.9" - resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.9.tgz#280c13565c9f13ceb727ec21e767abe0e9b4aec3" - integrity sha512-mpC0jqxhP4mhmOl3P4ipRsgTgbNofMRXJb08Ms6gekViLj61v1hOZEKWDCyWsdONr6EjEA6ZHXC446wdywDe0w== + version "7.1.16" + resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.16.tgz#0fbd04c2500c12105494c83d4a3e45c084e3cb21" + integrity sha512-f/FKzIrZwZk7YEO9E1yoxIuDNRiDducxkFlkw/GNMGEnK9n4K8wJzlJBghpSuOVDgEUHoDkDF7Gi9lHNQR4siw== dependencies: "@types/hoist-non-react-statics" "^3.3.0" "@types/react" "*" @@ -511,26 +521,34 @@ redux "^4.0.0" "@types/react-router-dom@^5.1.6": - version "5.1.6" - resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.1.6.tgz#07b14e7ab1893a837c8565634960dc398564b1fb" - integrity sha512-gjrxYqxz37zWEdMVvQtWPFMFj1dRDb4TGOcgyOfSXTrEXdF92L00WE3C471O3TV/RF1oskcStkXsOU0Ete4s/g== + version "5.1.7" + resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.1.7.tgz#a126d9ea76079ffbbdb0d9225073eb5797ab7271" + integrity sha512-D5mHD6TbdV/DNHYsnwBTv+y73ei+mMjrkGrla86HthE4/PVvL1J94Bu3qABU+COXzpL23T1EZapVVpwHuBXiUg== dependencies: "@types/history" "*" "@types/react" "*" "@types/react-router" "*" "@types/react-router@*": - version "5.1.8" - resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.8.tgz#4614e5ba7559657438e17766bb95ef6ed6acc3fa" - integrity sha512-HzOyJb+wFmyEhyfp4D4NYrumi+LQgQL/68HvJO+q6XtuHSDvw6Aqov7sCAhjbNq3bUPgPqbdvjXC5HeB2oEAPg== + version "5.1.11" + resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.11.tgz#b01ce4cb21bf7d6b32edc862fc1e2c0088044b5b" + integrity sha512-ofHbZMlp0Y2baOHgsWBQ4K3AttxY61bDMkwTiBOkPg7U6C/3UwwB5WaIx28JmSVi/eX3uFEMRo61BV22fDQIvg== dependencies: "@types/history" "*" "@types/react" "*" -"@types/react@*", "@types/react@^16.9.53": - version "16.9.53" - resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.53.tgz#40cd4f8b8d6b9528aedd1fff8fcffe7a112a3d23" - integrity sha512-4nW60Sd4L7+WMXH1D6jCdVftuW7j4Za6zdp6tJ33Rqv0nk1ZAmQKML9ZLD4H0dehA3FZxXR/GM8gXplf82oNGw== +"@types/react@*": + version "17.0.0" + resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.0.tgz#5af3eb7fad2807092f0046a1302b7823e27919b8" + integrity sha512-aj/L7RIMsRlWML3YB6KZiXB3fV2t41+5RBGYF8z+tAKU43Px8C3cYUZsDvf1/+Bm4FK21QWBrDutu8ZJ/70qOw== + dependencies: + "@types/prop-types" "*" + csstype "^3.0.2" + +"@types/react@^16", "@types/react@^16.9.53": + version "16.14.2" + resolved "https://registry.yarnpkg.com/@types/react/-/react-16.14.2.tgz#85dcc0947d0645349923c04ccef6018a1ab7538c" + integrity sha512-BzzcAlyDxXl2nANlabtT4thtvbbnhee8hMmH/CcJrISDBVcJS1iOsP1f0OAgSdGE0MsY9tqcrb9YoZcOFv9dbQ== dependencies: "@types/prop-types" "*" csstype "^3.0.2" @@ -546,30 +564,30 @@ integrity sha512-W+bw9ds02rAQaMvaLYxAbJ6cvguW/iJXNT6lTssS1ps6QdrMKttqEAMEG/b5CR8TZl3/L7/lH0ZV5nNR1LXikA== "@types/uglify-js@*": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@types/uglify-js/-/uglify-js-3.11.0.tgz#2868d405cc45cd9dc3069179052103032c33afbc" - integrity sha512-I0Yd8TUELTbgRHq2K65j8rnDPAzAP+DiaF/syLem7yXwYLsHZhPd+AM2iXsWmf9P2F2NlFCgl5erZPQx9IbM9Q== + version "3.11.1" + resolved "https://registry.yarnpkg.com/@types/uglify-js/-/uglify-js-3.11.1.tgz#97ff30e61a0aa6876c270b5f538737e2d6ab8ceb" + integrity sha512-7npvPKV+jINLu1SpSYVWG8KvyJBhBa8tmzMMdDoVc2pWUYHN8KIXlPJhjJ4LT97c4dXJA2SHL/q6ADbDriZN+Q== dependencies: source-map "^0.6.1" -"@types/unist@^2.0.0", "@types/unist@^2.0.2": +"@types/unist@*", "@types/unist@^2.0.0", "@types/unist@^2.0.2": version "2.0.3" resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.3.tgz#9c088679876f374eb5983f150d4787aa6fb32d7e" integrity sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ== "@types/webpack-sources@*": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@types/webpack-sources/-/webpack-sources-2.0.0.tgz#08216ab9be2be2e1499beaebc4d469cec81e82a7" - integrity sha512-a5kPx98CNFRKQ+wqawroFunvFqv7GHm/3KOI52NY9xWADgc8smu4R6prt4EU/M4QfVjvgBkMqU4fBhw3QfMVkg== + version "2.1.0" + resolved "https://registry.yarnpkg.com/@types/webpack-sources/-/webpack-sources-2.1.0.tgz#8882b0bd62d1e0ce62f183d0d01b72e6e82e8c10" + integrity sha512-LXn/oYIpBeucgP1EIJbKQ2/4ZmpvRl+dlrFdX7+94SKRUV3Evy3FsfMZY318vGhkWUS5MPhtOM3w1/hCOAOXcg== dependencies: "@types/node" "*" "@types/source-list-map" "*" source-map "^0.7.3" "@types/webpack@^4.41.8": - version "4.41.23" - resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-4.41.23.tgz#1925f42a7325be4ae0fce38329f1cc27768fcda7" - integrity sha512-ojA4CupZg8RCzVJLugWlvqrHpT59GWhqFxbinlsnvk10MjQCWB+ot7XDACctbWhnhtdhYK7+HOH1JxkVLiZhMg== + version "4.41.26" + resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-4.41.26.tgz#27a30d7d531e16489f9c7607c747be6bc1a459ef" + integrity sha512-7ZyTfxjCRwexh+EJFwRUM+CDB2XvgHl4vfuqf1ZKrgGvcS5BrNvPQqJh3tsZ0P6h6Aa1qClVHaJZszLPzpqHeA== dependencies: "@types/anymatch" "*" "@types/node" "*" @@ -579,27 +597,28 @@ source-map "^0.6.0" "@typescript-eslint/eslint-plugin@^4.5.0": - version "4.5.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.5.0.tgz#4ff9c1d8535ae832e239f0ef6d7210592d9b0b07" - integrity sha512-mjb/gwNcmDKNt+6mb7Aj/TjKzIJjOPcoCJpjBQC9ZnTRnBt1p4q5dJSSmIqAtsZ/Pff5N+hJlbiPc5bl6QN4OQ== + version "4.14.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.14.1.tgz#22dd301ce228aaab3416b14ead10b1db3e7d3180" + integrity sha512-5JriGbYhtqMS1kRcZTQxndz1lKMwwEXKbwZbkUZNnp6MJX0+OVXnG0kOlBZP4LUAxEyzu3cs+EXd/97MJXsGfw== dependencies: - "@typescript-eslint/experimental-utils" "4.5.0" - "@typescript-eslint/scope-manager" "4.5.0" + "@typescript-eslint/experimental-utils" "4.14.1" + "@typescript-eslint/scope-manager" "4.14.1" debug "^4.1.1" functional-red-black-tree "^1.0.1" + lodash "^4.17.15" regexpp "^3.0.0" semver "^7.3.2" tsutils "^3.17.1" -"@typescript-eslint/experimental-utils@4.5.0": - version "4.5.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.5.0.tgz#547fe1158609143ce60645383aa1d6f83ada28df" - integrity sha512-bW9IpSAKYvkqDGRZzayBXIgPsj2xmmVHLJ+flGSoN0fF98pGoKFhbunIol0VF2Crka7z984EEhFi623Rl7e6gg== +"@typescript-eslint/experimental-utils@4.14.1": + version "4.14.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.14.1.tgz#a5c945cb24dabb96747180e1cfc8487f8066f471" + integrity sha512-2CuHWOJwvpw0LofbyG5gvYjEyoJeSvVH2PnfUQSn0KQr4v8Dql2pr43ohmx4fdPQ/eVoTSFjTi/bsGEXl/zUUQ== dependencies: "@types/json-schema" "^7.0.3" - "@typescript-eslint/scope-manager" "4.5.0" - "@typescript-eslint/types" "4.5.0" - "@typescript-eslint/typescript-estree" "4.5.0" + "@typescript-eslint/scope-manager" "4.14.1" + "@typescript-eslint/types" "4.14.1" + "@typescript-eslint/typescript-estree" "4.14.1" eslint-scope "^5.0.0" eslint-utils "^2.0.0" @@ -614,15 +633,23 @@ debug "^4.1.1" "@typescript-eslint/parser@^4.5.0": - version "4.5.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.5.0.tgz#b2d659f25eec0041c7bc5660b91db1eefe8d7122" - integrity sha512-xb+gmyhQcnDWe+5+xxaQk5iCw6KqXd8VQxGiTeELTMoYeRjpocZYYRP1gFVM2C8Yl0SpUvLa1lhprwqZ00w3Iw== + version "4.14.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.14.1.tgz#3bd6c24710cd557d8446625284bcc9c6d52817c6" + integrity sha512-mL3+gU18g9JPsHZuKMZ8Z0Ss9YP1S5xYZ7n68Z98GnPq02pYNQuRXL85b9GYhl6jpdvUc45Km7hAl71vybjUmw== dependencies: - "@typescript-eslint/scope-manager" "4.5.0" - "@typescript-eslint/types" "4.5.0" - "@typescript-eslint/typescript-estree" "4.5.0" + "@typescript-eslint/scope-manager" "4.14.1" + "@typescript-eslint/types" "4.14.1" + "@typescript-eslint/typescript-estree" "4.14.1" debug "^4.1.1" +"@typescript-eslint/scope-manager@4.14.1": + version "4.14.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.14.1.tgz#8444534254c6f370e9aa974f035ced7fe713ce02" + integrity sha512-F4bjJcSqXqHnC9JGUlnqSa3fC2YH5zTtmACS1Hk+WX/nFB0guuynVK5ev35D4XZbdKjulXBAQMyRr216kmxghw== + dependencies: + "@typescript-eslint/types" "4.14.1" + "@typescript-eslint/visitor-keys" "4.14.1" + "@typescript-eslint/scope-manager@4.4.1": version "4.4.1" resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.4.1.tgz#d19447e60db2ce9c425898d62fa03b2cce8ea3f9" @@ -631,23 +658,29 @@ "@typescript-eslint/types" "4.4.1" "@typescript-eslint/visitor-keys" "4.4.1" -"@typescript-eslint/scope-manager@4.5.0": - version "4.5.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.5.0.tgz#8dfd53c3256d4357e7d66c2fc8956835f4d239be" - integrity sha512-C0cEO0cTMPJ/w4RA/KVe4LFFkkSh9VHoFzKmyaaDWAnPYIEzVCtJ+Un8GZoJhcvq+mPFXEsXa01lcZDHDG6Www== - dependencies: - "@typescript-eslint/types" "4.5.0" - "@typescript-eslint/visitor-keys" "4.5.0" +"@typescript-eslint/types@4.14.1": + version "4.14.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.14.1.tgz#b3d2eb91dafd0fd8b3fce7c61512ac66bd0364aa" + integrity sha512-SkhzHdI/AllAgQSxXM89XwS1Tkic7csPdndUuTKabEwRcEfR8uQ/iPA3Dgio1rqsV3jtqZhY0QQni8rLswJM2w== "@typescript-eslint/types@4.4.1": version "4.4.1" resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.4.1.tgz#c507b35cf523bc7ba00aae5f75ee9b810cdabbc1" integrity sha512-KNDfH2bCyax5db+KKIZT4rfA8rEk5N0EJ8P0T5AJjo5xrV26UAzaiqoJCxeaibqc0c/IvZxp7v2g3difn2Pn3w== -"@typescript-eslint/types@4.5.0": - version "4.5.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.5.0.tgz#98256e07bad1c8d15d0c9627ebec82fd971bb3c3" - integrity sha512-n2uQoXnyWNk0Les9MtF0gCK3JiWd987JQi97dMSxBOzVoLZXCNtxFckVqt1h8xuI1ix01t+iMY4h4rFMj/303g== +"@typescript-eslint/typescript-estree@4.14.1": + version "4.14.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.14.1.tgz#20d3b8c8e3cdc8f764bdd5e5b0606dd83da6075b" + integrity sha512-M8+7MbzKC1PvJIA8kR2sSBnex8bsR5auatLCnVlNTJczmJgqRn8M+sAlQfkEq7M4IY3WmaNJ+LJjPVRrREVSHQ== + dependencies: + "@typescript-eslint/types" "4.14.1" + "@typescript-eslint/visitor-keys" "4.14.1" + debug "^4.1.1" + globby "^11.0.1" + is-glob "^4.0.1" + lodash "^4.17.15" + semver "^7.3.2" + tsutils "^3.17.1" "@typescript-eslint/typescript-estree@4.4.1": version "4.4.1" @@ -663,19 +696,13 @@ semver "^7.3.2" tsutils "^3.17.1" -"@typescript-eslint/typescript-estree@4.5.0": - version "4.5.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.5.0.tgz#d50cf91ae3a89878401111031eb6fb6d03554f64" - integrity sha512-gN1mffq3zwRAjlYWzb5DanarOPdajQwx5MEWkWCk0XvqC8JpafDTeioDoow2L4CA/RkYZu7xEsGZRhqrTsAG8w== +"@typescript-eslint/visitor-keys@4.14.1": + version "4.14.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.14.1.tgz#e93c2ff27f47ee477a929b970ca89d60a117da91" + integrity sha512-TAblbDXOI7bd0C/9PE1G+AFo7R5uc+ty1ArDoxmrC1ah61Hn6shURKy7gLdRb1qKJmjHkqu5Oq+e4Kt0jwf1IA== dependencies: - "@typescript-eslint/types" "4.5.0" - "@typescript-eslint/visitor-keys" "4.5.0" - debug "^4.1.1" - globby "^11.0.1" - is-glob "^4.0.1" - lodash "^4.17.15" - semver "^7.3.2" - tsutils "^3.17.1" + "@typescript-eslint/types" "4.14.1" + eslint-visitor-keys "^2.0.0" "@typescript-eslint/visitor-keys@4.4.1": version "4.4.1" @@ -685,170 +712,143 @@ "@typescript-eslint/types" "4.4.1" eslint-visitor-keys "^2.0.0" -"@typescript-eslint/visitor-keys@4.5.0": - version "4.5.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.5.0.tgz#b59f26213ac597efe87f6b13cf2aabee70542af0" - integrity sha512-UHq4FSa55NDZqscRU//O5ROFhHa9Hqn9KWTEvJGTArtTQp5GKv9Zqf6d/Q3YXXcFv4woyBml7fJQlQ+OuqRcHA== +"@webassemblyjs/ast@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.0.tgz#a5aa679efdc9e51707a4207139da57920555961f" + integrity sha512-kX2W49LWsbthrmIRMbQZuQDhGtjyqXfEmmHyEi4XWnSZtPmxY0+3anPIzsnRb45VH/J55zlOfWvZuY47aJZTJg== dependencies: - "@typescript-eslint/types" "4.5.0" - eslint-visitor-keys "^2.0.0" + "@webassemblyjs/helper-numbers" "1.11.0" + "@webassemblyjs/helper-wasm-bytecode" "1.11.0" -"@webassemblyjs/ast@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.9.0.tgz#bd850604b4042459a5a41cd7d338cbed695ed964" - integrity sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA== +"@webassemblyjs/floating-point-hex-parser@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.0.tgz#34d62052f453cd43101d72eab4966a022587947c" + integrity sha512-Q/aVYs/VnPDVYvsCBL/gSgwmfjeCb4LW8+TMrO3cSzJImgv8lxxEPM2JA5jMrivE7LSz3V+PFqtMbls3m1exDA== + +"@webassemblyjs/helper-api-error@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.0.tgz#aaea8fb3b923f4aaa9b512ff541b013ffb68d2d4" + integrity sha512-baT/va95eXiXb2QflSx95QGT5ClzWpGaa8L7JnJbgzoYeaA27FCvuBXU758l+KXWRndEmUXjP0Q5fibhavIn8w== + +"@webassemblyjs/helper-buffer@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.0.tgz#d026c25d175e388a7dbda9694e91e743cbe9b642" + integrity sha512-u9HPBEl4DS+vA8qLQdEQ6N/eJQ7gT7aNvMIo8AAWvAl/xMrcOSiI2M0MAnMCy3jIFke7bEee/JwdX1nUpCtdyA== + +"@webassemblyjs/helper-numbers@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.0.tgz#7ab04172d54e312cc6ea4286d7d9fa27c88cd4f9" + integrity sha512-DhRQKelIj01s5IgdsOJMKLppI+4zpmcMQ3XboFPLwCpSNH6Hqo1ritgHgD0nqHeSYqofA6aBN/NmXuGjM1jEfQ== dependencies: - "@webassemblyjs/helper-module-context" "1.9.0" - "@webassemblyjs/helper-wasm-bytecode" "1.9.0" - "@webassemblyjs/wast-parser" "1.9.0" + "@webassemblyjs/floating-point-hex-parser" "1.11.0" + "@webassemblyjs/helper-api-error" "1.11.0" + "@xtuc/long" "4.2.2" -"@webassemblyjs/floating-point-hex-parser@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz#3c3d3b271bddfc84deb00f71344438311d52ffb4" - integrity sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA== +"@webassemblyjs/helper-wasm-bytecode@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.0.tgz#85fdcda4129902fe86f81abf7e7236953ec5a4e1" + integrity sha512-MbmhvxXExm542tWREgSFnOVo07fDpsBJg3sIl6fSp9xuu75eGz5lz31q7wTLffwL3Za7XNRCMZy210+tnsUSEA== -"@webassemblyjs/helper-api-error@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz#203f676e333b96c9da2eeab3ccef33c45928b6a2" - integrity sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw== - -"@webassemblyjs/helper-buffer@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz#a1442d269c5feb23fcbc9ef759dac3547f29de00" - integrity sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA== - -"@webassemblyjs/helper-code-frame@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.0.tgz#647f8892cd2043a82ac0c8c5e75c36f1d9159f27" - integrity sha512-ERCYdJBkD9Vu4vtjUYe8LZruWuNIToYq/ME22igL+2vj2dQ2OOujIZr3MEFvfEaqKoVqpsFKAGsRdBSBjrIvZA== +"@webassemblyjs/helper-wasm-section@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.0.tgz#9ce2cc89300262509c801b4af113d1ca25c1a75b" + integrity sha512-3Eb88hcbfY/FCukrg6i3EH8H2UsD7x8Vy47iVJrP967A9JGqgBVL9aH71SETPx1JrGsOUVLo0c7vMCN22ytJew== dependencies: - "@webassemblyjs/wast-printer" "1.9.0" + "@webassemblyjs/ast" "1.11.0" + "@webassemblyjs/helper-buffer" "1.11.0" + "@webassemblyjs/helper-wasm-bytecode" "1.11.0" + "@webassemblyjs/wasm-gen" "1.11.0" -"@webassemblyjs/helper-fsm@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.0.tgz#c05256b71244214671f4b08ec108ad63b70eddb8" - integrity sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw== - -"@webassemblyjs/helper-module-context@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.0.tgz#25d8884b76839871a08a6c6f806c3979ef712f07" - integrity sha512-MJCW8iGC08tMk2enck1aPW+BE5Cw8/7ph/VGZxwyvGbJwjktKkDK7vy7gAmMDx88D7mhDTCNKAW5tED+gZ0W8g== - dependencies: - "@webassemblyjs/ast" "1.9.0" - -"@webassemblyjs/helper-wasm-bytecode@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz#4fed8beac9b8c14f8c58b70d124d549dd1fe5790" - integrity sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw== - -"@webassemblyjs/helper-wasm-section@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz#5a4138d5a6292ba18b04c5ae49717e4167965346" - integrity sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw== - dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/helper-buffer" "1.9.0" - "@webassemblyjs/helper-wasm-bytecode" "1.9.0" - "@webassemblyjs/wasm-gen" "1.9.0" - -"@webassemblyjs/ieee754@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz#15c7a0fbaae83fb26143bbacf6d6df1702ad39e4" - integrity sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg== +"@webassemblyjs/ieee754@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.0.tgz#46975d583f9828f5d094ac210e219441c4e6f5cf" + integrity sha512-KXzOqpcYQwAfeQ6WbF6HXo+0udBNmw0iXDmEK5sFlmQdmND+tr773Ti8/5T/M6Tl/413ArSJErATd8In3B+WBA== dependencies: "@xtuc/ieee754" "^1.2.0" -"@webassemblyjs/leb128@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.9.0.tgz#f19ca0b76a6dc55623a09cffa769e838fa1e1c95" - integrity sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw== +"@webassemblyjs/leb128@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.0.tgz#f7353de1df38aa201cba9fb88b43f41f75ff403b" + integrity sha512-aqbsHa1mSQAbeeNcl38un6qVY++hh8OpCOzxhixSYgbRfNWcxJNJQwe2rezK9XEcssJbbWIkblaJRwGMS9zp+g== dependencies: "@xtuc/long" "4.2.2" -"@webassemblyjs/utf8@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.9.0.tgz#04d33b636f78e6a6813227e82402f7637b6229ab" - integrity sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w== +"@webassemblyjs/utf8@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.0.tgz#86e48f959cf49e0e5091f069a709b862f5a2cadf" + integrity sha512-A/lclGxH6SpSLSyFowMzO/+aDEPU4hvEiooCMXQPcQFPPJaYcPQNKGOCLUySJsYJ4trbpr+Fs08n4jelkVTGVw== -"@webassemblyjs/wasm-edit@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz#3fe6d79d3f0f922183aa86002c42dd256cfee9cf" - integrity sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw== +"@webassemblyjs/wasm-edit@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.0.tgz#ee4a5c9f677046a210542ae63897094c2027cb78" + integrity sha512-JHQ0damXy0G6J9ucyKVXO2j08JVJ2ntkdJlq1UTiUrIgfGMmA7Ik5VdC/L8hBK46kVJgujkBIoMtT8yVr+yVOQ== dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/helper-buffer" "1.9.0" - "@webassemblyjs/helper-wasm-bytecode" "1.9.0" - "@webassemblyjs/helper-wasm-section" "1.9.0" - "@webassemblyjs/wasm-gen" "1.9.0" - "@webassemblyjs/wasm-opt" "1.9.0" - "@webassemblyjs/wasm-parser" "1.9.0" - "@webassemblyjs/wast-printer" "1.9.0" + "@webassemblyjs/ast" "1.11.0" + "@webassemblyjs/helper-buffer" "1.11.0" + "@webassemblyjs/helper-wasm-bytecode" "1.11.0" + "@webassemblyjs/helper-wasm-section" "1.11.0" + "@webassemblyjs/wasm-gen" "1.11.0" + "@webassemblyjs/wasm-opt" "1.11.0" + "@webassemblyjs/wasm-parser" "1.11.0" + "@webassemblyjs/wast-printer" "1.11.0" -"@webassemblyjs/wasm-gen@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz#50bc70ec68ded8e2763b01a1418bf43491a7a49c" - integrity sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA== +"@webassemblyjs/wasm-gen@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.0.tgz#3cdb35e70082d42a35166988dda64f24ceb97abe" + integrity sha512-BEUv1aj0WptCZ9kIS30th5ILASUnAPEvE3tVMTrItnZRT9tXCLW2LEXT8ezLw59rqPP9klh9LPmpU+WmRQmCPQ== dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/helper-wasm-bytecode" "1.9.0" - "@webassemblyjs/ieee754" "1.9.0" - "@webassemblyjs/leb128" "1.9.0" - "@webassemblyjs/utf8" "1.9.0" + "@webassemblyjs/ast" "1.11.0" + "@webassemblyjs/helper-wasm-bytecode" "1.11.0" + "@webassemblyjs/ieee754" "1.11.0" + "@webassemblyjs/leb128" "1.11.0" + "@webassemblyjs/utf8" "1.11.0" -"@webassemblyjs/wasm-opt@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz#2211181e5b31326443cc8112eb9f0b9028721a61" - integrity sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A== +"@webassemblyjs/wasm-opt@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.0.tgz#1638ae188137f4bb031f568a413cd24d32f92978" + integrity sha512-tHUSP5F4ywyh3hZ0+fDQuWxKx3mJiPeFufg+9gwTpYp324mPCQgnuVKwzLTZVqj0duRDovnPaZqDwoyhIO8kYg== dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/helper-buffer" "1.9.0" - "@webassemblyjs/wasm-gen" "1.9.0" - "@webassemblyjs/wasm-parser" "1.9.0" + "@webassemblyjs/ast" "1.11.0" + "@webassemblyjs/helper-buffer" "1.11.0" + "@webassemblyjs/wasm-gen" "1.11.0" + "@webassemblyjs/wasm-parser" "1.11.0" -"@webassemblyjs/wasm-parser@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz#9d48e44826df4a6598294aa6c87469d642fff65e" - integrity sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA== +"@webassemblyjs/wasm-parser@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.0.tgz#3e680b8830d5b13d1ec86cc42f38f3d4a7700754" + integrity sha512-6L285Sgu9gphrcpDXINvm0M9BskznnzJTE7gYkjDbxET28shDqp27wpruyx3C2S/dvEwiigBwLA1cz7lNUi0kw== dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/helper-api-error" "1.9.0" - "@webassemblyjs/helper-wasm-bytecode" "1.9.0" - "@webassemblyjs/ieee754" "1.9.0" - "@webassemblyjs/leb128" "1.9.0" - "@webassemblyjs/utf8" "1.9.0" + "@webassemblyjs/ast" "1.11.0" + "@webassemblyjs/helper-api-error" "1.11.0" + "@webassemblyjs/helper-wasm-bytecode" "1.11.0" + "@webassemblyjs/ieee754" "1.11.0" + "@webassemblyjs/leb128" "1.11.0" + "@webassemblyjs/utf8" "1.11.0" -"@webassemblyjs/wast-parser@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-parser/-/wast-parser-1.9.0.tgz#3031115d79ac5bd261556cecc3fa90a3ef451914" - integrity sha512-qsqSAP3QQ3LyZjNC/0jBJ/ToSxfYJ8kYyuiGvtn/8MK89VrNEfwj7BPQzJVHi0jGTRK2dGdJ5PRqhtjzoww+bw== +"@webassemblyjs/wast-printer@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.11.0.tgz#680d1f6a5365d6d401974a8e949e05474e1fab7e" + integrity sha512-Fg5OX46pRdTgB7rKIUojkh9vXaVN6sGYCnEiJN1GYkb0RPwShZXp6KTDqmoMdQPKhcroOXh3fEzmkWmCYaKYhQ== dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/floating-point-hex-parser" "1.9.0" - "@webassemblyjs/helper-api-error" "1.9.0" - "@webassemblyjs/helper-code-frame" "1.9.0" - "@webassemblyjs/helper-fsm" "1.9.0" + "@webassemblyjs/ast" "1.11.0" "@xtuc/long" "4.2.2" -"@webassemblyjs/wast-printer@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz#4935d54c85fef637b00ce9f52377451d00d47899" - integrity sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA== - dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/wast-parser" "1.9.0" - "@xtuc/long" "4.2.2" +"@webpack-cli/configtest@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@webpack-cli/configtest/-/configtest-1.0.0.tgz#2aff5f1ebc6f793c13ba9b2a701d180eab17f5ee" + integrity sha512-Un0SdBoN1h4ACnIO7EiCjWuyhNI0Jl96JC+63q6xi4HDUYRZn8Auluea9D+v9NWKc5J4sICVEltdBaVjLX39xw== -"@webpack-cli/info@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@webpack-cli/info/-/info-1.1.0.tgz#c596d5bc48418b39df00c5ed7341bf0f102dbff1" - integrity sha512-uNWSdaYHc+f3LdIZNwhdhkjjLDDl3jP2+XBqAq9H8DjrJUvlOKdP8TNruy1yEaDfgpAIgbSAN7pye4FEHg9tYQ== +"@webpack-cli/info@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@webpack-cli/info/-/info-1.2.1.tgz#af98311f983d0b9fce7284cfcf1acaf1e9f4879c" + integrity sha512-fLnDML5HZ5AEKzHul8xLAksoKN2cibu6MgonkUj8R9V7bbeVRkd1XbGEGWrAUNYHbX1jcqCsDEpBviE5StPMzQ== dependencies: envinfo "^7.7.3" -"@webpack-cli/serve@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-1.1.0.tgz#13ad38f89b6e53d1133bac0006a128217a6ebf92" - integrity sha512-7RfnMXCpJ/NThrhq4gYQYILB18xWyoQcBey81oIyVbmgbc6m5ZHHyFK+DyH7pLHJf0p14MxL4mTsoPAgBSTpIg== +"@webpack-cli/serve@^1.2.2": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-1.2.2.tgz#1f8eee44f96524756268f5e3f43e9d943f864d41" + integrity sha512-03GkWxcgFfm8+WIwcsqJb9agrSDNDDoxaNnexPnCCexP5SCE4IgFd9lNpSy+K2nFqVMpgTFw6SwbmVAVTndVew== "@xtuc/ieee754@^1.2.0": version "1.2.0" @@ -868,7 +868,7 @@ accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7: mime-types "~2.1.24" negotiator "0.6.2" -acorn-jsx@^5.2.0: +acorn-jsx@^5.3.1: version "5.3.1" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.1.tgz#fc8661e11b7ac1539c47dbfea2e72b3af34d267b" integrity sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng== @@ -879,16 +879,9 @@ acorn@^7.4.0: integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== acorn@^8.0.4: - version "8.0.4" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.0.4.tgz#7a3ae4191466a6984eee0fe3407a4f3aa9db8354" - integrity sha512-XNP0PqF1XD19ZlLKvB7cMmnZswW4C/03pRHgirB30uSJTaS3A3V1/P4sS3HPvFmjoriPCJQs+JDSbm4bL1TxGQ== - -add-dom-event-listener@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/add-dom-event-listener/-/add-dom-event-listener-1.1.0.tgz#6a92db3a0dd0abc254e095c0f1dc14acbbaae310" - integrity sha512-WCxx1ixHT0GQU9hb0KI/mhgRQhnU+U3GvwY6ZvVjYq8rsihIGoaIOUbY0yMPBxLH5MDtr0kz3fisWGNcbWW7Jw== - dependencies: - object-assign "4.x" + version "8.0.5" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.0.5.tgz#a3bfb872a74a6a7f661bc81b9849d9cac12601b7" + integrity sha512-v+DieK/HJkJOpFBETDJioequtc3PfxsWMaxIdIwujtF7FEV/MAyDQLlm6/zPvr7Mix07mLh6ccVwIsloceodlg== aggregate-error@^3.0.0: version "3.1.0" @@ -908,7 +901,7 @@ ajv-keywords@^3.1.0, ajv-keywords@^3.5.2: resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== -ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.4, ajv@^6.12.5: +ajv@^6.1.0, ajv@^6.10.0, ajv@^6.12.4, ajv@^6.12.5: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== @@ -918,6 +911,16 @@ ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.4, ajv@^6.12.5: json-schema-traverse "^0.4.1" uri-js "^4.2.2" +ajv@^7.0.2: + version "7.0.3" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-7.0.3.tgz#13ae747eff125cafb230ac504b2406cf371eece2" + integrity sha512-R50QRlXSxqXcQP5SvKUrw8VZeypvo12i2IX0EeR5PiZ7bEKeHWgzgo264LDadUsCU42lTJVhFikTqJwNeH34gQ== + dependencies: + fast-deep-equal "^3.1.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + uri-js "^4.2.2" + alphanum-sort@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" @@ -968,59 +971,55 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: color-convert "^2.0.1" antd-dayjs-webpack-plugin@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/antd-dayjs-webpack-plugin/-/antd-dayjs-webpack-plugin-1.0.1.tgz#cc31cc05c9bd69652a4bddab19eb49726bebe0d5" - integrity sha512-1UWGAnv856s5r/2bi6KJvWMeevDEHwFFdHaymZElW5kbH06A/xP7JcZ75UdYoxqny3scD22DWErbGg+vQ9RNdg== + version "1.0.6" + resolved "https://registry.yarnpkg.com/antd-dayjs-webpack-plugin/-/antd-dayjs-webpack-plugin-1.0.6.tgz#7d98bcb51422248b8cd4a32e352a0425a3bffa3a" + integrity sha512-UlK3BfA0iE2c5+Zz/Bd2iPAkT6cICtrKG4/swSik5MZweBHtgmu1aUQCHvICdiv39EAShdZy/edfP6mlkS/xXg== antd@^4.7.2: - version "4.7.2" - resolved "https://registry.yarnpkg.com/antd/-/antd-4.7.2.tgz#6be4c34de141786bbc5425c743d0a3b990042641" - integrity sha512-baMyvvNRB0rqhUxi4cSaH4AG9Cd2W7TjAJnOrVTow8y5E45g3JU31+dAVUHWvtht6LTiWh4BLiKfCdZrSYBeEA== + version "4.11.2" + resolved "https://registry.yarnpkg.com/antd/-/antd-4.11.2.tgz#28c20409e2d186d8915cdc6eacf1cfb2b82d9b5b" + integrity sha512-cdjPRlmamETae6c2uvQHRXDN5/T7I/zPiByaeolbq/FRG14JYv9hyUaydXI7n4s6rynPQ2Q6bFdCQ+/r9xZYbA== dependencies: - "@ant-design/colors" "^4.0.5" - "@ant-design/css-animation" "^1.7.2" - "@ant-design/icons" "^4.2.1" - "@ant-design/react-slick" "~0.27.0" + "@ant-design/colors" "^5.0.0" + "@ant-design/icons" "^4.4.0" + "@ant-design/react-slick" "~0.28.1" "@babel/runtime" "^7.11.2" array-tree-filter "^2.1.0" classnames "^2.2.6" copy-to-clipboard "^3.2.0" lodash "^4.17.20" moment "^2.25.3" - omit.js "^2.0.2" - raf "^3.4.1" - rc-animate "~3.1.0" rc-cascader "~1.4.0" rc-checkbox "~2.3.0" - rc-collapse "~2.0.0" - rc-dialog "~8.4.0" - rc-drawer "~4.1.0" + rc-collapse "~3.1.0" + rc-dialog "~8.5.1" + rc-drawer "~4.2.0" rc-dropdown "~3.2.0" - rc-field-form "~1.12.0" - rc-image "~3.2.1" + rc-field-form "~1.18.0" + rc-image "~5.1.1" rc-input-number "~6.1.0" rc-mentions "~1.5.0" - rc-menu "~8.8.2" - rc-motion "^2.2.0" + rc-menu "~8.10.0" + rc-motion "^2.4.0" rc-notification "~4.5.2" - rc-pagination "~3.1.0" - rc-picker "~2.3.0" + rc-pagination "~3.1.2" + rc-picker "~2.5.1" rc-progress "~3.1.0" - rc-rate "~2.8.2" - rc-resize-observer "^0.2.3" - rc-select "~11.4.0" - rc-slider "~9.5.2" + rc-rate "~2.9.0" + rc-resize-observer "^1.0.0" + rc-select "~12.1.0" + rc-slider "~9.7.1" rc-steps "~4.1.0" rc-switch "~3.2.0" - rc-table "~7.10.0" + rc-table "~7.12.0" rc-tabs "~11.7.0" rc-textarea "~0.3.0" rc-tooltip "~5.0.0" - rc-tree "~3.10.0" - rc-tree-select "~4.1.1" - rc-trigger "~5.0.3" - rc-upload "~3.3.1" - rc-util "^5.1.0" + rc-tree "~4.1.0" + rc-tree-select "~4.3.0" + rc-trigger "^5.2.1" + rc-upload "~3.3.4" + rc-util "^5.7.0" scroll-into-view-if-needed "^2.2.25" warning "^4.0.3" @@ -1059,11 +1058,6 @@ arr-union@^3.1.0: resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= -array-back@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/array-back/-/array-back-4.0.1.tgz#9b80312935a52062e1a233a9c7abeb5481b30e90" - integrity sha512-Z/JnaVEXv+A9xabHzN43FiiiWEE7gPCRXMrVmRm00tWbjZRul1iHm7ECzlyNq1p4a4ATXz+G9FJ3GqGOkOV3fg== - array-differ@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-3.0.0.tgz#3cbb3d0f316810eafcc47624734237d6aee4ae6b" @@ -1079,13 +1073,15 @@ array-flatten@^2.1.0: resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.2.tgz#24ef80a28c1a893617e2149b0c6d0d788293b099" integrity sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ== -array-includes@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.1.tgz#cdd67e6852bdf9c1215460786732255ed2459348" - integrity sha512-c2VXaCHl7zPsvpkFsw4nxvFie4fh1ur9bpcgsVkIjqn0H/Xwdg+7fv3n2r/isyS8EBj5b06M9kHyZuIr4El6WQ== +array-includes@^3.1.1, array-includes@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.2.tgz#a8db03e0b88c8c6aeddc49cb132f9bcab4ebf9c8" + integrity sha512-w2GspexNQpx+PutG3QpT437/BenZBj0M/MZGn5mzv/MofYqo0xmRHzn4lFsoDlWJ+THYsGJmFlW68WlDFx7VRw== dependencies: + call-bind "^1.0.0" define-properties "^1.1.3" - es-abstract "^1.17.0" + es-abstract "^1.18.0-next.1" + get-intrinsic "^1.0.1" is-string "^1.0.5" array-tree-filter@^2.1.0: @@ -1116,20 +1112,22 @@ array-unique@^0.3.2: integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= array.prototype.flat@^1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.3.tgz#0de82b426b0318dbfdb940089e38b043d37f6c7b" - integrity sha512-gBlRZV0VSmfPIeWfuuy56XZMvbVfbEUnOXUvt3F/eUUUSyzlgLxhEX4YAEpxNAogRGehPSnfXyPtYyKAhkzQhQ== + version "1.2.4" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.4.tgz#6ef638b43312bd401b4c6199fdec7e2dc9e9a123" + integrity sha512-4470Xi3GAPAjZqFcljX2xzckv1qeKPizoNkiS0+O4IoPR2ZNpcjE0pkhdihlDouK+x6QOast26B4Q/O9DJnwSg== dependencies: + call-bind "^1.0.0" define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" + es-abstract "^1.18.0-next.1" array.prototype.flatmap@^1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.2.3.tgz#1c13f84a178566042dd63de4414440db9222e443" - integrity sha512-OOEk+lkePcg+ODXIpvuU9PAryCikCJyo7GlDG1upleEpQRx6mzL9puEBkozQ5iAx20KV0l3DbyQwqciJtqe5Pg== + version "1.2.4" + resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.2.4.tgz#94cfd47cc1556ec0747d97f7c7738c58122004c9" + integrity sha512-r9Z0zYoxqHz60vvQbWEdXIEtCwHF0yxaWfno9qzXeNHvfyl3BZqygmGzb84dsubyaXLH4husF+NFgMSdpZhk2Q== dependencies: + call-bind "^1.0.0" define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" + es-abstract "^1.18.0-next.1" function-bind "^1.1.1" arrify@^1.0.1: @@ -1147,11 +1145,6 @@ assign-symbols@^1.0.0: resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= -astral-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" - integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== - astral-regex@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" @@ -1168,9 +1161,9 @@ async-limiter@~1.0.0: integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== async-validator@^3.0.3: - version "3.4.0" - resolved "https://registry.yarnpkg.com/async-validator/-/async-validator-3.4.0.tgz#871b3e594124bf4c4eb7bcd1a9e78b44f3b09cae" - integrity sha512-VrFk4eYiJAWKskEz115iiuCf9O0ftnMMPXrOFMqyzGH2KxO7YwncKyn/FgOOP+0MDHMfXL7gLExagCutaZGigA== + version "3.5.1" + resolved "https://registry.yarnpkg.com/async-validator/-/async-validator-3.5.1.tgz#cd62b9688b2465f48420e27adb47760ab1b5559f" + integrity sha512-DDmKA7sdSAJtTVeNZHrnr2yojfFaoeW8MfQN8CeuXg8DDQHTqKk9Fdv38dSvnesHoO8MUwMI2HphOeSyIF+wmQ== async@^2.6.2: version "2.6.3" @@ -1190,15 +1183,15 @@ atob@^2.1.2: integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== autoprefixer@^10.0.1: - version "10.0.1" - resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.0.1.tgz#e2d9000f84ebd98d77b7bc16f8adb2ff1f7bb946" - integrity sha512-aQo2BDIsoOdemXUAOBpFv4ZQa2DrOtEufarYhtFsK1088Ca0TUwu/aQWf0M3mrILXZ3mTIVn1lR3hPW8acacsw== + version "10.2.3" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.2.3.tgz#2834b55b75cfc10fa80c66000a66dc94b7136804" + integrity sha512-vlz+iv+EnLkVaTgX8wApfYzmK3LUfK8Z9XAnmflzxMy/+oFuNK8fVGQV79SOpBv4jxk2YQJimw4hXIKZ29570A== dependencies: - browserslist "^4.14.5" - caniuse-lite "^1.0.30001137" + browserslist "^4.16.1" + caniuse-lite "^1.0.30001178" colorette "^1.2.1" + fraction.js "^4.0.13" normalize-range "^0.1.2" - num2fraction "^1.2.2" postcss-value-parser "^4.1.0" autoprefixer@^9.6.1, autoprefixer@^9.8.6: @@ -1323,15 +1316,16 @@ braces@^3.0.1: dependencies: fill-range "^7.0.1" -browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.14.5, browserslist@^4.6.4: - version "4.14.5" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.14.5.tgz#1c751461a102ddc60e40993639b709be7f2c4015" - integrity sha512-Z+vsCZIvCBvqLoYkBFTwEYH3v5MCQbsAjp50ERycpOjnPmolg1Gjy4+KaWWpm8QOJt9GHkhdqAl14NpCX73CWA== +browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.14.5, browserslist@^4.16.1, browserslist@^4.6.4: + version "4.16.1" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.1.tgz#bf757a2da376b3447b800a16f0f1c96358138766" + integrity sha512-UXhDrwqsNcpTYJBTZsbGATDxZbiVDsx6UjpmRUmtnP10pr8wAYr5LgFoEFw9ixriQH2mv/NX2SfGzE/o8GndLA== dependencies: - caniuse-lite "^1.0.30001135" - electron-to-chromium "^1.3.571" - escalade "^3.1.0" - node-releases "^1.1.61" + caniuse-lite "^1.0.30001173" + colorette "^1.2.1" + electron-to-chromium "^1.3.634" + escalade "^3.1.1" + node-releases "^1.1.69" buffer-from@^1.0.0: version "1.1.1" @@ -1391,13 +1385,13 @@ cache-base@^1.0.1: union-value "^1.0.0" unset-value "^1.0.0" -call-bind@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.0.tgz#24127054bb3f9bdcb4b1fb82418186072f77b8ce" - integrity sha512-AEXsYIyyDY3MCzbwdhzG3Jx1R0J2wetQyUynn6dYHAO+bg8l1k7jwZtRv4ryryFs7EP+NDlikJlVe59jr0cM2w== +call-bind@^1.0.0, call-bind@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" + integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== dependencies: function-bind "^1.1.1" - get-intrinsic "^1.0.0" + get-intrinsic "^1.0.2" caller-callsite@^2.0.0: version "2.0.0" @@ -1424,12 +1418,12 @@ callsites@^3.0.0: integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== camel-case@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-4.1.1.tgz#1fc41c854f00e2f7d0139dfeba1542d6896fe547" - integrity sha512-7fa2WcG4fYFkclIvEmxBbTvmibwF2/agfEBc6q3lOpVu0A13ltLsA+Hr/8Hp6kp5f+G7hKi6t8lys6XxP+1K6Q== + version "4.1.2" + resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-4.1.2.tgz#9728072a954f805228225a6deea6b38461e1bd5a" + integrity sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw== dependencies: - pascal-case "^3.1.1" - tslib "^1.10.0" + pascal-case "^3.1.2" + tslib "^2.0.3" camelcase-css@^2.0.1: version "2.0.1" @@ -1450,10 +1444,10 @@ camelcase@^5.0.0, camelcase@^5.3.1: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== -camelcase@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.1.0.tgz#27dc176173725fb0adf8a48b647f4d7871944d78" - integrity sha512-WCMml9ivU60+8rEJgELlFp1gxFcEGxwYleE3bziHEDeqsqAWGHdimB7beBFGjLzVNgPGyDsfgXLQEYMpmIFnVQ== +camelcase@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809" + integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg== caniuse-api@^3.0.0: version "3.0.0" @@ -1465,17 +1459,12 @@ caniuse-api@^3.0.0: lodash.memoize "^4.1.2" lodash.uniq "^4.5.0" -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001135, caniuse-lite@^1.0.30001137: - version "1.0.30001150" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001150.tgz#6d0d829da654b0b233576de00335586bc2004df1" - integrity sha512-kiNKvihW0m36UhAFnl7bOAv0i1K1f6wpfVtTF5O5O82XzgtBnb05V0XeV3oZ968vfg2sRNChsHw8ASH2hDfoYQ== +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001173, caniuse-lite@^1.0.30001178: + version "1.0.30001180" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001180.tgz#67abcd6d1edf48fa5e7d1e84091d1d65ab76e33b" + integrity sha512-n8JVqXuZMVSPKiPiypjFtDTXc4jWIdjxull0f92WLo7e1MSi3uJ3NvveakSh/aCl1QKFAvIz3vIj0v+0K+FrXw== -ccount@^1.0.0: - version "1.0.5" - resolved "https://registry.yarnpkg.com/ccount/-/ccount-1.0.5.tgz#ac82a944905a65ce204eb03023157edf29425c17" - integrity sha512-MOli1W+nfbPLlKEhInaxhRdp7KVLFxLN5ykwzHgLsLI3H3gs5jjFAK4Eoj3OzzcxCtumDaI8onoVDeQyWaNTkw== - -chalk@^2.0.0, chalk@^2.3.0, chalk@^2.4.1, chalk@^2.4.2: +chalk@^2.0.0, chalk@^2.4.1, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -1492,11 +1481,6 @@ chalk@^4.0.0, chalk@^4.1.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -character-entities-html4@^1.0.0: - version "1.1.4" - resolved "https://registry.yarnpkg.com/character-entities-html4/-/character-entities-html4-1.1.4.tgz#0e64b0a3753ddbf1fdc044c5fd01d0199a02e125" - integrity sha512-HRcDxZuZqMx3/a+qrzxdBKBPUpxWEq9xw2OPZ3a/174ihfrQKVsFhqtthBInFy1zZ9GgZyFXOatNujm8M+El3g== - character-entities-legacy@^1.0.0: version "1.1.4" resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz#94bc1845dce70a5bb9d2ecc748725661293d8fc1" @@ -1543,11 +1527,6 @@ chrome-trace-event@^1.0.2: dependencies: tslib "^1.9.0" -ci-info@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" - integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== - class-utils@^0.3.5: version "0.3.6" resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" @@ -1615,14 +1594,9 @@ coa@^2.0.2: q "^1.1.2" code-block-writer@^10.1.0: - version "10.1.0" - resolved "https://registry.yarnpkg.com/code-block-writer/-/code-block-writer-10.1.0.tgz#54fc410ebef2af836d9c2314ac40af7d7b37eee9" - integrity sha512-RG9hpXtWFeUWhuUav1YuP/vGcyncW+t90yJLk9fNZs1De2OuHTHKAKThVCokt29PYq5RoJ0QSZaIZ+rvPO23hA== - -collapse-white-space@^1.0.2: - version "1.0.6" - resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-1.0.6.tgz#e63629c0016665792060dbbeb79c42239d2c5287" - integrity sha512-jEovNnrhMuqyCcjfEJA56v0Xq8SkIoPKDyaHahwo3POf4qcSXqMYuwNcOTzp74vTsR9Tn08z4MxWqAhcekogkQ== + version "10.1.1" + resolved "https://registry.yarnpkg.com/code-block-writer/-/code-block-writer-10.1.1.tgz#ad5684ed4bfb2b0783c8b131281ae84ee640a42f" + integrity sha512-67ueh2IRGst/51p0n6FvPrnRjAGHY5F8xdjkgrYE7DDzpJe6qA07RYQ9VcoUeo5ATOjSOiWpSL3SWBRRbempMw== collection-visit@^1.0.0: version "1.0.0" @@ -1677,16 +1651,6 @@ colorette@^1.2.1: resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.1.tgz#4d0b921325c14faf92633086a536db6e89564b1b" integrity sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw== -command-line-usage@^6.1.0: - version "6.1.1" - resolved "https://registry.yarnpkg.com/command-line-usage/-/command-line-usage-6.1.1.tgz#c908e28686108917758a49f45efb4f02f76bc03f" - integrity sha512-F59pEuAR9o1SF/bD0dQBDluhpT4jJQNWUHEuVBqpDmCUo6gPjCi+m9fCWnWZVR/oG6cMTUms4h+3NPl74wGXvA== - dependencies: - array-back "^4.0.1" - chalk "^2.4.2" - table-layout "^1.0.1" - typical "^5.2.0" - commander@^2.20.0: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" @@ -1698,20 +1662,15 @@ commander@^4.1.1: integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== commander@^6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.0.tgz#b990bfb8ac030aedc6d11bc04d1488ffef56db75" - integrity sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q== + version "6.2.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" + integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== commondir@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= -compare-versions@^3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.6.0.tgz#1a5689913685e5a87637b8d3ffca75514ec41d62" - integrity sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA== - component-emitter@^1.2.1: version "1.3.0" resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" @@ -1747,10 +1706,10 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -confusing-browser-globals@^1.0.9: - version "1.0.9" - resolved "https://registry.yarnpkg.com/confusing-browser-globals/-/confusing-browser-globals-1.0.9.tgz#72bc13b483c0276801681871d4898516f8f54fdd" - integrity sha512-KbS1Y0jMtyPgIxjO7ZzMAuUpAKMt1SzCL9fsrKsX6b0zJPTaT0SiSPmewwVZg9UAO83HVIlEhZF84LIjZ0lmAw== +confusing-browser-globals@^1.0.10, confusing-browser-globals@^1.0.9: + version "1.0.10" + resolved "https://registry.yarnpkg.com/confusing-browser-globals/-/confusing-browser-globals-1.0.10.tgz#30d1e7f3d1b882b25ec4933d1d1adac353d20a59" + integrity sha512-gNld/3lySHwuhaVluJUKLePYirM3QNCKzVxqAdhJII9/WXKVX5PURzMVJspS1jTslSqjeuG4KMVTSouit5YPHA== connect-history-api-fallback@^1.6.0: version "1.6.0" @@ -1791,6 +1750,13 @@ cookie@0.4.0: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== +copy-anything@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/copy-anything/-/copy-anything-2.0.1.tgz#2afbce6da684bdfcbec93752fa762819cb480d9a" + integrity sha512-lA57e7viQHOdPQcrytv5jFeudZZOXuyk47lZym279FiDQ8jeZomXiGuVf6ffMKkJ+3TIai3J1J3yi6M+/4U35g== + dependencies: + is-what "^3.7.1" + copy-descriptor@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" @@ -1804,9 +1770,9 @@ copy-to-clipboard@^3.2.0: toggle-selection "^1.0.6" copy-webpack-plugin@^6.2.1: - version "6.2.1" - resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-6.2.1.tgz#8015e4d5c5e637ab7b39c76daa9e03c7a4bf1ae5" - integrity sha512-VH2ZTMIBsx4p++Lmpg77adZ0KUyM5gFR/9cuTrbneNnJlcQXUFvsNariPqq2dq2kV3F2skHiDGPQCyKWy1+U0Q== + version "6.4.1" + resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-6.4.1.tgz#138cd9b436dbca0a6d071720d5414848992ec47e" + integrity sha512-MXyPCjdPVx5iiWyl40Va3JGh27bKzOTNY3NjUTrosD2q7dR/cLD0013uqJ3BpFbUjyONINjb6qI7nDIJujrMbA== dependencies: cacache "^15.0.5" fast-glob "^3.2.4" @@ -1846,6 +1812,11 @@ cosmiconfig@^7.0.0: path-type "^4.0.0" yaml "^1.10.0" +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + cross-spawn@^6.0.0: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" @@ -1857,7 +1828,7 @@ cross-spawn@^6.0.0: shebang-command "^1.2.0" which "^1.2.9" -cross-spawn@^7.0.0, cross-spawn@^7.0.2: +cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== @@ -1895,15 +1866,15 @@ css-has-pseudo@^0.10.0: postcss-selector-parser "^5.0.0-rc.4" css-loader@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-5.0.0.tgz#f0a48dfacc3ab9936a05ee16a09e7f313872e117" - integrity sha512-9g35eXRBgjvswyJWoqq/seWp+BOxvUl8IinVNTsUBFFxtwfEYvlmEn6ciyn0liXGbGh5HyJjPGCuobDSfqMIVg== + version "5.0.1" + resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-5.0.1.tgz#9e4de0d6636a6266a585bd0900b422c85539d25f" + integrity sha512-cXc2ti9V234cq7rJzFKhirb2L2iPy8ZjALeVJAozXYz9te3r4eqLSixNAbMDJSgJEQywqXzs8gonxaboeKqwiw== dependencies: - camelcase "^6.1.0" + camelcase "^6.2.0" cssesc "^3.0.0" icss-utils "^5.0.0" loader-utils "^2.0.0" - postcss "^8.1.1" + postcss "^8.1.4" postcss-modules-extract-imports "^3.0.0" postcss-modules-local-by-default "^4.0.0" postcss-modules-scope "^3.0.0" @@ -1924,16 +1895,6 @@ css-select-base-adapter@^0.1.1: resolved "https://registry.yarnpkg.com/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz#3b2ff4972cc362ab88561507a95408a1432135d7" integrity sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w== -css-select@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858" - integrity sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg= - dependencies: - boolbase "~1.0.0" - css-what "2.1" - domutils "1.5.1" - nth-check "~1.0.1" - css-select@^2.0.0, css-select@^2.0.2: version "2.1.0" resolved "https://registry.yarnpkg.com/css-select/-/css-select-2.1.0.tgz#6a34653356635934a81baca68d0255432105dbef" @@ -1952,18 +1913,18 @@ css-tree@1.0.0-alpha.37: mdn-data "2.0.4" source-map "^0.6.1" -css-tree@1.0.0-alpha.39: - version "1.0.0-alpha.39" - resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.39.tgz#2bff3ffe1bb3f776cf7eefd91ee5cba77a149eeb" - integrity sha512-7UvkEYgBAHRG9Nt980lYxjsTrCyHFN53ky3wVsDkiMdVqylqRt+Zc+jm5qw7/qyOvN2dHSYtX0e4MbCCExSvnA== +css-tree@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.2.tgz#9ae393b5dafd7dae8a622475caec78d3d8fbd7b5" + integrity sha512-wCoWush5Aeo48GLhfHPbmvZs59Z+M7k5+B1xDnXbdWNcEF423DoFdqSWE0PM5aNk5nI5cp1q7ms36zGApY/sKQ== dependencies: - mdn-data "2.0.6" + mdn-data "2.0.14" source-map "^0.6.1" -css-what@2.1: - version "2.1.3" - resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2" - integrity sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg== +css-unit-converter@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/css-unit-converter/-/css-unit-converter-1.1.2.tgz#4c77f5a1954e6dbff60695ecb214e3270436ab21" + integrity sha512-IiJwMC8rdZE0+xiEZHeru6YoONC4rfPMqGm2W85jMIbkFvv5nFTwJVFHam2eFrN6txmoUYFAFXiv8ICVeTO0MA== css-what@^3.2.1: version "3.4.2" @@ -2054,16 +2015,75 @@ cssnano@^4.1.10: postcss "^7.0.0" csso@^4.0.2: - version "4.0.3" - resolved "https://registry.yarnpkg.com/csso/-/csso-4.0.3.tgz#0d9985dc852c7cc2b2cacfbbe1079014d1a8e903" - integrity sha512-NL3spysxUkcrOgnpsT4Xdl2aiEiBG6bXswAABQVHcMrfjjBisFOKwLDOmf4wf32aPdcJws1zds2B0Rg+jqMyHQ== + version "4.2.0" + resolved "https://registry.yarnpkg.com/csso/-/csso-4.2.0.tgz#ea3a561346e8dc9f546d6febedd50187cf389529" + integrity sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA== dependencies: - css-tree "1.0.0-alpha.39" + css-tree "^1.1.2" csstype@^3.0.2: - version "3.0.3" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.3.tgz#2b410bbeba38ba9633353aff34b05d9755d065f8" - integrity sha512-jPl+wbWPOWJ7SXsWyqGRk3lGecbar0Cb0OvZF/r/ZU011R4YqiRehgkQ9p4eQfo9DSDLqLL3wHwfxeJiuIsNag== + version "3.0.6" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.6.tgz#865d0b5833d7d8d40f4e5b8a6d76aea3de4725ef" + integrity sha512-+ZAmfyWMT7TiIlzdqJgjMb7S4f1beorDbWbsocyK4RaiqA5RTX3K14bnBWmmA9QEM0gRdsjyyrEmcyga8Zsxmw== + +d3-array@^2.3.0: + version "2.11.0" + resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-2.11.0.tgz#5ed6a2869bc7d471aec8df9ff6ed9fef798facc4" + integrity sha512-26clcwmHQEdsLv34oNKq5Ia9tQ26Y/4HqS3dQzF42QBUqymZJ+9PORcN1G52bt37NsL2ABoX4lvyYZc+A9Y0zw== + dependencies: + internmap "^1.0.0" + +"d3-color@1 - 2": + version "2.0.0" + resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-2.0.0.tgz#8d625cab42ed9b8f601a1760a389f7ea9189d62e" + integrity sha512-SPXi0TSKPD4g9tw0NMZFnR95XVgUZiBH+uUTqQuDu1OsE2zomHU7ho0FISciaPvosimixwHFl3WHLGabv6dDgQ== + +"d3-format@1 - 2": + version "2.0.0" + resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-2.0.0.tgz#a10bcc0f986c372b729ba447382413aabf5b0767" + integrity sha512-Ab3S6XuE/Q+flY96HXT0jOXcM4EAClYFnRGY5zsjRGNy6qCYrQsMffs7cV5Q9xejb35zxW5hf/guKw34kvIKsA== + +"d3-interpolate@1.2.0 - 2", d3-interpolate@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-2.0.1.tgz#98be499cfb8a3b94d4ff616900501a64abc91163" + integrity sha512-c5UhwwTs/yybcmTpAVqwSFl6vrQ8JZJoT5F7xNFK9pymv5C0Ymcc9/LIJHtYIggg/yS9YHw8i8O8tgb9pupjeQ== + dependencies: + d3-color "1 - 2" + +"d3-path@1 - 2": + version "2.0.0" + resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-2.0.0.tgz#55d86ac131a0548adae241eebfb56b4582dd09d8" + integrity sha512-ZwZQxKhBnv9yHaiWd6ZU4x5BtCQ7pXszEV9CU6kRgwIQVQGLMv1oiL4M+MK/n79sYzsj+gcgpPQSctJUsLN7fA== + +d3-scale@^3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-3.2.3.tgz#be380f57f1f61d4ff2e6cbb65a40593a51649cfd" + integrity sha512-8E37oWEmEzj57bHcnjPVOBS3n4jqakOeuv1EDdQSiSrYnMCBdMd3nc4HtKk7uia8DUHcY/CGuJ42xxgtEYrX0g== + dependencies: + d3-array "^2.3.0" + d3-format "1 - 2" + d3-interpolate "1.2.0 - 2" + d3-time "1 - 2" + d3-time-format "2 - 3" + +d3-shape@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-2.0.0.tgz#2331b62fa784a2a1daac47a7233cfd69301381fd" + integrity sha512-djpGlA779ua+rImicYyyjnOjeubyhql1Jyn1HK0bTyawuH76UQRWXd+pftr67H6Fa8hSwetkgb/0id3agKWykw== + dependencies: + d3-path "1 - 2" + +"d3-time-format@2 - 3": + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-3.0.0.tgz#df8056c83659e01f20ac5da5fdeae7c08d5f1bb6" + integrity sha512-UXJh6EKsHBTjopVqZBhFysQcoXSv/5yLONZvkQ5Kk3qbwiUYkdX17Xa1PT6U1ZWXGGfB1ey5L8dKMlFq2DO0Ag== + dependencies: + d3-time "1 - 2" + +"d3-time@1 - 2": + version "2.0.0" + resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-2.0.0.tgz#ad7c127d17c67bd57a4c61f3eaecb81108b1e0ab" + integrity sha512-2mvhstTFcMvwStWd9Tj3e6CEqtOivtD8AUiHT8ido/xmzrI9ijrUUihZ6nHuf/vsScRBonagOdj0Vv+SEL5G3Q== date-fns@^2.15.0: version "2.16.1" @@ -2071,9 +2091,9 @@ date-fns@^2.15.0: integrity sha512-sAJVKx/FqrLYHAQeN7VpJrPhagZc9R4ImZIWYRFZaaohR3KzmuK88touwsSwSVT8Qcbd4zoDsnGfX4GFB4imyQ== dayjs@^1.8.30, dayjs@^1.9.3: - version "1.9.3" - resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.9.3.tgz#b7f94b22ad2a136a4ca02a01ab68ae893fe1a268" - integrity sha512-V+1SyIvkS+HmNbN1G7A9+ERbFTV9KTXu6Oor98v2xHmzzpp52OIJhQuJSTywWuBY5pyAEmlwbCi1Me87n/SLOw== + version "1.10.4" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.4.tgz#8e544a9b8683f61783f570980a8a80eaf54ab1e2" + integrity sha512-RI/Hh4kqRc1UKLOAf/T5zdMMX5DQIlDxwUe3wSyMMnEbGunnpENCdbUgM+dW7kXidZqCttBrmw7BhN4TMddkCw== debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9: version "2.6.9" @@ -2082,17 +2102,17 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9: dependencies: ms "2.0.0" -debug@^3.1.1, debug@^3.2.5: +debug@^3.1.1, debug@^3.2.6: version "3.2.7" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== dependencies: ms "^2.1.1" -debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: - version "4.2.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.2.0.tgz#7f150f93920e94c58f5574c2fd01a3110effe7f1" - integrity sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg== +debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" + integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== dependencies: ms "2.1.2" @@ -2109,6 +2129,11 @@ decamelize@^1.1.0, decamelize@^1.2.0: resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= +decimal.js-light@^2.4.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/decimal.js-light/-/decimal.js-light-2.5.1.tgz#134fd32508f19e208f4fb2f8dac0d2626a867934" + integrity sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg== + decode-uri-component@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" @@ -2126,11 +2151,6 @@ deep-equal@^1.0.1: object-keys "^1.1.1" regexp.prototype.flags "^1.2.0" -deep-extend@~0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" - integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== - deep-is@^0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" @@ -2272,6 +2292,13 @@ dom-converter@^0.2: dependencies: utila "~0.4" +dom-helpers@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.4.0.tgz#e9b369700f959f62ecde5a6babde4bccd9169af8" + integrity sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA== + dependencies: + "@babel/runtime" "^7.1.2" + dom-serializer@0: version "0.2.2" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51" @@ -2294,9 +2321,9 @@ domelementtype@1, domelementtype@^1.3.0, domelementtype@^1.3.1: integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== domelementtype@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.0.2.tgz#f3b6e549201e46f588b59463dd77187131fe6971" - integrity sha512-wFwTwCVebUrMgGeAwRL/NhZtHAUyT9n9yg4IMDwf10+6iCMxSkVq9MGCVEH+QZWo1nNidy8kNvwmv4zWHDTqvA== + version "2.1.0" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.1.0.tgz#a851c080a6d1c3d94344aed151d99f669edf585e" + integrity sha512-LsTgx/L5VpD+Q8lmsXSHW2WpA+eBlZ9HPf3erD1IoPF00/3JKHZ3BknUVA2QGDNu69ZNmyFmCWBSO45XjYKC5w== domhandler@^2.3.0: version "2.4.2" @@ -2305,14 +2332,6 @@ domhandler@^2.3.0: dependencies: domelementtype "1" -domutils@1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf" - integrity sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8= - dependencies: - dom-serializer "0" - domelementtype "1" - domutils@^1.5.1, domutils@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a" @@ -2321,13 +2340,13 @@ domutils@^1.5.1, domutils@^1.7.0: dom-serializer "0" domelementtype "1" -dot-case@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.3.tgz#21d3b52efaaba2ea5fda875bb1aa8124521cf4aa" - integrity sha512-7hwEmg6RiSQfm/GwPL4AAWXKy3YNNZA3oFv2Pdiey0mwkRCPZ9x6SZbkLcn8Ma5PYeVokzoD4Twv2n7LKp5WeA== +dot-case@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751" + integrity sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w== dependencies: - no-case "^3.0.3" - tslib "^1.10.0" + no-case "^3.0.4" + tslib "^2.0.3" dot-prop@^5.2.0: version "5.3.0" @@ -2341,10 +2360,10 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= -electron-to-chromium@^1.3.571: - version "1.3.582" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.582.tgz#1adfac5affce84d85b3d7b3dfbc4ade293a6ffc4" - integrity sha512-0nCJ7cSqnkMC+kUuPs0YgklFHraWGl/xHqtZWWtOeVtyi+YqkoAOMGuZQad43DscXCQI/yizcTa3u6B5r+BLww== +electron-to-chromium@^1.3.634: + version "1.3.645" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.645.tgz#c0b269ae2ecece5aedc02dd4586397d8096affb1" + integrity sha512-T7mYop3aDpRHIQaUYcmzmh6j9MAe560n6ukqjJMbVC6bVTau7dSpvB18bcsBPPtOSe10cKxhJFtlbEzLa0LL1g== emoji-regex@^7.0.1: version "7.0.3" @@ -2374,21 +2393,21 @@ end-of-stream@^1.1.0: once "^1.4.0" enhanced-resolve@^4.0.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.3.0.tgz#3b806f3bfafc1ec7de69551ef93cca46c1704126" - integrity sha512-3e87LvavsdxyoCfGusJnrZ5G8SLPOFeHSNpZI/ATL9a5leXo2k0w6MKnbqhdBad9qTobSfB20Ld7UmgoNbAZkQ== + version "4.5.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz#2f3cfd84dbe3b487f18f2db2ef1e064a571ca5ec" + integrity sha512-Nv9m36S/vxpsI+Hc4/ZGRs0n9mXqSWGGq49zxb/cJfPAQMbUtttJAlNPS4AQzaBdw/pKskw5bMbekT/Y7W/Wlg== dependencies: graceful-fs "^4.1.2" memory-fs "^0.5.0" tapable "^1.0.0" -enhanced-resolve@^5.3.1: - version "5.4.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.4.0.tgz#a8bcf23b00affac9455cf71efd80844f4054f4dc" - integrity sha512-ZmqfWURB2lConOBM1JdCVfPyMRv5RdKWktLXO6123p97ovVm2CLBgw9t5MBj3jJWA6eHyOeIws9iJQoGFR4euQ== +enhanced-resolve@^5.7.0: + version "5.7.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.7.0.tgz#525c5d856680fbd5052de453ac83e32049958b5c" + integrity sha512-6njwt/NsZFUKhM6j9U8hzVyD4E4r0x7NQzhTCbcWOJ0IQjNSAoalWmb0AE51Wn+fwan5qVESWi7t2ToBxs9vrw== dependencies: graceful-fs "^4.2.4" - tapable "^2.0.0" + tapable "^2.2.0" enquirer@^2.3.5, enquirer@^2.3.6: version "2.3.6" @@ -2403,9 +2422,9 @@ entities@^1.1.1: integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w== entities@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5" - integrity sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w== + version "2.2.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" + integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== envinfo@^7.7.3: version "7.7.3" @@ -2413,9 +2432,9 @@ envinfo@^7.7.3: integrity sha512-46+j5QxbPWza0PB1i15nZx0xQ4I/EfQxg9J8Had3b408SV63nEtor2e+oiY63amTo9KTuh2a3XLObNwduxYwwA== errno@^0.1.1, errno@^0.1.3: - version "0.1.7" - resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618" - integrity sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg== + version "0.1.8" + resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.8.tgz#8bb3e9c7d463be4976ff888f76b4809ebc2e811f" + integrity sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A== dependencies: prr "~1.0.1" @@ -2426,7 +2445,7 @@ error-ex@^1.2.0, error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -es-abstract@^1.17.0, es-abstract@^1.17.0-next.1, es-abstract@^1.17.2, es-abstract@^1.17.5: +es-abstract@^1.17.2: version "1.17.7" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.7.tgz#a4de61b2f66989fc7421676c1cb9787573ace54c" integrity sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g== @@ -2443,23 +2462,30 @@ es-abstract@^1.17.0, es-abstract@^1.17.0-next.1, es-abstract@^1.17.2, es-abstrac string.prototype.trimend "^1.0.1" string.prototype.trimstart "^1.0.1" -es-abstract@^1.18.0-next.0, es-abstract@^1.18.0-next.1: - version "1.18.0-next.1" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.0-next.1.tgz#6e3a0a4bda717e5023ab3b8e90bec36108d22c68" - integrity sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA== +es-abstract@^1.18.0-next.1: + version "1.18.0-next.2" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.0-next.2.tgz#088101a55f0541f595e7e057199e27ddc8f3a5c2" + integrity sha512-Ih4ZMFHEtZupnUh6497zEL4y2+w8+1ljnCyaTa+adcoafI1GOvMwFlDjBLfWR7y9VLfrjRJe9ocuHY1PSR9jjw== dependencies: + call-bind "^1.0.2" es-to-primitive "^1.2.1" function-bind "^1.1.1" + get-intrinsic "^1.0.2" has "^1.0.3" has-symbols "^1.0.1" is-callable "^1.2.2" - is-negative-zero "^2.0.0" + is-negative-zero "^2.0.1" is-regex "^1.1.1" - object-inspect "^1.8.0" + object-inspect "^1.9.0" object-keys "^1.1.1" - object.assign "^4.1.1" - string.prototype.trimend "^1.0.1" - string.prototype.trimstart "^1.0.1" + object.assign "^4.1.2" + string.prototype.trimend "^1.0.3" + string.prototype.trimstart "^1.0.3" + +es-module-lexer@^0.3.26: + version "0.3.26" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-0.3.26.tgz#7b507044e97d5b03b01d4392c74ffeb9c177a83b" + integrity sha512-Va0Q/xqtrss45hWzP8CZJwzGSZJjDM5/MJRE3IXXnUCcVLElR9BRaE9F62BopysASyc4nM3uwhSW7FFB9nlWAA== es-to-primitive@^1.2.1: version "1.2.1" @@ -2470,7 +2496,7 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" -escalade@^3.1.0: +escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== @@ -2485,7 +2511,7 @@ escape-string-regexp@^1.0.3, escape-string-regexp@^1.0.5: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= -eslint-config-airbnb-base@14.2.0, eslint-config-airbnb-base@^14.2.0: +eslint-config-airbnb-base@14.2.0: version "14.2.0" resolved "https://registry.yarnpkg.com/eslint-config-airbnb-base/-/eslint-config-airbnb-base-14.2.0.tgz#fe89c24b3f9dc8008c9c0d0d88c28f95ed65e9c4" integrity sha512-Snswd5oC6nJaevs3nZoLSTvGJBvzTfnBqOIArkf3cbyTyq9UD79wOk8s+RiL6bhca0p/eRO6veczhf6A/7Jy8Q== @@ -2494,6 +2520,15 @@ eslint-config-airbnb-base@14.2.0, eslint-config-airbnb-base@^14.2.0: object.assign "^4.1.0" object.entries "^1.1.2" +eslint-config-airbnb-base@^14.2.0: + version "14.2.1" + resolved "https://registry.yarnpkg.com/eslint-config-airbnb-base/-/eslint-config-airbnb-base-14.2.1.tgz#8a2eb38455dc5a312550193b319cdaeef042cd1e" + integrity sha512-GOrQyDtVEc1Xy20U7vsB2yAoB4nBlfH5HZJeatRXHleO+OS5Ot+MWij4Dpltw4/DyIkqUfqz1epfhVR5XWWQPA== + dependencies: + confusing-browser-globals "^1.0.10" + object.assign "^4.1.2" + object.entries "^1.1.2" + eslint-config-airbnb-typescript@^12.0.0: version "12.0.0" resolved "https://registry.yarnpkg.com/eslint-config-airbnb-typescript/-/eslint-config-airbnb-typescript-12.0.0.tgz#4bb6b4b72b1cfc45ef1fa0607735679ceb9a3814" @@ -2575,9 +2610,9 @@ eslint-plugin-react-hooks@^4.2.0: integrity sha512-623WEiZJqxR7VdxFCKLI6d6LLpwJkGPYKODnkH3D7WpOG5KM8yWueBd8TLsNAetEJNF5iJmolaAKO3F8yzyVBQ== eslint-plugin-react@^7.21.5: - version "7.21.5" - resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.21.5.tgz#50b21a412b9574bfe05b21db176e8b7b3b15bff3" - integrity sha512-8MaEggC2et0wSF6bUeywF7qQ46ER81irOdWS4QWxnnlAEsnzeBevk1sWh7fhpCghPpXb+8Ks7hvaft6L/xsR6g== + version "7.22.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.22.0.tgz#3d1c542d1d3169c45421c1215d9470e341707269" + integrity sha512-p30tuX3VS+NWv9nQot9xIGAHBXR0+xJVaZriEsHoJrASGCJZDJ8JLNM0YqKqI0AKm6Uxaa1VUHoNEibxRCMQHA== dependencies: array-includes "^3.1.1" array.prototype.flatmap "^1.2.3" @@ -2617,12 +2652,12 @@ eslint-visitor-keys@^2.0.0: integrity sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ== eslint@^7.11.0: - version "7.11.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.11.0.tgz#aaf2d23a0b5f1d652a08edacea0c19f7fadc0b3b" - integrity sha512-G9+qtYVCHaDi1ZuWzBsOWo2wSwd70TXnU6UHA3cTYHp7gCTXZcpggWFoUVAMRarg68qtPoNfFbzPh+VdOgmwmw== + version "7.18.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.18.0.tgz#7fdcd2f3715a41fe6295a16234bd69aed2c75e67" + integrity sha512-fbgTiE8BfUJZuBeq2Yi7J3RB3WGUQ9PNuNbmgi6jt9Iv8qrkxfy19Ds3OpL1Pm7zg3BtTVhvcUZbIRQ0wmSjAQ== dependencies: "@babel/code-frame" "^7.0.0" - "@eslint/eslintrc" "^0.1.3" + "@eslint/eslintrc" "^0.3.0" ajv "^6.10.0" chalk "^4.0.0" cross-spawn "^7.0.2" @@ -2632,10 +2667,10 @@ eslint@^7.11.0: eslint-scope "^5.1.1" eslint-utils "^2.1.0" eslint-visitor-keys "^2.0.0" - espree "^7.3.0" + espree "^7.3.1" esquery "^1.2.0" esutils "^2.0.2" - file-entry-cache "^5.0.1" + file-entry-cache "^6.0.0" functional-red-black-tree "^1.0.1" glob-parent "^5.0.0" globals "^12.1.0" @@ -2646,7 +2681,7 @@ eslint@^7.11.0: js-yaml "^3.13.1" json-stable-stringify-without-jsonify "^1.0.1" levn "^0.4.1" - lodash "^4.17.19" + lodash "^4.17.20" minimatch "^3.0.4" natural-compare "^1.4.0" optionator "^0.9.1" @@ -2655,17 +2690,17 @@ eslint@^7.11.0: semver "^7.2.1" strip-ansi "^6.0.0" strip-json-comments "^3.1.0" - table "^5.2.3" + table "^6.0.4" text-table "^0.2.0" v8-compile-cache "^2.0.3" -espree@^7.3.0: - version "7.3.0" - resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.0.tgz#dc30437cf67947cf576121ebd780f15eeac72348" - integrity sha512-dksIWsvKCixn1yrEXO8UosNSxaDoSYpq9reEjZSbHLpT5hpaCAKTLBwq0RHtLrIr+c0ByiYzWT8KTMRzoRCNlw== +espree@^7.3.0, espree@^7.3.1: + version "7.3.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.1.tgz#f2df330b752c6f55019f8bd89b7660039c1bbbb6" + integrity sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g== dependencies: acorn "^7.4.0" - acorn-jsx "^5.2.0" + acorn-jsx "^5.3.1" eslint-visitor-keys "^1.3.0" esprima@^4.0.0: @@ -2707,7 +2742,7 @@ etag@~1.8.1: resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= -eventemitter3@^4.0.0: +eventemitter3@^4.0.0, eventemitter3@^4.0.1: version "4.0.7" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== @@ -2737,19 +2772,19 @@ execa@^1.0.0: signal-exit "^3.0.0" strip-eof "^1.0.0" -execa@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a" - integrity sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA== +execa@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.0.0.tgz#4029b0007998a841fbd1032e5f4de86a3c1e3376" + integrity sha512-ov6w/2LCiuyO4RLYGdpFGjkcs0wMTgGE8PrkTHikeUy5iJekXyPIKUjifk5CsE0pt7sMCrMZ3YNqoCj6idQOnQ== dependencies: - cross-spawn "^7.0.0" - get-stream "^5.0.0" - human-signals "^1.1.1" + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" is-stream "^2.0.0" merge-stream "^2.0.0" - npm-run-path "^4.0.0" - onetime "^5.1.0" - signal-exit "^3.0.2" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" strip-final-newline "^2.0.0" execall@^2.0.0: @@ -2847,10 +2882,10 @@ fast-deep-equal@^3.1.1: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== -fast-glob@^3.1.1, fast-glob@^3.2.4: - version "3.2.4" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.4.tgz#d20aefbf99579383e7f3cc66529158c9b98554d3" - integrity sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ== +fast-glob@^3.1.1, fast-glob@^3.2.4, fast-glob@^3.2.5: + version "3.2.5" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.5.tgz#7939af2a656de79a4f1901903ee8adcaa7cb9661" + integrity sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg== dependencies: "@nodelib/fs.stat" "^2.0.2" "@nodelib/fs.walk" "^1.2.3" @@ -2875,37 +2910,30 @@ fastest-levenshtein@^1.0.12: integrity sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow== fastq@^1.6.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.8.0.tgz#550e1f9f59bbc65fe185cb6a9b4d95357107f481" - integrity sha512-SMIZoZdLh/fgofivvIkmknUXyPnvxRE3DhtZ5Me3Mrsk5gyPL42F0xr51TdRXskBxHfMp+07bcYzfsYEsSQA9Q== + version "1.10.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.10.0.tgz#74dbefccade964932cdf500473ef302719c652bb" + integrity sha512-NL2Qc5L3iQEsyYzweq7qfgy5OtXCmGzGvhElGEd/SoFWEMOEczNh5s5ocaF01HDetxz+p8ecjNPA6cZxxIHmzA== dependencies: reusify "^1.0.4" -faye-websocket@^0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.10.0.tgz#4e492f8d04dfb6f89003507f6edbf2d501e7c6f4" - integrity sha1-TkkvjQTftviQA1B/btvy1QHnxvQ= - dependencies: - websocket-driver ">=0.5.1" - -faye-websocket@~0.11.1: +faye-websocket@^0.11.3: version "0.11.3" resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.3.tgz#5c0e9a8968e8912c286639fde977a8b209f2508e" integrity sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA== dependencies: websocket-driver ">=0.5.1" -file-entry-cache@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz#ca0f6efa6dd3d561333fb14515065c2fafdf439c" - integrity sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g== +file-entry-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.0.tgz#7921a89c391c6d93efec2169ac6bf300c527ea0a" + integrity sha512-fqoO76jZ3ZnYrXLDRxBR1YvOvc0k844kcOg40bgsPrE25LAb/PDqTY+ho64Xh2c8ZXgIKldchCFHczG2UVRcWA== dependencies: - flat-cache "^2.0.1" + flat-cache "^3.0.4" file-loader@^6.1.1: - version "6.1.1" - resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.1.1.tgz#a6f29dfb3f5933a1c350b2dbaa20ac5be0539baa" - integrity sha512-Klt8C4BjWSXYQAfhpYYkG4qHNTna4toMHEbWrI5IuVoxbU6uiDKeKAP99R8mmbJi3lvewn/jQBOgU4+NS3tDQw== + version "6.2.0" + resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.2.0.tgz#baef7cf8e1840df325e4390b4484879480eebe4d" + integrity sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw== dependencies: loader-utils "^2.0.0" schema-utils "^3.0.0" @@ -2984,26 +3012,18 @@ find-up@^5.0.0: locate-path "^6.0.0" path-exists "^4.0.0" -find-versions@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/find-versions/-/find-versions-3.2.0.tgz#10297f98030a786829681690545ef659ed1d254e" - integrity sha512-P8WRou2S+oe222TOCHitLy8zj+SIsVJh52VP4lvXkaFVnOFFdoWv1H1Jjvel1aI6NCFOAaeAVm8qrI0odiLcww== +flat-cache@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" + integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== dependencies: - semver-regex "^2.0.0" + flatted "^3.1.0" + rimraf "^3.0.2" -flat-cache@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0" - integrity sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA== - dependencies: - flatted "^2.0.0" - rimraf "2.6.3" - write "1.0.3" - -flatted@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138" - integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA== +flatted@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.1.1.tgz#c4b489e80096d9df1dfc97c79871aea7c617c469" + integrity sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA== flatten@^1.0.2: version "1.0.3" @@ -3011,9 +3031,9 @@ flatten@^1.0.2: integrity sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg== follow-redirects@^1.0.0: - version "1.13.0" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.0.tgz#b42e8d93a2a7eea5ed88633676d6597bc8e384db" - integrity sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA== + version "1.13.2" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.2.tgz#dd73c8effc12728ba5cf4259d760ea5fb83e3147" + integrity sha512-6mPTgLxYm3r6Bkkg0vNM0HTjfGrOEtsfbhagQvbxDEsEkpNhw582upBaoRZylzen6krEmxXJgt9Ju6HiI4O7BA== for-in@^1.0.2: version "1.0.2" @@ -3021,16 +3041,15 @@ for-in@^1.0.2: integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= formik@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/formik/-/formik-2.2.0.tgz#04c067f6b832aa15cae055f6df6f4aa485b5be35" - integrity sha512-l47RfvejhfHNh8rTRVaCaPfx8nyeYDSTLaEqRvLX4qkWnrrq9ByGVCWggVR+0TVtzc5Ub1gLUuVu9UKuGwfhjA== + version "2.2.6" + resolved "https://registry.yarnpkg.com/formik/-/formik-2.2.6.tgz#378a4bafe4b95caf6acf6db01f81f3fe5147559d" + integrity sha512-Kxk2zQRafy56zhLmrzcbryUpMBvT0tal5IvcifK5+4YNGelKsnrODFJ0sZQRMQboblWNym4lAW3bt+tf2vApSA== dependencies: deepmerge "^2.1.1" hoist-non-react-statics "^3.3.0" lodash "^4.17.14" lodash-es "^4.17.14" react-fast-compare "^2.0.1" - scheduler "^0.18.0" tiny-warning "^1.0.2" tslib "^1.10.0" @@ -3039,6 +3058,11 @@ forwarded@~0.1.2: resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= +fraction.js@^4.0.13: + version "4.0.13" + resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.0.13.tgz#3c1c315fa16b35c85fffa95725a36fa729c69dfe" + integrity sha512-E1fz2Xs9ltlUp+qbiyx9wmt2n9dRzPsS11Jtdb8D2o+cC7wr9xkkKsVKJuBX0ST+LVS+LhLO+SbLJNtfWcJvXA== + fragment-cache@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" @@ -3061,14 +3085,14 @@ fs-extra@^8.1.0: universalify "^0.1.0" fs-extra@^9.0.1: - version "9.0.1" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.0.1.tgz#910da0062437ba4c39fedd863f1675ccfefcb9fc" - integrity sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ== + version "9.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" + integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== dependencies: at-least-node "^1.0.0" graceful-fs "^4.2.0" jsonfile "^6.0.1" - universalify "^1.0.0" + universalify "^2.0.0" fs-minipass@^2.0.0: version "2.1.0" @@ -3108,19 +3132,19 @@ generic-names@^2.0.1: loader-utils "^1.1.0" gensync@^1.0.0-beta.1: - version "1.0.0-beta.1" - resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.1.tgz#58f4361ff987e5ff6e1e7a210827aa371eaac269" - integrity sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg== + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== get-caller-file@^2.0.1: version "2.0.5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-intrinsic@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.0.1.tgz#94a9768fcbdd0595a1c9273aacf4c89d075631be" - integrity sha512-ZnWP+AmS1VUaLgTRy47+zKtjTxz+0xMpx3I52i+aalBK1QP19ggLF3Db89KJX7kjfOfP2eoa01qc++GwPgufPg== +get-intrinsic@^1.0.1, get-intrinsic@^1.0.2, get-intrinsic@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.0.tgz#892e62931e6938c8a23ea5aaebcfb67bd97da97e" + integrity sha512-M11rgtQp5GZMZzDL7jLTNxbDfurpzuau5uqRWDPvlHjfvg3TdScAZo96GLvhMjImrmR8uAt0FS2RLoMrfWGKlg== dependencies: function-bind "^1.1.1" has "^1.0.3" @@ -3138,12 +3162,10 @@ get-stream@^4.0.0: dependencies: pump "^3.0.0" -get-stream@^5.0.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" - integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== - dependencies: - pump "^3.0.0" +get-stream@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.0.tgz#3e0012cb6827319da2706e601a1583e8629a6718" + integrity sha512-A1B3Bh1UmL0bidM/YX2NsCOTnGJePL9rO/M+Mw3m9f2gUpfokS0hi5Eah0WSUEWZdZhIZtMjkIYS7mDfOqNHbg== get-value@^2.0.3, get-value@^2.0.6: version "2.0.6" @@ -3210,10 +3232,10 @@ globals@^12.1.0: dependencies: type-fest "^0.8.1" -globby@^11.0.1: - version "11.0.1" - resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.1.tgz#9a2bf107a068f3ffeabc49ad702c79ede8cfd357" - integrity sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ== +globby@^11.0.1, globby@^11.0.2: + version "11.0.2" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.2.tgz#1af538b766a3b540ebfb58a32b2e2d5897321d83" + integrity sha512-2ZThXDvvV8fYFRVIxnrMQBipZQDr7MxKAmQK1vujaj9/7eF0efG7BPUKJ7jP7G5SLF37xKDXvO4S/KKLj/Z0og== dependencies: array-union "^2.1.0" dir-glob "^3.0.1" @@ -3347,6 +3369,13 @@ hosted-git-info@^2.1.4: resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488" integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg== +hosted-git-info@^3.0.6: + version "3.0.7" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-3.0.7.tgz#a30727385ea85acfcee94e0aad9e368c792e036c" + integrity sha512-fWqc0IcuXs+BmE9orLDyVykAG9GJtGLGuZAAqgcckPgv5xad4AcXGIv8galtQvlwutxSlaMcdw7BUtq2EIvqCQ== + dependencies: + lru-cache "^6.0.0" + hpack.js@^2.1.6: version "2.1.6" resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2" @@ -3373,9 +3402,9 @@ html-comment-regex@^1.1.0: integrity sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ== html-entities@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.3.1.tgz#fb9a1a4b5b14c5daba82d3e34c6ae4fe701a0e44" - integrity sha512-rhE/4Z3hIhzHAUKbW8jVcCyuT5oJCXXqhN/6mXXVCpzTmvJnoH2HL/bt3EZ6p55jbFJBeAe1ZNpL5BugLujxNA== + version "1.4.0" + resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.4.0.tgz#cfbd1b01d2afaf9adca1b10ae7dffab98c71d2dc" + integrity sha512-8nxjcBcd8wovbeKx7h3wTji4e6+rhaVuPNpMqwWgnHh+N9ToqsCs6XztWRBPQ+UtzsoMAdKZtUENoVzU/EMtZA== html-minifier-terser@^5.0.1: version "5.1.1" @@ -3396,21 +3425,21 @@ html-tags@^3.1.0: integrity sha512-1qYz89hW3lFDEazhjW0yVAV87lw8lVkrJocr72XmBkMKsoSVJCQx3W8BXsC7hO2qAt8BoVjYjtAcZ9perqGnNg== html-webpack-plugin@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-4.5.0.tgz#625097650886b97ea5dae331c320e3238f6c121c" - integrity sha512-MouoXEYSjTzCrjIxWwg8gxL5fE2X2WZJLmBYXlaJhQUH5K/b5OrqmV7T4dB7iu0xkmJ6JlUuV6fFVtnqbPopZw== + version "4.5.1" + resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-4.5.1.tgz#40aaf1b5cb78f2f23a83333999625c20929cda65" + integrity sha512-yzK7RQZwv9xB+pcdHNTjcqbaaDZ+5L0zJHXfi89iWIZmb/FtzxhLk0635rmJihcQbs3ZUF27Xp4oWGx6EK56zg== dependencies: "@types/html-minifier-terser" "^5.0.0" "@types/tapable" "^1.0.5" "@types/webpack" "^4.41.8" html-minifier-terser "^5.0.1" loader-utils "^1.2.3" - lodash "^4.17.15" + lodash "^4.17.20" pretty-error "^2.1.1" tapable "^1.1.3" util.promisify "1.0.0" -htmlparser2@^3.10.0, htmlparser2@^3.10.1, htmlparser2@^3.3.0: +htmlparser2@^3.10.0, htmlparser2@^3.10.1: version "3.10.1" resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f" integrity sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ== @@ -3460,9 +3489,9 @@ http-errors@~1.7.2: toidentifier "1.0.0" http-parser-js@>=0.5.1: - version "0.5.2" - resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.2.tgz#da2e31d237b393aae72ace43882dd7e270a8ff77" - integrity sha512-opCO9ASqg5Wy2FNo7A0sxy71yGbbkJJXLdgMK04Tcypw9jr2MgWbyubb0+WdmDmGnFflO7fRbqbaihh/ENDlRQ== + version "0.5.3" + resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.3.tgz#01d2709c79d41698bb01d4decc5e9da4e4a033d9" + integrity sha512-t7hjvef/5HEK7RWTdUzVUhl8zkEu+LlaE0IYzdMuvbSDipxBRpOn4Uhw8ZyECEa808iVT8XCjzo6xmYt4CiLZg== http-proxy-middleware@0.19.1: version "0.19.1" @@ -3494,26 +3523,10 @@ http-proxy@^1.17.0, http-proxy@^1.18.1: follow-redirects "^1.0.0" requires-port "^1.0.0" -human-signals@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" - integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw== - -husky@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/husky/-/husky-4.3.0.tgz#0b2ec1d66424e9219d359e26a51c58ec5278f0de" - integrity sha512-tTMeLCLqSBqnflBZnlVDhpaIMucSGaYyX6855jM4AguGeWCeSzNdb1mfyWduTZ3pe3SJVvVWGL0jO1iKZVPfTA== - dependencies: - chalk "^4.0.0" - ci-info "^2.0.0" - compare-versions "^3.6.0" - cosmiconfig "^7.0.0" - find-versions "^3.2.0" - opencollective-postinstall "^2.0.2" - pkg-dir "^4.2.0" - please-upgrade-node "^3.2.0" - slash "^3.0.0" - which-pm-runs "^1.0.0" +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== iconv-lite@0.4.24: version "0.4.24" @@ -3535,9 +3548,9 @@ icss-utils@^4.0.0, icss-utils@^4.1.1: postcss "^7.0.14" icss-utils@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.0.0.tgz#03ed56c3accd32f9caaf1752ebf64ef12347bb84" - integrity sha512-aF2Cf/CkEZrI/vsu5WI/I+akFgdbwQHVE9YRZxATrhH4PVIe6a3BIjwjEcW+z+jP/hNh+YvM3lAAn1wJQ6opSg== + version "5.1.0" + resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae" + integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA== ignore@^4.0.6: version "4.0.6" @@ -3563,9 +3576,9 @@ import-fresh@^2.0.0: resolve-from "^3.0.0" import-fresh@^3.0.0, import-fresh@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.2.1.tgz#633ff618506e793af5ac91bf48b72677e15cbe66" - integrity sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ== + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== dependencies: parent-module "^1.0.0" resolve-from "^4.0.0" @@ -3619,7 +3632,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.0, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3: +inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -3630,9 +3643,9 @@ inherits@2.0.3: integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= ini@^1.3.5: - version "1.3.5" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" - integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== + version "1.3.8" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" + integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== insert-css@^2.0.0: version "2.0.0" @@ -3648,13 +3661,18 @@ internal-ip@^4.3.0: ipaddr.js "^1.9.0" internal-slot@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.2.tgz#9c2e9fb3cd8e5e4256c6f45fe310067fcfa378a3" - integrity sha512-2cQNfwhAfJIkU4KZPkDI+Gj5yNNnbqi40W9Gge6dfnk4TocEVm00B3bdiL+JINrbGJil2TeHvM4rETGzk/f/0g== + version "1.0.3" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c" + integrity sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA== dependencies: - es-abstract "^1.17.0-next.1" + get-intrinsic "^1.1.0" has "^1.0.3" - side-channel "^1.0.2" + side-channel "^1.0.4" + +internmap@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/internmap/-/internmap-1.0.0.tgz#3c6bf0944b0eae457698000412108752bbfddb56" + integrity sha512-SdoDWwNOTE2n4JWUsLn4KXZGuZPjPF9yyOGc8bnfWnBQh7BD/l80rzSznKc/r4Y0aQ7z3RTk9X+tV4tHBpu+dA== interpret@^2.2.0: version "2.2.0" @@ -3713,11 +3731,6 @@ is-alphabetical@^1.0.0: resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-1.0.4.tgz#9e7d6b94916be22153745d184c298cbf986a686d" integrity sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg== -is-alphanumeric@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-alphanumeric/-/is-alphanumeric-1.0.0.tgz#4a9cef71daf4c001c1d81d63d140cf53fd6889f4" - integrity sha1-Spzvcdr0wAHB2B1j0UDPU/1oifQ= - is-alphanumerical@^1.0.0: version "1.0.4" resolved "https://registry.yarnpkg.com/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz#7eb9a2431f855f6b1ef1a78e326df515696c4dbf" @@ -3756,9 +3769,9 @@ is-buffer@^1.1.5: integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== is-buffer@^2.0.0: - version "2.0.4" - resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.4.tgz#3e572f23c8411a5cfd9557c849e3665e0b290623" - integrity sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A== + version "2.0.5" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191" + integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ== is-callable@^1.1.4, is-callable@^1.2.2: version "1.2.2" @@ -3777,13 +3790,6 @@ is-color-stop@^1.0.0: rgb-regex "^1.0.1" rgba-regex "^1.0.0" -is-core-module@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.0.0.tgz#58531b70aed1db7c0e8d4eb1a0a2d1ddd64bd12d" - integrity sha512-jq1AH6C8MuteOoBPwkxHafmByhL9j5q4OaPGdbuD+ZtQJVzH+i6E3BJDQcBA09k57i2Hh2yQbEG8yObZ0jdlWw== - dependencies: - has "^1.0.3" - is-core-module@^2.1.0: version "2.2.0" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.2.0.tgz#97037ef3d52224d85163f5597b2b63d9afed981a" @@ -3889,10 +3895,10 @@ is-negated-glob@^1.0.0: resolved "https://registry.yarnpkg.com/is-negated-glob/-/is-negated-glob-1.0.0.tgz#6910bca5da8c95e784b5751b976cf5a10fee36d2" integrity sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI= -is-negative-zero@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.0.tgz#9553b121b0fac28869da9ed459e20c7543788461" - integrity sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE= +is-negative-zero@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24" + integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w== is-number@^3.0.0: version "3.0.0" @@ -4012,21 +4018,16 @@ is-unc-path@^1.0.0: dependencies: unc-path-regex "^0.1.2" -is-whitespace-character@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-whitespace-character/-/is-whitespace-character-1.0.4.tgz#0858edd94a95594c7c9dd0b5c174ec6e45ee4aa7" - integrity sha512-SDweEzfIZM0SJV0EUga669UTKlmL0Pq8Lno0QDQsPnvECB3IM2aP0gdx5TrU0A01MAPfViaZiI2V1QMZLaKK5w== +is-what@^3.7.1: + version "3.12.0" + resolved "https://registry.yarnpkg.com/is-what/-/is-what-3.12.0.tgz#f4405ce4bd6dd420d3ced51a026fb90e03705e55" + integrity sha512-2ilQz5/f/o9V7WRWJQmpFYNmQFZ9iM+OXRonZKcYgTkCzjb949Vi4h282PD1UfmgHk666rcWonbRJ++KI41VGw== is-windows@^1.0.1, is-windows@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== -is-word-character@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-word-character/-/is-word-character-1.0.4.tgz#ce0e73216f98599060592f62ff31354ddbeb0230" - integrity sha512-5SMO8RVennx3nZrqtKwCGyyetPE9VDba5ugvKLaD4KopPG5kR4mQ7tNt/r7feL5yt5h3lpuBbIUmCOG2eSzXHA== - is-wsl@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" @@ -4059,16 +4060,7 @@ isobject@^3.0.0, isobject@^3.0.1: resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= -jest-worker@^26.5.0: - version "26.5.0" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.5.0.tgz#87deee86dbbc5f98d9919e0dadf2c40e3152fa30" - integrity sha512-kTw66Dn4ZX7WpjZ7T/SUDgRhapFRKWmisVAF0Rv4Fu8SLFD7eLbqpLvbxVqYhSgaWa7I+bW7pHnbyfNsH6stug== - dependencies: - "@types/node" "*" - merge-stream "^2.0.0" - supports-color "^7.0.0" - -jest-worker@^26.6.1: +jest-worker@^26.6.2: version "26.6.2" resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed" integrity sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ== @@ -4083,9 +4075,9 @@ jest-worker@^26.6.1: integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== js-yaml@^3.13.1: - version "3.14.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482" - integrity sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A== + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== dependencies: argparse "^1.0.7" esprima "^4.0.0" @@ -4110,6 +4102,11 @@ json-schema-traverse@^0.4.1: resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== +json-schema-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" @@ -4122,7 +4119,7 @@ json2mq@^0.2.0: dependencies: string-convert "^0.2.0" -json3@^3.3.2: +json3@^3.3.3: version "3.3.3" resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.3.tgz#7fc10e375fc5ae42c4705a5cc0aa6f62be305b81" integrity sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA== @@ -4149,21 +4146,21 @@ jsonfile@^4.0.0: graceful-fs "^4.1.6" jsonfile@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.0.1.tgz#98966cba214378c8c84b82e085907b40bf614179" - integrity sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg== + version "6.1.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" + integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== dependencies: - universalify "^1.0.0" + universalify "^2.0.0" optionalDependencies: graceful-fs "^4.1.6" "jsx-ast-utils@^2.4.1 || ^3.0.0": - version "3.1.0" - resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.1.0.tgz#642f1d7b88aa6d7eb9d8f2210e166478444fa891" - integrity sha512-d4/UOjg+mxAWxCiF0c5UTSwyqbchkbqCvK87aBovhnh8GtysTjWmgC63tY0cJx/HzGgm9qnA147jVBdpOiQ2RA== + version "3.2.0" + resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.2.0.tgz#41108d2cec408c3453c1bbe8a4aae9e1e2bd8f82" + integrity sha512-EIsmt3O3ljsU6sot/J4E1zDRxfBNrhjyf/OKjlydwgEimQuznlM4Wv7U+ueONJMyEn1WRE0K8dhi3dVAXYT24Q== dependencies: - array-includes "^3.1.1" - object.assign "^4.1.1" + array-includes "^3.1.2" + object.assign "^4.1.2" killable@^1.0.1: version "1.0.1" @@ -4199,10 +4196,10 @@ klona@^2.0.4: resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.4.tgz#7bb1e3affb0cb8624547ef7e8f6708ea2e39dfc0" integrity sha512-ZRbnvdg/NxqzC7L9Uyqzf4psi1OM4Cuc+sJAkQPjO6XkQIJTNbfK2Rsmbw8fx1p2mkZdp2FZYo2+LwXYY/uwIA== -known-css-properties@^0.19.0: - version "0.19.0" - resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.19.0.tgz#5d92b7fa16c72d971bda9b7fe295bdf61836ee5b" - integrity sha512-eYboRV94Vco725nKMlpkn3nV2+96p9c3gKXRsYqAJSswSENvBhN7n5L+uDhY58xQa0UukWsDMTGELzmD8Q+wTA== +known-css-properties@^0.20.0: + version "0.20.0" + resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.20.0.tgz#0570831661b47dd835293218381166090ff60e96" + integrity sha512-URvsjaA9ypfreqJ2/ylDr5MUERhJZ+DhguoWRr2xgS5C7aGCalXo+ewL+GixgKBfhT2vuL02nbIgNGqVWgTOYw== last-call-webpack-plugin@^3.0.0: version "3.0.0" @@ -4222,10 +4219,11 @@ less-loader@^5.0.0: pify "^4.0.1" less@^3.12.2: - version "3.12.2" - resolved "https://registry.yarnpkg.com/less/-/less-3.12.2.tgz#157e6dd32a68869df8859314ad38e70211af3ab4" - integrity sha512-+1V2PCMFkL+OIj2/HrtrvZw0BC0sYLMICJfbQjuj/K8CEnlrFX6R5cKKgzzttsZDHyxQNL1jqMREjKN3ja/E3Q== + version "3.13.1" + resolved "https://registry.yarnpkg.com/less/-/less-3.13.1.tgz#0ebc91d2a0e9c0c6735b83d496b0ab0583077909" + integrity sha512-SwA1aQXGUvp+P5XdZslUOhhLnClSLIjWvJhmd+Vgib5BFIr9lMNlQwmwUNOjXThF/A0x+MCYYPeWEfeWiLRnTw== dependencies: + copy-anything "^2.0.1" tslib "^1.10.0" optionalDependencies: errno "^0.1.1" @@ -4236,11 +4234,6 @@ less@^3.12.2: native-request "^1.0.5" source-map "~0.6.0" -leven@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" - integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== - levn@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" @@ -4249,14 +4242,6 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" -line-column@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/line-column/-/line-column-1.0.2.tgz#d25af2936b6f4849172b312e4792d1d987bc34a2" - integrity sha1-0lryk2tvSEkXKzEuR5LR2Ye8NKI= - dependencies: - isarray "^1.0.0" - isobject "^2.0.0" - lines-and-columns@^1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" @@ -4272,12 +4257,12 @@ load-json-file@^2.0.0: pify "^2.0.0" strip-bom "^3.0.0" -loader-runner@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.1.0.tgz#f70bc0c29edbabdf2043e7ee73ccc3fe1c96b42d" - integrity sha512-oR4lB4WvwFoC70ocraKhn5nkKSs23t57h9udUgw8o0iH8hMXeEoRuUgfcvgUwAJ1ZpRqBvcou4N2SMvM1DwMrA== +loader-runner@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.2.0.tgz#d7022380d66d14c5fb1d496b89864ebcfd478384" + integrity sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw== -loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.2.3: +loader-utils@^1.1.0, loader-utils@^1.2.3: version "1.4.0" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613" integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA== @@ -4326,9 +4311,9 @@ locate-path@^6.0.0: p-locate "^5.0.0" lodash-es@^4.17.14: - version "4.17.15" - resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.15.tgz#21bd96839354412f23d7a10340e5eac6ee455d78" - integrity sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ== + version "4.17.20" + resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.20.tgz#29f6332eefc60e849f869c264bc71126ad61e8f7" + integrity sha512-JD1COMZsq8maT6mnuz1UMV0jvYD0E0aUsSOdrr1/nAG3dhqQXwRRgeW0cSqH1U43INKcqxaiVIQNOUDld7gRDA== lodash._reinterpolate@^3.0.0: version "3.0.0" @@ -4390,7 +4375,7 @@ lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.5: +lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.5, lodash@~4.17.4: version "4.17.20" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== @@ -4407,7 +4392,7 @@ loglevel@^1.6.8: resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.7.1.tgz#005fde2f5e6e47068f935ff28573e125ef72f197" integrity sha512-Hesni4s5UkWkwCGJMQGAh71PaLUmKFM60dHvq0zi/vDhhrzuk+4GgNbTXJ12YYQJn6ZKBDNIjYcuQGKudvqrIw== -longest-streak@^2.0.1: +longest-streak@^2.0.0: version "2.0.4" resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-2.0.4.tgz#b8599957da5b5dab64dee3fe316fa774597d90e4" integrity sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg== @@ -4419,12 +4404,12 @@ loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3 dependencies: js-tokens "^3.0.0 || ^4.0.0" -lower-case@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.1.tgz#39eeb36e396115cc05e29422eaea9e692c9408c7" - integrity sha512-LiWgfDLLb1dwbFQZsSglpRj+1ctGnayXz3Uv0/WO8n558JycT5fg6zkNcnW0G68Nn0aEldTFeEfmjCfmqry/rQ== +lower-case@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28" + integrity sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg== dependencies: - tslib "^1.10.0" + tslib "^2.0.3" lru-cache@^6.0.0: version "6.0.0" @@ -4475,40 +4460,49 @@ map-visit@^1.0.0: dependencies: object-visit "^1.0.0" -markdown-escapes@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/markdown-escapes/-/markdown-escapes-1.0.4.tgz#c95415ef451499d7602b91095f3c8e8975f78535" - integrity sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg== - -markdown-table@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-2.0.0.tgz#194a90ced26d31fe753d8b9434430214c011865b" - integrity sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A== - dependencies: - repeat-string "^1.0.0" - mathml-tag-names@^2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3" integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg== -mdast-util-compact@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/mdast-util-compact/-/mdast-util-compact-2.0.1.tgz#cabc69a2f43103628326f35b1acf735d55c99490" - integrity sha512-7GlnT24gEwDrdAwEHrU4Vv5lLWrEer4KOkAiKT9nYstsTad7Oc1TwqT2zIMKRdZF7cTuaf+GA1E4Kv7jJh8mPA== +mdast-util-from-markdown@^0.8.0: + version "0.8.4" + resolved "https://registry.yarnpkg.com/mdast-util-from-markdown/-/mdast-util-from-markdown-0.8.4.tgz#2882100c1b9fc967d3f83806802f303666682d32" + integrity sha512-jj891B5pV2r63n2kBTFh8cRI2uR9LQHsXG1zSDqfhXkIlDzrTcIlbB5+5aaYEkl8vOPIOPLf8VT7Ere1wWTMdw== dependencies: - unist-util-visit "^2.0.0" + "@types/mdast" "^3.0.0" + mdast-util-to-string "^2.0.0" + micromark "~2.11.0" + parse-entities "^2.0.0" + unist-util-stringify-position "^2.0.0" + +mdast-util-to-markdown@^0.6.0: + version "0.6.2" + resolved "https://registry.yarnpkg.com/mdast-util-to-markdown/-/mdast-util-to-markdown-0.6.2.tgz#8fe6f42a2683c43c5609dfb40407c095409c85b4" + integrity sha512-iRczns6WMvu0hUw02LXsPDJshBIwtUPbvHBWo19IQeU0YqmzlA8Pd30U8V7uiI0VPkxzS7A/NXBXH6u+HS87Zg== + dependencies: + "@types/unist" "^2.0.0" + longest-streak "^2.0.0" + mdast-util-to-string "^2.0.0" + parse-entities "^2.0.0" + repeat-string "^1.0.0" + zwitch "^1.0.0" + +mdast-util-to-string@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mdast-util-to-string/-/mdast-util-to-string-2.0.0.tgz#b8cfe6a713e1091cb5b728fc48885a4767f8b97b" + integrity sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w== + +mdn-data@2.0.14: + version "2.0.14" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" + integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow== mdn-data@2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.4.tgz#699b3c38ac6f1d728091a64650b65d388502fd5b" integrity sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA== -mdn-data@2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.6.tgz#852dc60fcaa5daa2e8cf6c9189c440ed3e042978" - integrity sha512-rQvjv71olwNHgiTbfPZFkJtjNMciWgswYeciZhtvWLO8bmX3TnhyA62I6sTWOyZssWHJJjY6/KiWwqQsWWsqOA== - media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" @@ -4530,22 +4524,23 @@ memory-fs@^0.5.0: errno "^0.1.3" readable-stream "^2.0.1" -meow@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/meow/-/meow-7.1.1.tgz#7c01595e3d337fcb0ec4e8eed1666ea95903d306" - integrity sha512-GWHvA5QOcS412WCo8vwKDlTelGLsCGBVevQB5Kva961rmNfun0PCbv5+xta2kUMFJyR8/oWnn7ddeKdosbAPbA== +meow@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/meow/-/meow-9.0.0.tgz#cd9510bc5cac9dee7d03c73ee1f9ad959f4ea364" + integrity sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ== dependencies: "@types/minimist" "^1.2.0" camelcase-keys "^6.2.2" + decamelize "^1.2.0" decamelize-keys "^1.1.0" hard-rejection "^2.1.0" minimist-options "4.1.0" - normalize-package-data "^2.5.0" + normalize-package-data "^3.0.0" read-pkg-up "^7.0.1" redent "^3.0.0" trim-newlines "^3.0.0" - type-fest "^0.13.1" - yargs-parser "^18.1.3" + type-fest "^0.18.0" + yargs-parser "^20.2.3" merge-descriptors@1.0.1: version "1.0.1" @@ -4567,6 +4562,14 @@ methods@~1.1.2: resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= +micromark@~2.11.0: + version "2.11.2" + resolved "https://registry.yarnpkg.com/micromark/-/micromark-2.11.2.tgz#e8b6a05f54697d2d3d27fc89600c6bc40dd05f35" + integrity sha512-IXuP76p2uj8uMg4FQc1cRE7lPCLsfAXuEfdjtdO55VRiFO1asrCSQ5g43NmPqFtRwzEnEhafRVzn2jg0UiKArQ== + dependencies: + debug "^4.0.0" + parse-entities "^2.0.0" + micromatch@^3.1.10, micromatch@^3.1.4: version "3.1.10" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" @@ -4594,22 +4597,17 @@ micromatch@^4.0.0, micromatch@^4.0.2: braces "^3.0.1" picomatch "^2.0.5" -mime-db@1.44.0: - version "1.44.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" - integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg== - -"mime-db@>= 1.43.0 < 2": +mime-db@1.45.0, "mime-db@>= 1.43.0 < 2": version "1.45.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.45.0.tgz#cceeda21ccd7c3a745eba2decd55d4b73e7879ea" integrity sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w== mime-types@^2.1.27, mime-types@~2.1.17, mime-types@~2.1.24: - version "2.1.27" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f" - integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w== + version "2.1.28" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.28.tgz#1160c4757eab2c5363888e005273ecf79d2a0ecd" + integrity sha512-0TO2yJ5YHYr7M2zzT7gDU1tbwHxEUWBCLt0lscSNpcdAfFyJOVEpRYNS7EXVcTLNj/25QO8gulHC5JtTzSE2UQ== dependencies: - mime-db "1.44.0" + mime-db "1.45.0" mime@1.6.0, mime@^1.4.1: version "1.6.0" @@ -4617,9 +4615,9 @@ mime@1.6.0, mime@^1.4.1: integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== mime@^2.4.4: - version "2.4.6" - resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.6.tgz#e5b407c90db442f2beb5b162373d07b69affa4d1" - integrity sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA== + version "2.5.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-2.5.0.tgz#2b4af934401779806ee98026bb42e8c1ae1876b1" + integrity sha512-ft3WayFSFUVBuJj7BMLKAQcSlItKtfjsKDDsii3rqFDAZ7t11zRe8ASw/GlmivGwVUYtwkQrxiGGpL6gFvB0ag== mimic-fn@^2.1.0: version "2.1.0" @@ -4632,17 +4630,17 @@ min-indent@^1.0.0: integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== mini-create-react-context@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/mini-create-react-context/-/mini-create-react-context-0.4.0.tgz#df60501c83151db69e28eac0ef08b4002efab040" - integrity sha512-b0TytUgFSbgFJGzJqXPKCFCBWigAjpjo+Fl7Vf7ZbKRDptszpppKxXH6DRXEABZ/gcEQczeb0iZ7JvL8e8jjCA== + version "0.4.1" + resolved "https://registry.yarnpkg.com/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz#072171561bfdc922da08a60c2197a497cc2d1d5e" + integrity sha512-YWCYEmd5CQeHGSAKrYvXgmzzkrvssZcuuQDDeqkT+PziKGMgE+0MCCtcKbROzocGBG1meBLl2FotlRwf4gAzbQ== dependencies: - "@babel/runtime" "^7.5.5" + "@babel/runtime" "^7.12.1" tiny-warning "^1.0.3" mini-css-extract-plugin@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-1.1.1.tgz#4ab9948b7be38cf3a730cb4c9a881eb7f62baadb" - integrity sha512-pzlnOi/lMkwIkdb7zoRQvbkW18AFCQffouSBpxy+e3pnKTKMC5IuMVHYndexKZmacfsOZS2LXCe8gIgkrC+yqg== + version "1.3.4" + resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-1.3.4.tgz#706e69632cdcdb8b15bf8e638442a0dba304a9c8" + integrity sha512-dNjqyeogUd8ucUgw5sxm1ahvSfSUgef7smbmATRSbDm4EmNx5kQA6VdUEhEeCKSjX6CTYjb5vxgMUvRjqP3uHg== dependencies: loader-utils "^2.0.0" schema-utils "^3.0.0" @@ -4739,14 +4737,14 @@ mkdirp@^1.0.3, mkdirp@^1.0.4: integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== mobx-react-lite@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/mobx-react-lite/-/mobx-react-lite-3.0.1.tgz#417f54a819d1e3e00c073077f29373399c95b005" - integrity sha512-Ue8uGgT5iOjMyNf5ptoFW7BTvyLIwggzIkoFpwORrqf73TPqu47iLpz/DNvaba3v40kSsEpp050qYroMNuA1xw== + version "3.1.7" + resolved "https://registry.yarnpkg.com/mobx-react-lite/-/mobx-react-lite-3.1.7.tgz#8896e9ec4f3d2117ddc0a5bfc60802c39a5258f4" + integrity sha512-G6kgur98RrEpoi563ERzxlSwn606xY6Ay4BOtfYXl/QaHZYXbhbCe3YJGROPU2ao4f2bfhnt8iZW3YnBW2BqXQ== mobx@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/mobx/-/mobx-6.0.1.tgz#ec23520848527bf7834d7b4c0b54b2a8eb6e7c14" - integrity sha512-Pk6uJXZ34yqd661yRmS6z/9avm4FOGXpFpVjnEfiYYOsZXnAxv1fpYjxTCEZ9tuwk0Xe1qnUUlgm+rJtGe0YJA== + version "6.0.5" + resolved "https://registry.yarnpkg.com/mobx/-/mobx-6.0.5.tgz#617e716b7aa81d5b700598af3bb1643ff2144410" + integrity sha512-Q3/GiSj/QyazDn1n44PjdiMlokCE6gVs85BnFR0xCJmkF2d34k/ZnWAEG7wGbnJYEi+0f5CdvzDquBiKeo56bA== moment@^2.24.0, moment@^2.25.3: version "2.29.1" @@ -4763,11 +4761,16 @@ ms@2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== -ms@2.1.2, ms@^2.1.1: +ms@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== +ms@^2.1.1: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + multicast-dns-service-types@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz#899f11d9686e5e05cb91b35d5f0e63b773cfc901" @@ -4797,10 +4800,10 @@ nan@^2.12.1: resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19" integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ== -nanoid@^3.1.12: - version "3.1.12" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.12.tgz#6f7736c62e8d39421601e4a0c77623a97ea69654" - integrity sha512-1qstj9z5+x491jfiC4Nelk+f8XBad7LN20PmyWINJEMRSf3wcAjAWysw1qaA8z6NSKe2sjq1hRSDpBH5paCb6A== +nanoid@^3.1.20: + version "3.1.20" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.20.tgz#badc263c6b1dcf14b71efaa85f6ab4c1d6cfc788" + integrity sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw== nanomatch@^1.2.9: version "1.2.13" @@ -4820,9 +4823,9 @@ nanomatch@^1.2.9: to-regex "^3.0.1" native-request@^1.0.5: - version "1.0.7" - resolved "https://registry.yarnpkg.com/native-request/-/native-request-1.0.7.tgz#ff742dc555b4c8f2f1c14b548639ba174e573856" - integrity sha512-9nRjinI9bmz+S7dgNtf4A70+/vPhnd+2krGpy4SUlADuOuSa24IDkNaZ+R/QT1wQ6S8jBdi6wE7fLekFZNfUpQ== + version "1.0.8" + resolved "https://registry.yarnpkg.com/native-request/-/native-request-1.0.8.tgz#8f66bf606e0f7ea27c0e5995eb2f5d03e33ae6fb" + integrity sha512-vU2JojJVelUGp6jRcLwToPoWGxSx23z/0iX+I77J3Ht17rf2INGjrhOoQnjVo60nQd8wVsgzKkPfRXBiVdD2ag== natural-compare@^1.4.0: version "1.4.0" @@ -4844,23 +4847,23 @@ nice-try@^1.0.4: resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== -no-case@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.3.tgz#c21b434c1ffe48b39087e86cfb4d2582e9df18f8" - integrity sha512-ehY/mVQCf9BL0gKfsJBvFJen+1V//U+0HQMPrWct40ixE4jnv0bfvxDbWtAHL9EcaPEOJHVVYKoQn1TlZUB8Tw== +no-case@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d" + integrity sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg== dependencies: - lower-case "^2.0.1" - tslib "^1.10.0" + lower-case "^2.0.2" + tslib "^2.0.3" node-forge@^0.10.0: version "0.10.0" resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3" integrity sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA== -node-releases@^1.1.61: - version "1.1.64" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.64.tgz#71b4ae988e9b1dd7c1ffce58dd9e561752dfebc5" - integrity sha512-Iec8O9166/x2HRMJyLLLWkd0sFFLrFNy+Xf+JQfSQsdBJzPcHpNl3JQ9gD4j+aJxmCa25jNsIbM4bmACtSbkSg== +node-releases@^1.1.69: + version "1.1.70" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.70.tgz#66e0ed0273aa65666d7fe78febe7634875426a08" + integrity sha512-Slf2s69+2/uAD79pVVQo8uSiC34+g8GWY8UH2Qtqv34ZfhYrxpYpfzs9Js9d6O0mbDmALuxaTlplnBTnSELcrw== normalize-package-data@^2.3.2, normalize-package-data@^2.5.0: version "2.5.0" @@ -4872,6 +4875,16 @@ normalize-package-data@^2.3.2, normalize-package-data@^2.5.0: semver "2 || 3 || 4 || 5" validate-npm-package-license "^3.0.1" +normalize-package-data@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-3.0.0.tgz#1f8a7c423b3d2e85eb36985eaf81de381d01301a" + integrity sha512-6lUjEI0d3v6kFrtgA/lOx4zHCWULXsFNIjHolnZCKCTLA6m/G625cdn3O7eNmT0iD3jfo6HZ9cdImGZwf21prw== + dependencies: + hosted-git-info "^3.0.6" + resolve "^1.17.0" + semver "^7.3.2" + validate-npm-package-license "^3.0.1" + normalize-path@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" @@ -4906,14 +4919,14 @@ npm-run-path@^2.0.0: dependencies: path-key "^2.0.0" -npm-run-path@^4.0.0: +npm-run-path@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== dependencies: path-key "^3.0.0" -nth-check@^1.0.2, nth-check@~1.0.1: +nth-check@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c" integrity sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg== @@ -4925,7 +4938,7 @@ num2fraction@^1.2.2: resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede" integrity sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4= -object-assign@4.x, object-assign@^4.0.1, object-assign@^4.1.1: +object-assign@^4.0.1, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= @@ -4940,14 +4953,14 @@ object-copy@^0.1.0: kind-of "^3.0.3" object-hash@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.0.3.tgz#d12db044e03cd2ca3d77c0570d87225b02e1e6ea" - integrity sha512-JPKn0GMu+Fa3zt3Bmr66JhokJU5BaNBIh4ZeTlaCBzrBsOeXzwcKKAK1tbLiPKgvwmPXsDvvLHoWh5Bm7ofIYg== + version "2.1.1" + resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.1.1.tgz#9447d0279b4fcf80cff3259bf66a1dc73afabe09" + integrity sha512-VOJmgmS+7wvXf8CjbQmimtCnEx3IAoLxI3fp2fbWehxrWBcAQFbk+vcwb6vzR0VZv/eNCJ/27j151ZTwqW/JeQ== -object-inspect@^1.8.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.8.0.tgz#df807e5ecf53a609cc6bfe93eac3cc7be5b3a9d0" - integrity sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA== +object-inspect@^1.8.0, object-inspect@^1.9.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.9.0.tgz#c90521d74e1127b67266ded3394ad6116986533a" + integrity sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw== object-is@^1.0.1: version "1.1.4" @@ -4969,42 +4982,44 @@ object-visit@^1.0.0: dependencies: isobject "^3.0.0" -object.assign@^4.1.0, object.assign@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.1.tgz#303867a666cdd41936ecdedfb1f8f3e32a478cdd" - integrity sha512-VT/cxmx5yaoHSOTSyrCygIDFco+RsibY2NM0a4RdEeY/4KgqezwFtK1yr3U67xYhqJSlASm2pKhLVzPj2lr4bA== +object.assign@^4.1.0, object.assign@^4.1.1, object.assign@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" + integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ== dependencies: + call-bind "^1.0.0" define-properties "^1.1.3" - es-abstract "^1.18.0-next.0" has-symbols "^1.0.1" object-keys "^1.1.1" object.entries@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.2.tgz#bc73f00acb6b6bb16c203434b10f9a7e797d3add" - integrity sha512-BQdB9qKmb/HyNdMNWVr7O3+z5MUIx3aiegEIJqjMBbBf0YT9RRxTJSim4mzFqtyr7PDAHigq0N9dO0m0tRakQA== + version "1.1.3" + resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.3.tgz#c601c7f168b62374541a07ddbd3e2d5e4f7711a6" + integrity sha512-ym7h7OZebNS96hn5IJeyUmaWhaSM4SVtAPPfNLQEI2MYWCO2egsITb9nab2+i/Pwibx+R0mtn+ltKJXRSeTMGg== dependencies: + call-bind "^1.0.0" define-properties "^1.1.3" - es-abstract "^1.17.5" + es-abstract "^1.18.0-next.1" has "^1.0.3" object.fromentries@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.2.tgz#4a09c9b9bb3843dd0f89acdb517a794d4f355ac9" - integrity sha512-r3ZiBH7MQppDJVLx6fhD618GKNG40CZYH9wgwdhKxBDDbQgjeWGGd4AtkZad84d291YxvWe7bJGuE65Anh0dxQ== + version "2.0.3" + resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.3.tgz#13cefcffa702dc67750314a3305e8cb3fad1d072" + integrity sha512-IDUSMXs6LOSJBWE++L0lzIbSqHl9KDCfff2x/JSEIDtEUavUnyMYC2ZGay/04Zq4UT8lvd4xNhU4/YHKibAOlw== dependencies: + call-bind "^1.0.0" define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" - function-bind "^1.1.1" + es-abstract "^1.18.0-next.1" has "^1.0.3" object.getownpropertydescriptors@^2.0.3, object.getownpropertydescriptors@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz#369bf1f9592d8ab89d712dced5cb81c7c5352649" - integrity sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg== + version "2.1.1" + resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.1.tgz#0dfda8d108074d9c563e80490c883b6661091544" + integrity sha512-6DtXgZ/lIZ9hqx4GtZETobXLR/ZLaa0aqV0kzbn80Rf8Z2e/XFnhA0I7p07N2wH8bBBltr2xQPi6sbKWAY2Eng== dependencies: + call-bind "^1.0.0" define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" + es-abstract "^1.18.0-next.1" object.pick@^1.3.0: version "1.3.0" @@ -5014,13 +5029,13 @@ object.pick@^1.3.0: isobject "^3.0.1" object.values@^1.1.0, object.values@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.1.tgz#68a99ecde356b7e9295a3c5e0ce31dc8c953de5e" - integrity sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA== + version "1.1.2" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.2.tgz#7a2015e06fcb0f546bd652486ce8583a4731c731" + integrity sha512-MYC0jvJopr8EK6dPBiO8Nb9mvjdypOachO5REGk6MXzujbBrAisKo3HmdEI6kZDL6fC31Mwee/5YbtMebixeag== dependencies: + call-bind "^1.0.0" define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" - function-bind "^1.1.1" + es-abstract "^1.18.0-next.1" has "^1.0.3" obuf@^1.0.0, obuf@^1.1.2: @@ -5028,11 +5043,6 @@ obuf@^1.0.0, obuf@^1.1.2: resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== -omit.js@^2.0.0, omit.js@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/omit.js/-/omit.js-2.0.2.tgz#dd9b8436fab947a5f3ff214cb2538631e313ec2f" - integrity sha512-hJmu9D+bNB40YpL9jYebQl4lsTW6yEHRTroJzNLqQJYHm7c+NQnJGfZmIWh8S3q3KoaxV1aLhV6B3+0N0/kyJg== - on-finished@~2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" @@ -5052,18 +5062,13 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0: dependencies: wrappy "1" -onetime@^5.1.0: +onetime@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== dependencies: mimic-fn "^2.1.0" -opencollective-postinstall@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz#7a0fff978f6dbfa4d006238fbac98ed4198c3259" - integrity sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q== - opn@^5.5.0: version "5.5.0" resolved "https://registry.yarnpkg.com/opn/-/opn-5.5.0.tgz#fc7164fab56d235904c51c3b27da6758ca3b9bfc" @@ -5117,12 +5122,12 @@ p-limit@^2.0.0, p-limit@^2.2.0: dependencies: p-try "^2.0.0" -p-limit@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.0.2.tgz#1664e010af3cadc681baafd3e2a437be7b0fb5fe" - integrity sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg== +p-limit@^3.0.2, p-limit@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== dependencies: - p-try "^2.0.0" + yocto-queue "^0.1.0" p-locate@^2.0.0: version "2.0.0" @@ -5182,12 +5187,12 @@ p-try@^2.0.0: integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== param-case@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/param-case/-/param-case-3.0.3.tgz#4be41f8399eff621c56eebb829a5e451d9801238" - integrity sha512-VWBVyimc1+QrzappRs7waeN2YmoZFCGXWASRYX1/rGHtXqEcrGEIDm+jqIwFa2fRXNgQEwrxaYuIrX0WcAguTA== + version "3.0.4" + resolved "https://registry.yarnpkg.com/param-case/-/param-case-3.0.4.tgz#7d17fe4aa12bde34d4a77d91acfb6219caad01c5" + integrity sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A== dependencies: - dot-case "^3.0.3" - tslib "^1.10.0" + dot-case "^3.0.4" + tslib "^2.0.3" parent-module@^1.0.0: version "1.0.1" @@ -5224,9 +5229,9 @@ parse-json@^4.0.0: json-parse-better-errors "^1.0.1" parse-json@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.1.0.tgz#f96088cdf24a8faa9aea9a009f2d9d942c999646" - integrity sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ== + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== dependencies: "@babel/code-frame" "^7.0.0" error-ex "^1.3.1" @@ -5238,13 +5243,13 @@ parseurl@~1.3.2, parseurl@~1.3.3: resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== -pascal-case@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/pascal-case/-/pascal-case-3.1.1.tgz#5ac1975133ed619281e88920973d2cd1f279de5f" - integrity sha512-XIeHKqIrsquVTQL2crjq3NfJUxmdLasn3TYOU0VBM+UX2a6ztAWBlJQBePLGY7VHW8+2dRadeIPK5+KImwTxQA== +pascal-case@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/pascal-case/-/pascal-case-3.1.2.tgz#b48e0ef2b98e205e7c1dae747d0b1508237660eb" + integrity sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g== dependencies: - no-case "^3.0.3" - tslib "^1.10.0" + no-case "^3.0.4" + tslib "^2.0.3" pascalcase@^0.1.1: version "0.1.1" @@ -5375,13 +5380,6 @@ pkg-dir@^5.0.0: dependencies: find-up "^5.0.0" -please-upgrade-node@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz#aeddd3f994c933e4ad98b99d9a556efa0e2fe942" - integrity sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg== - dependencies: - semver-compare "^1.0.0" - portfinder@^1.0.26: version "1.0.28" resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.28.tgz#67c4622852bd5374dd1dd900f779f53462fac778" @@ -5574,9 +5572,9 @@ postcss-focus-within@^3.0.0: postcss "^7.0.2" postcss-font-variant@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/postcss-font-variant/-/postcss-font-variant-4.0.0.tgz#71dd3c6c10a0d846c5eda07803439617bbbabacc" - integrity sha512-M8BFYKOvCrI2aITzDad7kWuXXTm0YhGdP9Q8HanmN4EF1Hmcgs1KK5rSHylt/lUJe8yLxiSwWAHdScoEiIxztg== + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-font-variant/-/postcss-font-variant-4.0.1.tgz#42d4c0ab30894f60f98b17561eb5c0321f502641" + integrity sha512-I3ADQSTNtLTTd8uxZhtSOrTCQ9G4qUVKPjHiDk0bV75QSxXjVWiJVJ2VLdspGUi9fbW9BcjKJoRvxAH1pckqmA== dependencies: postcss "^7.0.2" @@ -5630,13 +5628,13 @@ postcss-inline-svg@^4.1.0: postcss "^7.0.17" postcss-value-parser "^4.0.0" -postcss-js@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/postcss-js/-/postcss-js-3.0.1.tgz#e467efdce80ca02e072c60b42e0b35ad2f950a94" - integrity sha512-m1DgECmEbOK9JhGkdctaP9ZRVheJuEnkk2eb/d3K+5uN10C3S004Ng6Hat4Aha7PsLt824x0xwrT7rVwGRVLHg== +postcss-js@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/postcss-js/-/postcss-js-3.0.3.tgz#2f0bd370a2e8599d45439f6970403b5873abda33" + integrity sha512-gWnoWQXKFw65Hk/mi2+WTQTHdPD5UJdDXZmX073EY/B3BWnYjO4F4t0VneTCnCGQ5E5GsCdMkzPaTXwl3r5dJw== dependencies: camelcase-css "^2.0.1" - postcss "^8.1.0" + postcss "^8.1.6" postcss-lab-function@^2.0.1: version "2.0.1" @@ -5655,15 +5653,15 @@ postcss-less@^3.1.4: postcss "^7.0.14" postcss-loader@^4.0.4: - version "4.0.4" - resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-4.0.4.tgz#b2d005b52e008a44991cf8123bee207e635eb53e" - integrity sha512-pntA9zIR14drQo84yGTjQJg1m7T0DkXR4vXYHBngiRZdJtEeCrojL6lOpqUanMzG375lIJbT4Yug85zC/AJWGw== + version "4.2.0" + resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-4.2.0.tgz#f6993ea3e0f46600fb3ee49bbd010448123a7db4" + integrity sha512-mqgScxHqbiz1yxbnNcPdKYo/6aVt+XExURmEbQlviFVWogDbM4AJ0A/B+ZBpYsJrTRxKw7HyRazg9x0Q9SWwLA== dependencies: cosmiconfig "^7.0.0" klona "^2.0.4" loader-utils "^2.0.0" schema-utils "^3.0.0" - semver "^7.3.2" + semver "^7.3.4" postcss-logical@^3.0.0: version "3.0.0" @@ -5747,14 +5745,14 @@ postcss-minify-selectors@^4.0.2: postcss-selector-parser "^3.0.0" postcss-mixins@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/postcss-mixins/-/postcss-mixins-7.0.1.tgz#f46a18abef1eea727b4f0b6198aada1cae20cafc" - integrity sha512-9VLSce+dkPYmeD7/MmPh5HTQch1HEO4avtkrT8x0t8F8TOd+gfUMkJXS82Kx1qV4c2pkMdhq4M5aEnZHYRJWiA== + version "7.0.2" + resolved "https://registry.yarnpkg.com/postcss-mixins/-/postcss-mixins-7.0.2.tgz#9c5b9db70aa74095402062b3cf38431259b58971" + integrity sha512-er22AZ/2C1P+jZOL4ZExVEz0XuPWJWWy7SySbb2HWFmAgXG8F4qaOmMaRTdLfSK1AbgWxRT+KQ8GQ2i5kOn1aw== dependencies: globby "^11.0.1" - postcss-js "^3.0.1" + postcss-js "^3.0.3" postcss-simple-vars "^6.0.1" - sugarss "^3.0.1" + sugarss "^3.0.3" postcss-modules-extract-imports@^2.0.0: version "2.0.0" @@ -5833,9 +5831,9 @@ postcss-modules@^3.2.2: string-hash "^1.1.1" postcss-nested@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-5.0.1.tgz#e7a77f7a806a09c8de0f2c163d8e3d09f00f3139" - integrity sha512-ZHNSAoHrMtbEzjq+Qs4R0gHijpXc6F1YUv4TGmGaz7rtfMvVJBbu5hMOH+CrhEaljQpEmx5N/P8i1pXTkbVAmg== + version "5.0.3" + resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-5.0.3.tgz#2f46d77a06fc98d9c22344fd097396f5431386db" + integrity sha512-R2LHPw+u5hFfDgJG748KpGbJyTv7Yr33/2tIMWxquYuHTd9EXu27PYnKi7BxMXLtzKC0a0WVsqHtd7qIluQu/g== dependencies: postcss-selector-parser "^6.0.4" @@ -6037,9 +6035,9 @@ postcss-replace-overflow-wrap@^3.0.0: postcss "^7.0.2" postcss-reporter@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/postcss-reporter/-/postcss-reporter-7.0.1.tgz#463780d0d8d64f356403eba901fdeae71d988f2b" - integrity sha512-R9AK80KIqqMb+lwGRBcRkXS7r96VCTxrZvvrfibyA/dWjqctwx7leHMCC05A9HbW8PnChwOWwrmISwp5HQu5wg== + version "7.0.2" + resolved "https://registry.yarnpkg.com/postcss-reporter/-/postcss-reporter-7.0.2.tgz#03e9e7381c1afe40646f9c22e7aeeb860e051065" + integrity sha512-JyQ96NTQQsso42y6L1H1RqHfWH1C3Jr0pt91mVv5IdYddZAE9DUZxuferNgk6q0o6vBVOrfVJb10X1FgDzjmDw== dependencies: colorette "^1.2.1" lodash.difference "^4.5.0" @@ -6047,7 +6045,6 @@ postcss-reporter@^7.0.1: lodash.get "^4.4.2" lodash.groupby "^4.6.0" lodash.sortby "^4.7.0" - log-symbols "^4.0.0" postcss-resolve-nested-selector@^0.1.1: version "0.1.1" @@ -6085,9 +6082,9 @@ postcss-selector-matches@^4.0.0: postcss "^7.0.2" postcss-selector-not@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/postcss-selector-not/-/postcss-selector-not-4.0.0.tgz#c68ff7ba96527499e832724a2674d65603b645c0" - integrity sha512-W+bkBZRhqJaYN8XAnbbZPLWMvZD1wKTu0UxtFKdhtGjWYmxhkUneoeOhRJKdAE5V7ZTlnbHfCR+6bNwK9e1dTQ== + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-selector-not/-/postcss-selector-not-4.0.1.tgz#263016eef1cf219e0ade9a913780fc1f48204cbf" + integrity sha512-YolvBgInEK5/79C+bdFMyzqTg6pkYqDbzZIST/PDMqa/o3qtXenD05apBG2jLgT0/BQ77d4U2UK12jWpilqMAQ== dependencies: balanced-match "^1.0.0" postcss "^7.0.2" @@ -6121,9 +6118,9 @@ postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2, postcss-selector util-deprecate "^1.0.2" postcss-simple-vars@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/postcss-simple-vars/-/postcss-simple-vars-6.0.1.tgz#7733415a7b57f5cb9b5a15cdd6b0546c505e7ad6" - integrity sha512-P1M+APrquBXOPKtk/LlWptuPBwTCjMgxZxv1lkpjG9RdOx8ewi8USOclPCt75xG7UT7viDUB3p3qy2ffPBefjQ== + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-simple-vars/-/postcss-simple-vars-6.0.2.tgz#cc0cc7745fb53c83245df99aaf4452475b454eda" + integrity sha512-tABK0OVtCa88TgJjgosa/1aLgiF4hTJBJxUjVE5idTGA0597OVdzWZvbySAF+HKo/sZidxSSpnWhUycDrxO8LA== postcss-svgo@^4.0.2: version "4.0.2" @@ -6149,7 +6146,7 @@ postcss-unique-selectors@^4.0.1: postcss "^7.0.0" uniqs "^2.0.0" -postcss-value-parser@^3.0.0: +postcss-value-parser@^3.0.0, postcss-value-parser@^3.3.0: version "3.3.1" resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281" integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ== @@ -6184,7 +6181,7 @@ postcss@^6.0.8, postcss@^6.0.9: source-map "^0.6.1" supports-color "^5.4.0" -postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.17, postcss@^7.0.2, postcss@^7.0.21, postcss@^7.0.26, postcss@^7.0.27, postcss@^7.0.32, postcss@^7.0.5, postcss@^7.0.6: +postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.17, postcss@^7.0.2, postcss@^7.0.21, postcss@^7.0.26, postcss@^7.0.27, postcss@^7.0.32, postcss@^7.0.35, postcss@^7.0.5, postcss@^7.0.6: version "7.0.35" resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.35.tgz#d2be00b998f7f211d8a276974079f2e92b970e24" integrity sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg== @@ -6193,14 +6190,13 @@ postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.17, postcss@^7.0.2 source-map "^0.6.1" supports-color "^6.1.0" -postcss@^8.1.0, postcss@^8.1.1, postcss@^8.1.2: - version "8.1.2" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.1.2.tgz#9731fcaa4f7b0bef47121821bdae9eeb609a324c" - integrity sha512-mToqEVFq8jF9TFhlIK4HhE34zknFJuNTgqtsr60vUvrWn+9TIYugCwiV1JZRxCuOrej2jjstun1bn4Bc7/1HkA== +postcss@^8.1.2, postcss@^8.1.4, postcss@^8.1.6: + version "8.2.4" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.2.4.tgz#20a98a39cf303d15129c2865a9ec37eda0031d04" + integrity sha512-kRFftRoExRVXZlwUuay9iC824qmXPcQQVzAjbCCgjpXnkdMCJYBu2gTwAaFBzv8ewND6O8xFb3aELmEkh9zTzg== dependencies: colorette "^1.2.1" - line-column "^1.0.2" - nanoid "^3.1.12" + nanoid "^3.1.20" source-map "^0.6.1" prelude-ls@^1.2.1: @@ -6231,7 +6227,7 @@ promise-inflight@^1.0.1: resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM= -prop-types@^15.5.10, prop-types@^15.6.2, prop-types@^15.7.2: +prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2: version "15.7.2" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== @@ -6282,9 +6278,9 @@ qs@6.7.0: integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== qs@^6.9.4: - version "6.9.4" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.4.tgz#9090b290d1f91728d3c22e54843ca44aea5ab687" - integrity sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ== + version "6.9.6" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.6.tgz#26ed3c8243a431b2924aca84cc90471f35d5a0ee" + integrity sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ== querystring@0.2.0: version "0.2.0" @@ -6301,7 +6297,12 @@ quick-lru@^4.0.1: resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f" integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g== -raf@^3.4.0, raf@^3.4.1: +raf-schd@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/raf-schd/-/raf-schd-4.0.2.tgz#bd44c708188f2e84c810bf55fcea9231bcaed8a0" + integrity sha512-VhlMZmGy6A6hrkJWHLNTGl5gtgMUm+xfGza6wbwnE914yeQ5Ybm18vgM734RZhMgfw4tacUrWseGZlpUrrakEQ== + +raf@^3.4.0: version "3.4.1" resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39" integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA== @@ -6331,9 +6332,9 @@ raw-body@2.4.0: unpipe "1.0.0" rc-align@^4.0.0: - version "4.0.8" - resolved "https://registry.yarnpkg.com/rc-align/-/rc-align-4.0.8.tgz#276c3f5dfadf0de4bb95392cb81568c9e947a668" - integrity sha512-2sRUkmB8z4UEXzaS+lDHzXMoR8HrtKH9nn2yHlHVNyUTnaucjMFbdEoCk+hO1g7cpIgW0MphG8i0EH2scSesfw== + version "4.0.9" + resolved "https://registry.yarnpkg.com/rc-align/-/rc-align-4.0.9.tgz#46d8801c4a139ff6a65ad1674e8efceac98f85f2" + integrity sha512-myAM2R4qoB6LqBul0leaqY8gFaiECDJ3MtQDmzDo9xM9NRT/04TvWOYd2YHU9zvGzqk9QXF6S9/MifzSKDZeMw== dependencies: "@babel/runtime" "^7.10.1" classnames "2.x" @@ -6341,63 +6342,54 @@ rc-align@^4.0.0: rc-util "^5.3.0" resize-observer-polyfill "^1.5.1" -rc-animate@3.x, rc-animate@~3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/rc-animate/-/rc-animate-3.1.1.tgz#defdd863f56816c222534e4dc68feddecd081386" - integrity sha512-8wg2Zg3EETy0k/9kYuis30NJNQg1D6/WSQwnCiz6SvyxQXNet/rVraRz3bPngwY6rcU2nlRvoShiYOorXyF7Sg== - dependencies: - "@ant-design/css-animation" "^1.7.2" - classnames "^2.2.6" - raf "^3.4.0" - rc-util "^4.15.3" - rc-cascader@~1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/rc-cascader/-/rc-cascader-1.4.0.tgz#d731ea8e07433558627941036091a2820e895474" - integrity sha512-6kgQljDQEKjVAVRkZtvvoi+2qv4u42M6oLuvt4ZDBa16r3X9ZN8TAq3atVyC840ivbGKlHT50OcdVx/iwiHc1w== + version "1.4.2" + resolved "https://registry.yarnpkg.com/rc-cascader/-/rc-cascader-1.4.2.tgz#caa81098e3ef4d5f823f9156f6d8d6dbd6321afa" + integrity sha512-JVuLGrSi+3G8DZyPvlKlGVWJjhoi9NTz6REHIgRspa5WnznRkKGm2ejb0jJtz0m2IL8Q9BG4ZA2sXuqAu71ltQ== dependencies: + "@babel/runtime" "^7.12.5" array-tree-filter "^2.1.0" rc-trigger "^5.0.4" rc-util "^5.0.1" warning "^4.0.1" rc-checkbox@~2.3.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/rc-checkbox/-/rc-checkbox-2.3.1.tgz#2a61bc43017c783bd2e9f1a67553bf8efe7aa4d3" - integrity sha512-i290/iTqmZ0WtI2UPIryqT9rW6O99+an4KeZIyZDH3r+Jbb6YdddaWNdzq7g5m9zaNhJvgjf//wJtC4fvve2Tg== + version "2.3.2" + resolved "https://registry.yarnpkg.com/rc-checkbox/-/rc-checkbox-2.3.2.tgz#f91b3678c7edb2baa8121c9483c664fa6f0aefc1" + integrity sha512-afVi1FYiGv1U0JlpNH/UaEXdh6WUJjcWokj/nUN2TgG80bfG+MDdbfHKlLcNNba94mbjy2/SXJ1HDgrOkXGAjg== dependencies: "@babel/runtime" "^7.10.1" classnames "^2.2.1" -rc-collapse@~2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/rc-collapse/-/rc-collapse-2.0.1.tgz#99e7655acd9c237b72369a39dcb5c713451e1e92" - integrity sha512-sRNqwQovzQoptTh7dCwj3kfxrdor2oNXrGSBz+QJxSFS7N3Ujgf8X/KlN2ElCkwBKf7nNv36t9dwH0HEku4wJg== +rc-collapse@~3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/rc-collapse/-/rc-collapse-3.1.0.tgz#4ce5e612568c5fbeaf368cc39214471c1461a1a1" + integrity sha512-EwpNPJcLe7b+5JfyaxM9ZNnkCgqArt3QQO0Cr5p5plwz/C9h8liAmjYY5I4+hl9lAjBqb7ZwLu94+z+rt5g1WQ== dependencies: - "@ant-design/css-animation" "^1.7.2" + "@babel/runtime" "^7.10.1" classnames "2.x" - rc-animate "3.x" + rc-motion "^2.3.4" rc-util "^5.2.1" shallowequal "^1.1.0" -rc-dialog@~8.4.0: - version "8.4.3" - resolved "https://registry.yarnpkg.com/rc-dialog/-/rc-dialog-8.4.3.tgz#de8650ce7d1fcb6c1f7e065b94a6894b9a5a54a4" - integrity sha512-LHsWXb+2Cy4vEOeJcPvk9M0WSr80Gi438ov5rXt3E6XB4j+53Z+vMFRr+TagnVuOVQRCLmmzT4qutfm2U1OK6w== +rc-dialog@~8.5.0, rc-dialog@~8.5.1: + version "8.5.1" + resolved "https://registry.yarnpkg.com/rc-dialog/-/rc-dialog-8.5.1.tgz#df316dd93e1685d7df1f5e4164ee35cba4a9af88" + integrity sha512-EcLgHHjF3Jp4C+TFceO2j7gIrpx0YIhY6ronki5QJDL/z+qWYozY5RNh4rnv4a6R21SPVhV+SK+gMMlMHZ/YRQ== dependencies: "@babel/runtime" "^7.10.1" classnames "^2.2.6" rc-motion "^2.3.0" - rc-util "^5.0.1" + rc-util "^5.6.1" -rc-drawer@~4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/rc-drawer/-/rc-drawer-4.1.0.tgz#d7bf0bc030300b62d282bc04e053b9acad6b08b4" - integrity sha512-kjeQFngPjdzAFahNIV0EvEBoIKMOnvUsAxpkSPELoD/1DuR4nLafom5ryma+TIxGwkFJ92W6yjsMi1U9aiOTeQ== +rc-drawer@~4.2.0: + version "4.2.2" + resolved "https://registry.yarnpkg.com/rc-drawer/-/rc-drawer-4.2.2.tgz#5fd8b18ce20575ff22b36e0c5ddbe363c13db555" + integrity sha512-zw48FATkAmJrEnfeRWiMqvKAzqGzUDLN1UXlluB7q7GgbR6mJFvc+QsmNrgxsFuMz86Lh9mKSIi7rXlPINmuzw== dependencies: "@babel/runtime" "^7.10.1" classnames "^2.2.6" - rc-util "^5.0.1" + rc-util "^5.7.0" rc-dropdown@^3.1.3, rc-dropdown@~3.2.0: version "3.2.0" @@ -6408,39 +6400,38 @@ rc-dropdown@^3.1.3, rc-dropdown@~3.2.0: classnames "^2.2.6" rc-trigger "^5.0.4" -rc-field-form@~1.12.0: - version "1.12.1" - resolved "https://registry.yarnpkg.com/rc-field-form/-/rc-field-form-1.12.1.tgz#e7f5dc5cc5a267420fd50e1e3ae3352e650c1425" - integrity sha512-c09NVEoGFtwqpTJH4Tw1D8UUitKrrTCW2UAFcJ57FHTg5zReozzgjrrv3UiKDVjbbFzikDLdYz3CzdWMlqVHXg== +rc-field-form@~1.18.0: + version "1.18.1" + resolved "https://registry.yarnpkg.com/rc-field-form/-/rc-field-form-1.18.1.tgz#41027816c80d1acf6f51db085d34c2c35213a701" + integrity sha512-/YRnelnHLxygl/ROGhFqfCT+uAZ5xLvu3qjtlETOneb7fXKk7tqp+RGfYqZ4uNViXlsfxox3qqMMTVet6wYfEA== dependencies: "@babel/runtime" "^7.8.4" async-validator "^3.0.3" rc-util "^5.0.0" -rc-image@~3.2.1: - version "3.2.2" - resolved "https://registry.yarnpkg.com/rc-image/-/rc-image-3.2.2.tgz#5d2b7d474dd01ea7af2cbc84fe6af3de8905b3fe" - integrity sha512-8D1pj4qTdC93IfeTPstGFBwpDRZPC565emm4VevrtyFoD9QHBF6kp9kOtzk0JAmbybLAQuX4GGNcwoc7tbZ9Zw== +rc-image@~5.1.1: + version "5.1.4" + resolved "https://registry.yarnpkg.com/rc-image/-/rc-image-5.1.4.tgz#e251101159db80c3e33276aaa4669a991c7486c0" + integrity sha512-hilxwwEAYJXocY6i+lEdEibvHVOLgN43EQFfjKFiiruRNiUQzGWcdCseyaeTZgInTDrf+QWZP6MujlZjtEbpkA== dependencies: - "@ant-design/icons" "^4.2.2" "@babel/runtime" "^7.11.2" classnames "^2.2.6" - rc-dialog "~8.4.0" + rc-dialog "~8.5.0" rc-util "^5.0.6" rc-input-number@~6.1.0: - version "6.1.1" - resolved "https://registry.yarnpkg.com/rc-input-number/-/rc-input-number-6.1.1.tgz#818c426942d1b4dc4d6d2639d741ca67773a9118" - integrity sha512-9t2xf1G0YEism7FAXAvF1huBk7ZNABPBf6NL+3/aDL123WiT/vhhod4cldiDWTM1Yb2EDKR//ZIa546ScdsUaA== + version "6.1.3" + resolved "https://registry.yarnpkg.com/rc-input-number/-/rc-input-number-6.1.3.tgz#d558be65793429807cc2cdc360af407599d80283" + integrity sha512-qCLWK9NuuKGTsPXjRU/XvSOX7EKdnHlOpg59nPjYSDdH/czsAHZyYq50O6b6RF2TMPOjVpmsZQoMjNJYcnn6JA== dependencies: "@babel/runtime" "^7.10.1" classnames "^2.2.5" rc-util "^5.0.1" rc-mentions@~1.5.0: - version "1.5.2" - resolved "https://registry.yarnpkg.com/rc-mentions/-/rc-mentions-1.5.2.tgz#94559a369de73e7cc92f343badaf94499fb410a7" - integrity sha512-GqV0tOtHY3pLpOsFCxJ2i6Ad8AVfxFmz0NlD/8rb8IG8pMpthJKcdfnXlNZRx3Fa9O4YEgJpdSY1WEbmlx2DWQ== + version "1.5.3" + resolved "https://registry.yarnpkg.com/rc-mentions/-/rc-mentions-1.5.3.tgz#b92bebadf8ad9fb3586ba1af922d63b49d991c67" + integrity sha512-NG/KB8YiKBCJPHHvr/QapAb4f9YzLJn7kDHtmI1K6t7ZMM5YgrjIxNNhoRKKP9zJvb9PdPts69Hbg4ZMvLVIFQ== dependencies: "@babel/runtime" "^7.10.1" classnames "^2.2.6" @@ -6449,52 +6440,61 @@ rc-mentions@~1.5.0: rc-trigger "^5.0.4" rc-util "^5.0.1" -rc-menu@^8.0.1, rc-menu@^8.6.1, rc-menu@~8.8.2: - version "8.8.3" - resolved "https://registry.yarnpkg.com/rc-menu/-/rc-menu-8.8.3.tgz#feb8ba0371dd342fbf1052d4fcca7b669b0bf66a" - integrity sha512-C9sT0SBXmUbVWRUseXASousacRVPnOm5aXdyJR569WIvZwbs2IncpGNmAcft1R5ZuFE3Y+SZZ5FYvtGtbCzkIQ== +rc-menu@^8.0.1, rc-menu@^8.6.1, rc-menu@~8.10.0: + version "8.10.5" + resolved "https://registry.yarnpkg.com/rc-menu/-/rc-menu-8.10.5.tgz#44b7381c650cc76020dfd65753b535f415012179" + integrity sha512-8Ets93wQFy9IysmgRUm1VGdrEz6XfZTM0jQOqOPLYNXah5HgAmCh4xT0UOygfHB3IWiQeqDgr2uPB4uVhwI2+Q== dependencies: "@babel/runtime" "^7.10.1" classnames "2.x" mini-store "^3.0.1" - omit.js "^2.0.0" rc-motion "^2.0.1" - rc-trigger "^5.0.4" - rc-util "^5.0.1" + rc-trigger "^5.1.2" + rc-util "^5.7.0" resize-observer-polyfill "^1.5.0" shallowequal "^1.1.0" -rc-motion@^2.0.0, rc-motion@^2.0.1, rc-motion@^2.2.0, rc-motion@^2.3.0: - version "2.3.3" - resolved "https://registry.yarnpkg.com/rc-motion/-/rc-motion-2.3.3.tgz#308fe27f2d2b4d6e951297740294fff32d13c15b" - integrity sha512-eOpPDFz6Y+gX1Nd3/AZOhS+Cqv9CiyJ+hrfAinfemJv+fiiVLv/NkFYe2fqw0onNeGiTKJaDF5Ah4Hm006K5yw== +rc-motion@^2.0.0, rc-motion@^2.0.1, rc-motion@^2.2.0, rc-motion@^2.3.0, rc-motion@^2.3.4, rc-motion@^2.4.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/rc-motion/-/rc-motion-2.4.1.tgz#323f47c8635e6b2bc0cba2dfad25fc415b58e1dc" + integrity sha512-TWLvymfMu8SngPx5MDH8dQ0D2RYbluNTfam4hY/dNNx9RQ3WtGuZ/GXHi2ymLMzH+UNd6EEFYkOuR5JTTtm8Xg== dependencies: "@babel/runtime" "^7.11.1" classnames "^2.2.1" rc-util "^5.2.1" rc-notification@~4.5.2: - version "4.5.2" - resolved "https://registry.yarnpkg.com/rc-notification/-/rc-notification-4.5.2.tgz#7836fc04068e00a764fca944074101faa144d503" - integrity sha512-rIgQip4BzUbHpDXDdNc2EFgIh1gxI97UjUbhU8hzdsjytBVstIEHXH36EgHTGllMkOhL9PkQOByg+mgV+I60ZQ== + version "4.5.4" + resolved "https://registry.yarnpkg.com/rc-notification/-/rc-notification-4.5.4.tgz#1292e163003db4b9162c856a4630e5d0f1359356" + integrity sha512-VsN0ouF4uglE5g3C9oDsXLNYX0Sz++ZNUFYCswkxhpImYJ9u6nJOpyA71uOYDVCu6bAF54Y5Hi/b+EcnMzkepg== dependencies: "@babel/runtime" "^7.10.1" classnames "2.x" rc-motion "^2.2.0" rc-util "^5.0.1" -rc-pagination@~3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/rc-pagination/-/rc-pagination-3.1.1.tgz#6a3b28f871181b7fad298a15a91f1de6f6e6304a" - integrity sha512-8chFRHXRXRhdPO2Tlmm4hnA/1FnO2hYSoop6FaukQ9/IaCugsVVcXo7OqZ03YJY+aWQLtS6tDsb+sAapCCmljw== +rc-overflow@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/rc-overflow/-/rc-overflow-1.0.2.tgz#f56bcd920029979989f576d55084b81f9632c19c" + integrity sha512-GXj4DAyNxm4f57LvXLwhJaZoJHzSge2l2lQq64MZP7NJAfLpQqOLD+v9JMV9ONTvDPZe8kdzR+UMmkAn7qlzFA== + dependencies: + "@babel/runtime" "^7.11.1" + classnames "^2.2.1" + rc-resize-observer "^1.0.0" + rc-util "^5.5.1" + +rc-pagination@~3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/rc-pagination/-/rc-pagination-3.1.3.tgz#afd779839fefab2cb14248d5e7b74027960bb48b" + integrity sha512-Z7CdC4xGkedfAwcUHPtfqNhYwVyDgkmhkvfsmoByCOwAd89p42t5O5T3ORar1wRmVWf3jxk/Bf4k0atenNvlFA== dependencies: "@babel/runtime" "^7.10.1" classnames "^2.2.1" -rc-picker@~2.3.0: - version "2.3.3" - resolved "https://registry.yarnpkg.com/rc-picker/-/rc-picker-2.3.3.tgz#c58c4270891f92aad02d0404d1699285f9321fd9" - integrity sha512-ah4ucCnAs8ss7GgV7sF7MGgRlyfP4753z+OjnF4X7cIrntygklQqiFDBZYS02RX773vhJ+jc6AbyoR7hI4aGng== +rc-picker@~2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/rc-picker/-/rc-picker-2.5.2.tgz#36d91b8cdddbf8b2474af29c2853b77502a7fb01" + integrity sha512-rQLgvjyFrxjiWlR+Q7CyXdTOP/gHbiXlBca7irOtuEb6HMRLdm+/OfIB7xaaPHgdkv1ZOsxCk8zCEX6j0qf24g== dependencies: "@babel/runtime" "^7.10.1" classnames "^2.2.1" @@ -6506,49 +6506,49 @@ rc-picker@~2.3.0: shallowequal "^1.1.0" rc-progress@~3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/rc-progress/-/rc-progress-3.1.1.tgz#f7ba5eb33cc438862513ab5a71739feaf6b3ef4f" - integrity sha512-1ns3pW7ll9bHfdXtlVLF+vngdvlxiCDtiqwXnZFEdurst11JTiPxVdeqnCNbhWx5hP4kCKkAPqG1N0FVfTSUGA== + version "3.1.3" + resolved "https://registry.yarnpkg.com/rc-progress/-/rc-progress-3.1.3.tgz#d77d8fd26d9d948d72c2a28b64b71a6e86df2426" + integrity sha512-Jl4fzbBExHYMoC6HBPzel0a9VmhcSXx24LVt/mdhDM90MuzoMCJjXZAlhA0V0CJi+SKjMhfBoIQ6Lla1nD4QNw== dependencies: "@babel/runtime" "^7.10.1" classnames "^2.2.6" -rc-rate@~2.8.2: - version "2.8.2" - resolved "https://registry.yarnpkg.com/rc-rate/-/rc-rate-2.8.2.tgz#d82d237d74fd4aef3e0581d2700b646cdd1cd8a2" - integrity sha512-f9T/D+ZwWQrWHkpidpQbnXpnVMGMC4eSRAkwuu88a8Qv1C/9LNc4AErazoh8tpnZBFqq19F3j0Glv+sDgkfEig== +rc-rate@~2.9.0: + version "2.9.1" + resolved "https://registry.yarnpkg.com/rc-rate/-/rc-rate-2.9.1.tgz#e43cb95c4eb90a2c1e0b16ec6614d8c43530a731" + integrity sha512-MmIU7FT8W4LYRRHJD1sgG366qKtSaKb67D0/vVvJYR0lrCuRrCiVQ5qhfT5ghVO4wuVIORGpZs7ZKaYu+KMUzA== dependencies: "@babel/runtime" "^7.10.1" classnames "^2.2.5" rc-util "^5.0.1" -rc-resize-observer@^0.2.0, rc-resize-observer@^0.2.1, rc-resize-observer@^0.2.3: - version "0.2.6" - resolved "https://registry.yarnpkg.com/rc-resize-observer/-/rc-resize-observer-0.2.6.tgz#c1b642f6d1293e34c4e3715f47f69443a167b825" - integrity sha512-YX6nYnd6fk7zbuvT6oSDMKiZjyngjHoy+fz+vL3Tez38d/G5iGdaDJa2yE7345G6sc4Mm1IGRUIwclvltddhmA== +rc-resize-observer@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/rc-resize-observer/-/rc-resize-observer-1.0.0.tgz#97fb89856f62fec32ab6e40933935cf58e2e102d" + integrity sha512-RgKGukg1mlzyGdvzF7o/LGFC8AeoMH9aGzXTUdp6m+OApvmRdUuOscq/Y2O45cJA+rXt1ApWlpFoOIioXL3AGg== dependencies: "@babel/runtime" "^7.10.1" classnames "^2.2.1" rc-util "^5.0.0" resize-observer-polyfill "^1.5.1" -rc-select@^11.1.1, rc-select@~11.4.0: - version "11.4.2" - resolved "https://registry.yarnpkg.com/rc-select/-/rc-select-11.4.2.tgz#5b431ee7b2cc6e439886ca855774fc116e6fe6fb" - integrity sha512-DQHYwMcvAajnnlahKkYIW47AVTXgxpGj9CWbe+juXgvxawQRFUdd8T8L2Q05aOkMy02UTG0Qrs7EZfHmn5QHbA== +rc-select@^12.0.0, rc-select@~12.1.0: + version "12.1.2" + resolved "https://registry.yarnpkg.com/rc-select/-/rc-select-12.1.2.tgz#7f5f06838b8a8c86516acb66ead26adbc55fbc16" + integrity sha512-WEcqj4ljz5kgp/yPN4RDQEZRvjGkwdk1PugpFrtd6tY+YqwKZs7vSZt6xphVIvWlmtwmZMe7e9G1U8XykUN0+g== dependencies: "@babel/runtime" "^7.10.1" classnames "2.x" rc-motion "^2.0.1" + rc-overflow "^1.0.0" rc-trigger "^5.0.4" rc-util "^5.0.1" rc-virtual-list "^3.2.0" - warning "^4.0.3" -rc-slider@~9.5.2: - version "9.5.4" - resolved "https://registry.yarnpkg.com/rc-slider/-/rc-slider-9.5.4.tgz#4bbb1c7810037adad030c82a1e47e1b331405449" - integrity sha512-24goJnWhmWi0ojNZMoPSMni2wh73IPqEK0TJh7rWn10hPLLKgG8x3KRR0g4uUdCS9APHyosqxGXUIJKGydJXVg== +rc-slider@~9.7.1: + version "9.7.1" + resolved "https://registry.yarnpkg.com/rc-slider/-/rc-slider-9.7.1.tgz#63535177a74a3ee44f090909e8c6f98426eb9dba" + integrity sha512-r9r0dpFA3PEvxBhIfVi1lVzxuSogWxeY+tGvi2AqMM1rPgaOXQ7WbtT+9kVFkJ9K8TntA/vYPgiCCKfN29KTkw== dependencies: "@babel/runtime" "^7.10.1" classnames "^2.2.5" @@ -6574,63 +6574,62 @@ rc-switch@~3.2.0: classnames "^2.2.1" rc-util "^5.0.1" -rc-table@~7.10.0: - version "7.10.3" - resolved "https://registry.yarnpkg.com/rc-table/-/rc-table-7.10.3.tgz#f667e3d7d8a00c490beb83086ed9f3a04df6850f" - integrity sha512-iX96RaERJiTsmO8wljxjCHhsPMTge/0BB1dHS4I+5xegr+bud8a2KV4mX3rYcrnVjYueTqmtXH2K6EQYNhpOGw== +rc-table@~7.12.0: + version "7.12.3" + resolved "https://registry.yarnpkg.com/rc-table/-/rc-table-7.12.3.tgz#c86e93b40dc3e2b8aae9e54ad7aa5a64ff7d261e" + integrity sha512-R87lx28os4Ftm/9x+MamwzLw+Dtp9yxihceAgLA/9JS9QP/sqej1qlO+3T/Uw4paZqCg9SpW0XzLI1MandviWA== dependencies: "@babel/runtime" "^7.10.1" classnames "^2.2.5" - rc-resize-observer "^0.2.0" + rc-resize-observer "^1.0.0" rc-util "^5.4.0" shallowequal "^1.1.0" rc-tabs@~11.7.0: - version "11.7.0" - resolved "https://registry.yarnpkg.com/rc-tabs/-/rc-tabs-11.7.0.tgz#e03a03fbf5b8d04a1c9f12f24a621b1d7ff6a093" - integrity sha512-nYwQcgML2drM0iau4aa6HI4qyyZSW0WpspCAtO5KGjXwHzUJcvv3qgLVuoQOWQaDDHXkI9Jj8U7Y/Hcrdyj1Kw== + version "11.7.3" + resolved "https://registry.yarnpkg.com/rc-tabs/-/rc-tabs-11.7.3.tgz#32a30e59c6992d60fb58115ba0bf2652b337ed43" + integrity sha512-5nd2NVss9TprPRV9r8N05SjQyAE7zDrLejxFLcbJ+BdLxSwnGnk3ws/Iq0smqKZUnPQC0XEvnpF3+zlllUUT2w== dependencies: "@babel/runtime" "^7.11.2" classnames "2.x" - raf "^3.4.1" rc-dropdown "^3.1.3" rc-menu "^8.6.1" - rc-resize-observer "^0.2.1" - rc-util "^5.0.0" + rc-resize-observer "^1.0.0" + rc-util "^5.5.0" rc-textarea@^0.3.0, rc-textarea@~0.3.0: - version "0.3.1" - resolved "https://registry.yarnpkg.com/rc-textarea/-/rc-textarea-0.3.1.tgz#e81e1ef6f0e29729f9f80bb266cf6769dd7ab69a" - integrity sha512-bO5Ol5uD6A++aWI6BJ0Pa/8OZcGeacP9LxIGkUqkCwPyOG3kaLOsWb8ya4xCfrsC2P4vDTsHsJmmmG5wuXGFRg== + version "0.3.4" + resolved "https://registry.yarnpkg.com/rc-textarea/-/rc-textarea-0.3.4.tgz#1408a64c87b5e76db5c847699ef9ab5ee97dd6f9" + integrity sha512-ILUYx831ZukQPv3m7R4RGRtVVWmL1LV4ME03L22mvT56US0DGCJJaRTHs4vmpcSjFHItph5OTmhodY4BOwy81A== dependencies: "@babel/runtime" "^7.10.1" classnames "^2.2.1" - omit.js "^2.0.0" - rc-resize-observer "^0.2.3" + rc-resize-observer "^1.0.0" + rc-util "^5.7.0" rc-tooltip@^5.0.1, rc-tooltip@~5.0.0: - version "5.0.1" - resolved "https://registry.yarnpkg.com/rc-tooltip/-/rc-tooltip-5.0.1.tgz#b82c4259604d2cb62ca610ed7932dd37fc6ef61d" - integrity sha512-3AnxhUS0j74xAV3khrKw8o6rg+Ima3nw09DJBezMPnX3ImQUAnayWsPSlN1mEnihjA43rcFkGM1emiKE+CXyMQ== + version "5.0.2" + resolved "https://registry.yarnpkg.com/rc-tooltip/-/rc-tooltip-5.0.2.tgz#e48258fc9931bd9281102b2d9eacc5b986cf3258" + integrity sha512-A4FejSG56PzYtSNUU4H1pVzfhtkV/+qMT2clK0CsSj+9mbc4USEtpWeX6A/jjVL+goBOMKj8qlH7BCZmZWh/Nw== dependencies: "@babel/runtime" "^7.11.2" rc-trigger "^5.0.0" -rc-tree-select@~4.1.1: - version "4.1.2" - resolved "https://registry.yarnpkg.com/rc-tree-select/-/rc-tree-select-4.1.2.tgz#bf012c3c32cf2e82fc7ffbdd60cb596163a290a0" - integrity sha512-2tRwZ4ChY+BarVKHoPR65kSZtopgwKCig6ngJiiTVgYfRdAhfdQp2j2+L8YW9TkosYGmwgTOhmlphlG3QNy7Pg== +rc-tree-select@~4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/rc-tree-select/-/rc-tree-select-4.3.0.tgz#714a4fe658aa73f2a7b0aa4bd6e43be63194a6ce" + integrity sha512-EEXB9dKBsJNJuKIU5NERZsaJ71GDGIj5uWLl7A4XiYr2jXM4JICfScvvp3O5jHMDfhqmgpqNc0z90mHkgh3hKg== dependencies: "@babel/runtime" "^7.10.1" classnames "2.x" - rc-select "^11.1.1" - rc-tree "^3.8.0" + rc-select "^12.0.0" + rc-tree "^4.0.0" rc-util "^5.0.5" -rc-tree@^3.8.0, rc-tree@~3.10.0: - version "3.10.0" - resolved "https://registry.yarnpkg.com/rc-tree/-/rc-tree-3.10.0.tgz#897498b3756f6c84f41ad2b244ee9489abf43b7f" - integrity sha512-kf7J/f2E2T8Kfta3/1BIg65AzTmXOgOjn0KOpvD3KI/gqkfKMRKUS1ybkxW39JUPpKwdeOHFnYH+nFFMq7tkfg== +rc-tree@^4.0.0, rc-tree@~4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/rc-tree/-/rc-tree-4.1.1.tgz#d40f418b31b75e61886e3969481df1444232c98b" + integrity sha512-ufq7CkWfvTQa+xMPzEWYfOjTfsEALlPr0/IyujEG4+4d8NdaR3e+0dc8LkkVWoe1VCcXV2FQqAsgr2z/ThFUrQ== dependencies: "@babel/runtime" "^7.10.1" classnames "2.x" @@ -6638,62 +6637,52 @@ rc-tree@^3.8.0, rc-tree@~3.10.0: rc-util "^5.0.0" rc-virtual-list "^3.0.1" -rc-trigger@^5.0.0, rc-trigger@^5.0.4, rc-trigger@~5.0.3: - version "5.0.7" - resolved "https://registry.yarnpkg.com/rc-trigger/-/rc-trigger-5.0.7.tgz#c2f6e988756c76f84b63ff1071e02affc56495ec" - integrity sha512-4QzwHL0IaXmSZnMfJV45dR3Cy4XgsQy2m0LySBAFiZYaH5EN3qnq2lOtg5aU4T36g4146fHpfGa7mtJpCgkwhg== +rc-trigger@^5.0.0, rc-trigger@^5.0.4, rc-trigger@^5.1.2, rc-trigger@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/rc-trigger/-/rc-trigger-5.2.1.tgz#54686220b884ed1e0750c4f2411fbb34d4928c99" + integrity sha512-XZilSlSDnb0L/R3Ff2xo9C0Fho2aBDoAn8u3coM60XdLqTCo24nsOh1bfAMm0uIB1qVjh5eqeyFqnBPmXi8pJg== dependencies: "@babel/runtime" "^7.11.2" classnames "^2.2.6" rc-align "^4.0.0" rc-motion "^2.0.0" - rc-util "^5.3.4" + rc-util "^5.5.0" -rc-upload@~3.3.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/rc-upload/-/rc-upload-3.3.1.tgz#ad8658b2a796031930b35d2b07ab312b7cd4c9ed" - integrity sha512-KWkJbVM9BwU8qi/2jZwmZpAcdRzDkuyfn/yAOLu+nm47dyd6//MtxzQD3XZDFkC6jQ6D5FmlKn6DhmOfV3v43w== +rc-upload@~3.3.4: + version "3.3.4" + resolved "https://registry.yarnpkg.com/rc-upload/-/rc-upload-3.3.4.tgz#b0668d18661595c69c0621cec220fd116cc79952" + integrity sha512-v2sirR4JL31UTHD/f0LGUdd+tpFaOVUTPeIEjAXRP9kRN8TFhqOgcXl5ixtyqj90FmtRUmKmafCv0EmhBQUHqQ== dependencies: "@babel/runtime" "^7.10.1" classnames "^2.2.5" rc-util "^5.2.0" -rc-util@^4.15.3: - version "4.21.1" - resolved "https://registry.yarnpkg.com/rc-util/-/rc-util-4.21.1.tgz#88602d0c3185020aa1053d9a1e70eac161becb05" - integrity sha512-Z+vlkSQVc1l8O2UjR3WQ+XdWlhj5q9BMQNLk2iOBch75CqPfrJyGtcWMcnhRlNuDu0Ndtt4kLVO8JI8BrABobg== - dependencies: - add-dom-event-listener "^1.1.0" - prop-types "^15.5.10" - react-is "^16.12.0" - react-lifecycles-compat "^3.0.4" - shallowequal "^1.1.0" - -rc-util@^5.0.0, rc-util@^5.0.1, rc-util@^5.0.5, rc-util@^5.0.6, rc-util@^5.0.7, rc-util@^5.1.0, rc-util@^5.2.0, rc-util@^5.2.1, rc-util@^5.3.0, rc-util@^5.3.4, rc-util@^5.4.0: - version "5.4.0" - resolved "https://registry.yarnpkg.com/rc-util/-/rc-util-5.4.0.tgz#688eaeecfdae9dae2bfdf10bedbe884591dba004" - integrity sha512-kXDn1JyLJTAWLBFt+fjkTcUtXhxKkipQCobQmxIEVrX62iXgo24z8YKoWehWfMxPZFPE+RXqrmEu9j5kHz/Lrg== +rc-util@^5.0.0, rc-util@^5.0.1, rc-util@^5.0.5, rc-util@^5.0.6, rc-util@^5.0.7, rc-util@^5.2.0, rc-util@^5.2.1, rc-util@^5.3.0, rc-util@^5.4.0, rc-util@^5.5.0, rc-util@^5.5.1, rc-util@^5.6.1, rc-util@^5.7.0: + version "5.7.0" + resolved "https://registry.yarnpkg.com/rc-util/-/rc-util-5.7.0.tgz#776b14cf5bbfc24f419fd40c42ffadddda0718fc" + integrity sha512-0hh5XkJ+vBDeMJsHElqT1ijMx+gC3gpClwQ10h/5hccrrgrMx8VUem183KLlH1YrWCfMMPmDXWWNnwsn+p6URw== dependencies: + "@babel/runtime" "^7.12.5" react-is "^16.12.0" shallowequal "^1.1.0" rc-virtual-list@^3.0.1, rc-virtual-list@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/rc-virtual-list/-/rc-virtual-list-3.2.0.tgz#21e70f42b22f510d38ceb1ba339b89b865686fcd" - integrity sha512-NZb+Z4tGkfrCNXprVUlLJxoRVIELwLmlY5nHwiV3pj4eA9Of8thpQwtT+AomwcZjKhC7R/EDtpk2ATMJXX5s3Q== + version "3.2.6" + resolved "https://registry.yarnpkg.com/rc-virtual-list/-/rc-virtual-list-3.2.6.tgz#2c92a40f4425e19881b38134d6bd286a11137d2d" + integrity sha512-8FiQLDzm3c/tMX0d62SQtKDhLH7zFlSI6pWBAPt+TUntEqd3Lz9zFAmpvTu8gkvUom/HCsDSZs4wfV4wDPWC0Q== dependencies: classnames "^2.2.6" - rc-resize-observer "^0.2.3" + rc-resize-observer "^1.0.0" rc-util "^5.0.7" react-dom@^17.0.0: - version "17.0.0" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.0.tgz#f8266e4d9861584553ccbd186d596a1c7dd8dcb4" - integrity sha512-OGnFbxCjI2TMAZYMVxi4hqheJiN8rCEVVrL7XIGzCB6beNc4Am8M47HtkvxODZw9QgjmAPKpLba9FTu4fC1byA== + version "17.0.1" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.1.tgz#1de2560474ec9f0e334285662ede52dbc5426fc6" + integrity sha512-6eV150oJZ9U2t9svnsspTMrWNyHc6chX0KzDeAOXftRa8bNeOKTTfCJ7KorIwenkHd2xqVTBTCZd79yk/lx/Ug== dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" - scheduler "^0.20.0" + scheduler "^0.20.1" react-fast-compare@^2.0.1: version "2.0.4" @@ -6710,6 +6699,16 @@ react-lifecycles-compat@^3.0.4: resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== +react-resize-detector@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/react-resize-detector/-/react-resize-detector-5.2.0.tgz#992083834432308c551a8251a2c52306d9d16718" + integrity sha512-PQAc03J2eyhvaiWgEdQ8+bKbbyGJzLEr70KuivBd1IEmP/iewNakLUMkxm6MWnDqsRPty85pioyg8MvGb0qC8A== + dependencies: + lodash "^4.17.20" + prop-types "^15.7.2" + raf-schd "^4.0.2" + resize-observer-polyfill "^1.5.1" + react-router-dom@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.2.0.tgz#9e65a4d0c45e13289e66c7b17c7e175d0ea15662" @@ -6739,10 +6738,30 @@ react-router@5.2.0: tiny-invariant "^1.0.2" tiny-warning "^1.0.0" +react-smooth@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/react-smooth/-/react-smooth-1.0.6.tgz#18b964f123f7bca099e078324338cd8739346d0a" + integrity sha512-B2vL4trGpNSMSOzFiAul9kFAsxTukL9Wyy9EXtkQy3GJr6sZqW9e1nShdVOJ3hRYamPZ94O17r3Q0bjSw3UYtg== + dependencies: + lodash "~4.17.4" + prop-types "^15.6.0" + raf "^3.4.0" + react-transition-group "^2.5.0" + +react-transition-group@^2.5.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.9.0.tgz#df9cdb025796211151a436c69a8f3b97b5b07c8d" + integrity sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg== + dependencies: + dom-helpers "^3.4.0" + loose-envify "^1.4.0" + prop-types "^15.6.2" + react-lifecycles-compat "^3.0.4" + react@^17.0.0: - version "17.0.0" - resolved "https://registry.yarnpkg.com/react/-/react-17.0.0.tgz#ad96d5fa1a33bb9b06d0cc52672f7992d84aa662" - integrity sha512-rG9bqS3LMuetoSUKHN8G3fMNuQOePKDThK6+2yXFWtoeTDLVNh/QCaxT+Jr+rNf4lwNXpx+atdn3Aa0oi8/6eQ== + version "17.0.1" + resolved "https://registry.yarnpkg.com/react/-/react-17.0.1.tgz#6e0600416bd57574e3f86d92edba3d9008726127" + integrity sha512-lG9c9UuMHdcAexXtigOZLX8exLWkW0Ku29qPRU8uhF2R9BN96dLCt0psvzPLlHc5OWkgymP3qwTRgbnw5BKx3w== dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" @@ -6821,6 +6840,29 @@ readdirp@^2.2.1: micromatch "^3.1.10" readable-stream "^2.0.2" +recharts-scale@^0.4.2: + version "0.4.3" + resolved "https://registry.yarnpkg.com/recharts-scale/-/recharts-scale-0.4.3.tgz#040b4f638ed687a530357292ecac880578384b59" + integrity sha512-t8p5sccG9Blm7c1JQK/ak9O8o95WGhNXD7TXg/BW5bYbVlr6eCeRBNpgyigD4p6pSSMehC5nSvBUPj6F68rbFA== + dependencies: + decimal.js-light "^2.4.1" + +recharts@^2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/recharts/-/recharts-2.0.4.tgz#0ffaa9437762ed0bf021968af42154777f97c94c" + integrity sha512-XwFRhyOW6APMKvrCqN8e1IPHAQi7lmrOqp48LKi40NFI8WjFWHgTaTfwBBMsGCnTJSezJEEd/41L3bo/tfevkw== + dependencies: + classnames "^2.2.5" + d3-interpolate "^2.0.1" + d3-scale "^3.2.3" + d3-shape "^2.0.0" + eventemitter3 "^4.0.1" + lodash "^4.17.19" + react-resize-detector "^5.2.0" + react-smooth "^1.0.6" + recharts-scale "^0.4.2" + reduce-css-calc "^2.1.7" + rechoir@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.7.0.tgz#32650fd52c21ab252aa5d65b19310441c7e03aca" @@ -6836,10 +6878,13 @@ redent@^3.0.0: indent-string "^4.0.0" strip-indent "^3.0.0" -reduce-flatten@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/reduce-flatten/-/reduce-flatten-2.0.0.tgz#734fd84e65f375d7ca4465c69798c25c9d10ae27" - integrity sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w== +reduce-css-calc@^2.1.7: + version "2.1.8" + resolved "https://registry.yarnpkg.com/reduce-css-calc/-/reduce-css-calc-2.1.8.tgz#7ef8761a28d614980dc0c982f772c93f7a99de03" + integrity sha512-8liAVezDmUcH+tdzoEGrhfbGcP7nOV4NkGE3a74+qqvE7nt9i4sKLGBuZNOnpI4WiGksiNPklZxva80061QiPg== + dependencies: + css-unit-converter "^1.1.1" + postcss-value-parser "^3.3.0" redux@^4.0.0: version "4.0.5" @@ -6863,12 +6908,12 @@ regex-not@^1.0.0, regex-not@^1.0.2: safe-regex "^1.1.0" regexp.prototype.flags@^1.2.0, regexp.prototype.flags@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz#7aba89b3c13a64509dabcf3ca8d9fbb9bdf5cb75" - integrity sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ== + version "1.3.1" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz#7ef352ae8d159e758c0eadca6f8fcb4eef07be26" + integrity sha512-JiBdRBq91WlY7uRJ0ds7R+dU02i6LKi8r3BuQhNXn+kmeLN+EfHhfjqMRis1zJxnlu88hq/4dx0P2OP3APRTOA== dependencies: + call-bind "^1.0.2" define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" regexpp@^3.0.0, regexpp@^3.1.0: version "3.1.0" @@ -6880,56 +6925,28 @@ relateurl@^0.2.7: resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" integrity sha1-VNvzd+UUQKypCkzSdGANP/LYiKk= -remark-parse@^8.0.0: - version "8.0.3" - resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-8.0.3.tgz#9c62aa3b35b79a486454c690472906075f40c7e1" - integrity sha512-E1K9+QLGgggHxCQtLt++uXltxEprmWzNfg+MxpfHsZlrddKzZ/hZyWHDbK3/Ap8HJQqYJRXP+jHczdL6q6i85Q== +remark-parse@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-9.0.0.tgz#4d20a299665880e4f4af5d90b7c7b8a935853640" + integrity sha512-geKatMwSzEXKHuzBNU1z676sGcDcFoChMK38TgdHJNAYfFtsfHDQG7MoJAjs6sgYMqyLduCYWDIWZIxiPeafEw== dependencies: - ccount "^1.0.0" - collapse-white-space "^1.0.2" - is-alphabetical "^1.0.0" - is-decimal "^1.0.0" - is-whitespace-character "^1.0.0" - is-word-character "^1.0.0" - markdown-escapes "^1.0.0" - parse-entities "^2.0.0" - repeat-string "^1.5.4" - state-toggle "^1.0.0" - trim "0.0.1" - trim-trailing-lines "^1.0.0" - unherit "^1.0.4" - unist-util-remove-position "^2.0.0" - vfile-location "^3.0.0" - xtend "^4.0.1" + mdast-util-from-markdown "^0.8.0" -remark-stringify@^8.0.0: - version "8.1.1" - resolved "https://registry.yarnpkg.com/remark-stringify/-/remark-stringify-8.1.1.tgz#e2a9dc7a7bf44e46a155ec78996db896780d8ce5" - integrity sha512-q4EyPZT3PcA3Eq7vPpT6bIdokXzFGp9i85igjmhRyXWmPs0Y6/d2FYwUNotKAWyLch7g0ASZJn/KHHcHZQ163A== +remark-stringify@^9.0.0: + version "9.0.1" + resolved "https://registry.yarnpkg.com/remark-stringify/-/remark-stringify-9.0.1.tgz#576d06e910548b0a7191a71f27b33f1218862894" + integrity sha512-mWmNg3ZtESvZS8fv5PTvaPckdL4iNlCHTt8/e/8oN08nArHRHjNZMKzA/YW3+p7/lYqIw4nx1XsjCBo/AxNChg== dependencies: - ccount "^1.0.0" - is-alphanumeric "^1.0.0" - is-decimal "^1.0.0" - is-whitespace-character "^1.0.0" - longest-streak "^2.0.1" - markdown-escapes "^1.0.0" - markdown-table "^2.0.0" - mdast-util-compact "^2.0.0" - parse-entities "^2.0.0" - repeat-string "^1.5.4" - state-toggle "^1.0.0" - stringify-entities "^3.0.0" - unherit "^1.0.4" - xtend "^4.0.1" + mdast-util-to-markdown "^0.6.0" -remark@^12.0.0: - version "12.0.1" - resolved "https://registry.yarnpkg.com/remark/-/remark-12.0.1.tgz#f1ddf68db7be71ca2bad0a33cd3678b86b9c709f" - integrity sha512-gS7HDonkdIaHmmP/+shCPejCEEW+liMp/t/QwmF0Xt47Rpuhl32lLtDV1uKWvGoq+kxr5jSgg5oAIpGuyULjUw== +remark@^13.0.0: + version "13.0.0" + resolved "https://registry.yarnpkg.com/remark/-/remark-13.0.0.tgz#d15d9bf71a402f40287ebe36067b66d54868e425" + integrity sha512-HDz1+IKGtOyWN+QgBiAT0kn+2s6ovOxHyPAFGKVE81VSzJ+mq7RwHFledEvB5F1p4iJvOah/LOKdFuzvRnNLCA== dependencies: - remark-parse "^8.0.0" - remark-stringify "^8.0.0" - unified "^9.0.0" + remark-parse "^9.0.0" + remark-stringify "^9.0.0" + unified "^9.1.0" remove-trailing-separator@^1.0.1: version "1.1.0" @@ -6937,13 +6954,13 @@ remove-trailing-separator@^1.0.1: integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= renderkid@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/renderkid/-/renderkid-2.0.4.tgz#d325e532afb28d3f8796ffee306be8ffd6fc864c" - integrity sha512-K2eXrSOJdq+HuKzlcjOlGoOarUu5SDguDEhE7+Ah4zuOWL40j8A/oHvLlLob9PSTNvVnBd+/q0Er1QfpEuem5g== + version "2.0.5" + resolved "https://registry.yarnpkg.com/renderkid/-/renderkid-2.0.5.tgz#483b1ac59c6601ab30a7a596a5965cabccfdd0a5" + integrity sha512-ccqoLg+HLOHq1vdfYNm4TBeaCDIi1FLt3wGojTDSvdewUv65oTmI3cnT2E4hRjl1gzKZIPK+KZrXzlUYKnR+vQ== dependencies: - css-select "^1.1.0" + css-select "^2.0.2" dom-converter "^0.2" - htmlparser2 "^3.3.0" + htmlparser2 "^3.10.1" lodash "^4.17.20" strip-ansi "^3.0.0" @@ -6952,21 +6969,21 @@ repeat-element@^1.1.2: resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce" integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g== -repeat-string@^1.0.0, repeat-string@^1.5.4, repeat-string@^1.6.1: +repeat-string@^1.0.0, repeat-string@^1.6.1: version "1.6.1" resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= -replace-ext@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.0.tgz#de63128373fcbf7c3ccfa4de5a480c45a67958eb" - integrity sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs= - require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= +require-from-string@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + require-main-filename@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" @@ -7021,15 +7038,7 @@ resolve-url@^0.2.1: resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= -resolve@^1.1.7, resolve@^1.10.0, resolve@^1.13.1, resolve@^1.17.0, resolve@^1.18.1, resolve@^1.3.2: - version "1.18.1" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.18.1.tgz#018fcb2c5b207d2a6424aee361c5a266da8f4130" - integrity sha512-lDfCPaMKfOJXjy0dPayzPdF1phampNWr3qFCjAu+rw/qbQmr5jWH5xN2hwh9QKfw9E5v4hwV7A+jrCmL8yjjqA== - dependencies: - is-core-module "^2.0.0" - path-parse "^1.0.6" - -resolve@^1.9.0: +resolve@^1.1.7, resolve@^1.10.0, resolve@^1.13.1, resolve@^1.17.0, resolve@^1.18.1, resolve@^1.9.0: version "1.19.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.19.0.tgz#1af5bf630409734a067cae29318aac7fa29a267c" integrity sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg== @@ -7062,14 +7071,7 @@ rgba-regex@^1.0.0: resolved "https://registry.yarnpkg.com/rgba-regex/-/rgba-regex-1.0.0.tgz#43374e2e2ca0968b0ef1523460b7d730ff22eeb3" integrity sha1-QzdOLiyglosO8VI0YLfXMP8i7rM= -rimraf@2.6.3: - version "2.6.3" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" - integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== - dependencies: - glob "^7.1.3" - -rimraf@^2.6.3: +rimraf@^2.6.3, rimraf@^2.7.1: version "2.7.1" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== @@ -7084,9 +7086,9 @@ rimraf@^3.0.2: glob "^7.1.3" run-parallel@^1.1.9: - version "1.1.9" - resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.9.tgz#c9dd3a7cf9f4b2c4b6244e173a6ed866e61dd679" - integrity sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q== + version "1.1.10" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.10.tgz#60a51b2ae836636c81377df16cb107351bcd13ef" + integrity sha512-zb/1OuZ6flOlH6tQyMPUrE3x3Ulxjlo9WIVXR4yVYi4H9UXQaeIsPbLn2R3O3vQCnDKkAl2qHiuocKKX4Tz/Sw== safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" @@ -7115,18 +7117,10 @@ sax@~1.2.4: resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== -scheduler@^0.18.0: - version "0.18.0" - resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.18.0.tgz#5901ad6659bc1d8f3fdaf36eb7a67b0d6746b1c4" - integrity sha512-agTSHR1Nbfi6ulI0kYNK0203joW2Y5W4po4l+v03tOoiJKpTBbxpNhWDvqc/4IcOw+KLmSiQLTasZ4cab2/UWQ== - dependencies: - loose-envify "^1.1.0" - object-assign "^4.1.1" - -scheduler@^0.20.0: - version "0.20.0" - resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.0.tgz#3ff543696b169613afadb09d3fb3fe998d234dd2" - integrity sha512-XegIgta1bIaz2LdaL6eg1GEcE42g0BY9qFXCqlZ/+s2MuEKfigFCW6DEGBlZzeVFlwDmVusrWEyFtBo4sbkkdA== +scheduler@^0.20.1: + version "0.20.1" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.1.tgz#da0b907e24026b01181ecbc75efdc7f27b5a000c" + integrity sha512-LKTe+2xNJBNxu/QhHvDR14wUXHRQbVY5ZOYpOGWRzhydZUqrLb2JBvLPY7cAqFmqrWuDED0Mjk7013SZiOz6Bw== dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" @@ -7170,23 +7164,13 @@ select-hose@^2.0.0: resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" integrity sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo= -selfsigned@^1.10.7: +selfsigned@^1.10.8: version "1.10.8" resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.8.tgz#0d17208b7d12c33f8eac85c41835f27fc3d81a30" integrity sha512-2P4PtieJeEwVgTU9QEcwIRDQ/mXJLX8/+I3ur+Pg16nS8oNbrGxEso9NyYWy8NAmXiNl4dlAp5MwoNeCWzON4w== dependencies: node-forge "^0.10.0" -semver-compare@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" - integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w= - -semver-regex@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/semver-regex/-/semver-regex-2.0.0.tgz#a93c2c5844539a770233379107b38c7b4ac9d338" - integrity sha512-mUdIBBvdn0PLOeP3TEkMH7HHeUP3GjsXCwKarjv/kGmUFOYg1VqEemKhoQpWMu6X2I8kHeuVdGibLGkVK+/5Qw== - "semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.5.0, semver@^5.6.0: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" @@ -7197,10 +7181,12 @@ semver@^6.0.0, semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== -semver@^7.2.1, semver@^7.3.2: - version "7.3.2" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" - integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== +semver@^7.2.1, semver@^7.3.2, semver@^7.3.4: + version "7.3.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97" + integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw== + dependencies: + lru-cache "^6.0.0" send@0.17.1: version "0.17.1" @@ -7312,15 +7298,16 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== -side-channel@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.3.tgz#cdc46b057550bbab63706210838df5d4c19519c3" - integrity sha512-A6+ByhlLkksFoUepsGxfj5x1gTSrs+OydsRptUxeNCabQpCFUvcwIczgOigI8vhY/OJCnPnyE9rGiwgvr9cS1g== +side-channel@^1.0.3, side-channel@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" + integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== dependencies: - es-abstract "^1.18.0-next.0" - object-inspect "^1.8.0" + call-bind "^1.0.0" + get-intrinsic "^1.0.2" + object-inspect "^1.9.0" -signal-exit@^3.0.0, signal-exit@^3.0.2: +signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== @@ -7337,15 +7324,6 @@ slash@^3.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== -slice-ansi@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636" - integrity sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ== - dependencies: - ansi-styles "^3.2.0" - astral-regex "^1.0.0" - is-fullwidth-code-point "^2.0.0" - slice-ansi@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" @@ -7385,26 +7363,26 @@ snapdragon@^0.8.1: source-map-resolve "^0.5.0" use "^3.1.0" -sockjs-client@1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.4.0.tgz#c9f2568e19c8fd8173b4997ea3420e0bb306c7d5" - integrity sha512-5zaLyO8/nri5cua0VtOrFXBPK1jbL4+1cebT/mmKA1E1ZXOvJrII75bPu0l0k843G/+iAbhEqzyKr0w/eCCj7g== +sockjs-client@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.5.0.tgz#2f8ff5d4b659e0d092f7aba0b7c386bd2aa20add" + integrity sha512-8Dt3BDi4FYNrCFGTL/HtwVzkARrENdwOUf1ZoW/9p3M8lZdFT35jVdrHza+qgxuG9H3/shR4cuX/X9umUrjP8Q== dependencies: - debug "^3.2.5" + debug "^3.2.6" eventsource "^1.0.7" - faye-websocket "~0.11.1" - inherits "^2.0.3" - json3 "^3.3.2" - url-parse "^1.4.3" + faye-websocket "^0.11.3" + inherits "^2.0.4" + json3 "^3.3.3" + url-parse "^1.4.7" -sockjs@0.3.20: - version "0.3.20" - resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.20.tgz#b26a283ec562ef8b2687b44033a4eeceac75d855" - integrity sha512-SpmVOVpdq0DJc0qArhF3E5xsxvaiqGNb73XfgBpK1y3UD5gs8DSo8aCTsuT5pX8rssdc2NDIzANwP9eCAiSdTA== +sockjs@^0.3.21: + version "0.3.21" + resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.21.tgz#b34ffb98e796930b60a0cfa11904d6a339a7d417" + integrity sha512-DhbPFGpxjc6Z3I+uX07Id5ZO2XwYsWOrYjaSeieES78cq+JaJvVe5q/m1uvjIQhXinhIeCFRH6JgXe+mvVMyXw== dependencies: - faye-websocket "^0.10.0" + faye-websocket "^0.11.3" uuid "^3.4.0" - websocket-driver "0.6.5" + websocket-driver "^0.7.4" source-list-map@^2.0.0, source-list-map@^2.0.1: version "2.0.1" @@ -7472,9 +7450,9 @@ spdx-expression-parse@^3.0.0: spdx-license-ids "^3.0.0" spdx-license-ids@^3.0.0: - version "3.0.6" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.6.tgz#c80757383c28abf7296744998cbc106ae8b854ce" - integrity sha512-+orQK83kyMva3WyPf59k1+Y525csj5JejicWut55zeTWANuN17qSiSLUXWtzHeNWORSvT7GLDJ/E/XiIWoXBTw== + version "3.0.7" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.7.tgz#e9c18a410e5ed7e12442a549fbd8afa767038d65" + integrity sha512-U+MTEOO0AiDzxwFvoa4JVnMV6mZlJKk2sBLt90s7G0Gd0Mlknc7kxEn3nuDPNZRta7O2uy8oLcZLVT+4sqNZHQ== spdy-transport@^3.0.0: version "3.0.0" @@ -7528,11 +7506,6 @@ stable@^0.1.8: resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== -state-toggle@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/state-toggle/-/state-toggle-1.0.3.tgz#e123b16a88e143139b09c6852221bc9815917dfe" - integrity sha512-d/5Z4/2iiCnHw6Xzghyhb+GcmF89bxwgXG60wjIiZaxnymbyOmI8Hk4VqHXiVVp6u2ysaskFfXg3ekCj4WNftQ== - static-extend@^0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" @@ -7575,32 +7548,33 @@ string-width@^4.2.0: strip-ansi "^6.0.0" string.prototype.matchall@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.2.tgz#48bb510326fb9fdeb6a33ceaa81a6ea04ef7648e" - integrity sha512-N/jp6O5fMf9os0JU3E72Qhf590RSRZU/ungsL/qJUYVTNv7hTG0P/dbPjxINVN9jpscu3nzYwKESU3P3RY5tOg== + version "4.0.3" + resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.3.tgz#24243399bc31b0a49d19e2b74171a15653ec996a" + integrity sha512-OBxYDA2ifZQ2e13cP82dWFMaCV9CGF8GzmN4fljBVw5O5wep0lu4gacm1OL6MjROoUnB8VbkWRThqkV2YFLNxw== dependencies: + call-bind "^1.0.0" define-properties "^1.1.3" - es-abstract "^1.17.0" + es-abstract "^1.18.0-next.1" has-symbols "^1.0.1" internal-slot "^1.0.2" regexp.prototype.flags "^1.3.0" - side-channel "^1.0.2" + side-channel "^1.0.3" -string.prototype.trimend@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.2.tgz#6ddd9a8796bc714b489a3ae22246a208f37bfa46" - integrity sha512-8oAG/hi14Z4nOVP0z6mdiVZ/wqjDtWSLygMigTzAb+7aPEDTleeFf+WrF+alzecxIRkckkJVn+dTlwzJXORATw== +string.prototype.trimend@^1.0.1, string.prototype.trimend@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.3.tgz#a22bd53cca5c7cf44d7c9d5c732118873d6cd18b" + integrity sha512-ayH0pB+uf0U28CtjlLvL7NaohvR1amUvVZk+y3DYb0Ey2PUV5zPkkKy9+U1ndVEIXO8hNg18eIv9Jntbii+dKw== dependencies: + call-bind "^1.0.0" define-properties "^1.1.3" - es-abstract "^1.18.0-next.1" -string.prototype.trimstart@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.2.tgz#22d45da81015309cd0cdd79787e8919fc5c613e7" - integrity sha512-7F6CdBTl5zyu30BJFdzSTlSlLPwODC23Od+iLoVH8X6+3fvDPPuBVVj9iaB1GOsSTSIgVfsfm27R2FGrAPznWg== +string.prototype.trimstart@^1.0.1, string.prototype.trimstart@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.3.tgz#9b4cb590e123bb36564401d59824298de50fd5aa" + integrity sha512-oBIBUy5lea5tt0ovtOFiEQaBkoBBkyJhZXzJYrSmDo5IUUqbOPvVezuRs/agBIdZ2p2Eo1FD6bD9USyBLfl3xg== dependencies: + call-bind "^1.0.0" define-properties "^1.1.3" - es-abstract "^1.18.0-next.1" string_decoder@^1.1.1: version "1.3.0" @@ -7616,15 +7590,6 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -stringify-entities@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/stringify-entities/-/stringify-entities-3.1.0.tgz#b8d3feac256d9ffcc9fa1fefdcf3ca70576ee903" - integrity sha512-3FP+jGMmMV/ffZs86MoghGqAoqXAdxLrJP4GUdrDN1aIScYih5tuIO3eF4To5AJZ79KDZ8Fpdy7QJnK8SsL1Vg== - dependencies: - character-entities-html4 "^1.0.0" - character-entities-legacy "^1.0.0" - xtend "^4.0.0" - strip-ansi@^3.0.0, strip-ansi@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" @@ -7705,37 +7670,37 @@ stylelint-webpack-plugin@^2.1.1: schema-utils "^3.0.0" stylelint@^13.7.2: - version "13.7.2" - resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-13.7.2.tgz#6f3c58eea4077680ed0ceb0d064b22b100970486" - integrity sha512-mmieorkfmO+ZA6CNDu1ic9qpt4tFvH2QUB7vqXgrMVHe5ENU69q7YDq0YUg/UHLuCsZOWhUAvcMcLzLDIERzSg== + version "13.9.0" + resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-13.9.0.tgz#93921ee6e11d4556b9f31131f485dc813b68e32a" + integrity sha512-VVWH2oixOAxpWL1vH+V42ReCzBjW2AeqskSAbi8+3OjV1Xg3VZkmTcAqBZfRRvJeF4BvYuDLXebW3tIHxgZDEg== dependencies: "@stylelint/postcss-css-in-js" "^0.37.2" - "@stylelint/postcss-markdown" "^0.36.1" + "@stylelint/postcss-markdown" "^0.36.2" autoprefixer "^9.8.6" balanced-match "^1.0.0" chalk "^4.1.0" cosmiconfig "^7.0.0" - debug "^4.1.1" + debug "^4.3.1" execall "^2.0.0" - fast-glob "^3.2.4" + fast-glob "^3.2.5" fastest-levenshtein "^1.0.12" - file-entry-cache "^5.0.1" + file-entry-cache "^6.0.0" get-stdin "^8.0.0" global-modules "^2.0.0" - globby "^11.0.1" + globby "^11.0.2" globjoin "^0.1.4" html-tags "^3.1.0" ignore "^5.1.8" import-lazy "^4.0.0" imurmurhash "^0.1.4" - known-css-properties "^0.19.0" + known-css-properties "^0.20.0" lodash "^4.17.20" log-symbols "^4.0.0" mathml-tag-names "^2.1.3" - meow "^7.1.1" + meow "^9.0.0" micromatch "^4.0.2" normalize-selector "^0.2.0" - postcss "^7.0.32" + postcss "^7.0.35" postcss-html "^0.36.0" postcss-less "^3.1.4" postcss-media-query-parser "^0.2.3" @@ -7743,7 +7708,7 @@ stylelint@^13.7.2: postcss-safe-parser "^4.0.2" postcss-sass "^0.4.4" postcss-scss "^2.1.1" - postcss-selector-parser "^6.0.2" + postcss-selector-parser "^6.0.4" postcss-syntax "^0.36.2" postcss-value-parser "^4.1.0" resolve-from "^5.0.0" @@ -7754,8 +7719,8 @@ stylelint@^13.7.2: style-search "^0.1.0" sugarss "^2.0.0" svg-tags "^1.0.0" - table "^6.0.1" - v8-compile-cache "^2.1.1" + table "^6.0.7" + v8-compile-cache "^2.2.0" write-file-atomic "^3.0.3" sugarss@^2.0.0: @@ -7765,12 +7730,12 @@ sugarss@^2.0.0: dependencies: postcss "^7.0.2" -sugarss@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/sugarss/-/sugarss-3.0.1.tgz#1e4e315b3b321eec477ef9617c8964bcf3833b0c" - integrity sha512-xW0tTjuJdd3VSsPH2dLgNDzESka1+Ul3GYVziyhX7GyXQboOARDaeEU++IjhOZPnoKoMENsU0tvtrCKr1sJwlw== +sugarss@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/sugarss/-/sugarss-3.0.3.tgz#bb2489961b98fbd15e4e35d6b9f4f2ee5547a6cb" + integrity sha512-uxa2bbuc+w7ov7DyYIhF6bM0qZF3UkFT5/nE8AJgboiVnKsBDbwxs++dehEIe1JNhpMaGJc37wGQ2QrrWey2Sg== dependencies: - postcss "^8.1.0" + postcss "^8.1.6" supports-color@^5.3.0, supports-color@^5.4.0: version "5.5.0" @@ -7822,32 +7787,12 @@ symbol-observable@^1.2.0: resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ== -table-layout@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/table-layout/-/table-layout-1.0.1.tgz#8411181ee951278ad0638aea2f779a9ce42894f9" - integrity sha512-dEquqYNJiGwY7iPfZ3wbXDI944iqanTSchrACLL2nOB+1r+h1Nzu2eH+DuPPvWvm5Ry7iAPeFlgEtP5bIp5U7Q== +table@^6.0.4, table@^6.0.7: + version "6.0.7" + resolved "https://registry.yarnpkg.com/table/-/table-6.0.7.tgz#e45897ffbcc1bcf9e8a87bf420f2c9e5a7a52a34" + integrity sha512-rxZevLGTUzWna/qBLObOe16kB2RTnnbhciwgPbMMlazz1yZGVEgnZK762xyVdVznhqxrfCeBMmMkgOOaPwjH7g== dependencies: - array-back "^4.0.1" - deep-extend "~0.6.0" - typical "^5.2.0" - wordwrapjs "^4.0.0" - -table@^5.2.3: - version "5.4.6" - resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e" - integrity sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug== - dependencies: - ajv "^6.10.2" - lodash "^4.17.14" - slice-ansi "^2.1.0" - string-width "^3.0.0" - -table@^6.0.1: - version "6.0.3" - resolved "https://registry.yarnpkg.com/table/-/table-6.0.3.tgz#e5b8a834e37e27ad06de2e0fda42b55cfd8a0123" - integrity sha512-8321ZMcf1B9HvVX/btKv8mMZahCjn2aYrDlpqHaBFCfnox64edeH9kEid0vTLTRR8gWR2A20aDgeuTTea4sVtw== - dependencies: - ajv "^6.12.4" + ajv "^7.0.2" lodash "^4.17.20" slice-ansi "^4.0.0" string-width "^4.2.0" @@ -7857,15 +7802,15 @@ tapable@^1.0.0, tapable@^1.1.3: resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== -tapable@^2.0.0, tapable@^2.1.1: +tapable@^2.1.1, tapable@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.0.tgz#5c373d281d9c672848213d0e037d1c4165ab426b" integrity sha512-FBk4IesMV1rBxX2tfiK8RAmogtWn53puLOQlvO8XuwlgxcYbP4mVPS9Ph4aeamSyyVjOl24aYWAuc8U5kCVwMw== tar@^6.0.2: - version "6.0.5" - resolved "https://registry.yarnpkg.com/tar/-/tar-6.0.5.tgz#bde815086e10b39f1dcd298e89d596e1535e200f" - integrity sha512-0b4HOimQHj9nXNEAA7zWwMM91Zhhba3pspja6sQbgTpynOJf+bkjBnfybNYzbpLbnwXnbyB4LOREvlyXLkCHSg== + version "6.1.0" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.0.tgz#d1724e9bcc04b977b18d5c573b333a2207229a83" + integrity sha512-DUCttfhsnLCjwoDoFcI+B2iJgYa93vBnDUATYEeRx6sntCTdN01VnqsIuTlALXla/LWooNg0yEGeB+Y8WdFxGA== dependencies: chownr "^2.0.0" fs-minipass "^2.0.0" @@ -7874,29 +7819,17 @@ tar@^6.0.2: mkdirp "^1.0.3" yallist "^4.0.0" -terser-webpack-plugin@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.0.0.tgz#88f58d27d1c8244965c59540d3ccda1598fc958c" - integrity sha512-rf7l5a9xamIVX3enQeTl0MY2MNeZClo5yPX/tVPy22oY0nzu0b45h7JqyFi/bygqKWtzXMnml0u12mArhQPsBQ== +terser-webpack-plugin@^5.0.0, terser-webpack-plugin@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.1.1.tgz#7effadee06f7ecfa093dbbd3e9ab23f5f3ed8673" + integrity sha512-5XNNXZiR8YO6X6KhSGXfY0QrGrCRlSwAEjIIrlRQR4W8nP69TaJUlh3bkuac6zzgspiGPfKEHcY295MMVExl5Q== dependencies: - jest-worker "^26.5.0" - p-limit "^3.0.2" + jest-worker "^26.6.2" + p-limit "^3.1.0" schema-utils "^3.0.0" serialize-javascript "^5.0.1" source-map "^0.6.1" - terser "^5.3.5" - -terser-webpack-plugin@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.0.3.tgz#ec60542db2421f45735c719d2e17dabfbb2e3e42" - integrity sha512-zFdGk8Lh9ZJGPxxPE6jwysOlATWB8GMW8HcfGULWA/nPal+3VdATflQvSBSLQJRCmYZnfFJl6vkRTiwJGNgPiQ== - dependencies: - jest-worker "^26.6.1" - p-limit "^3.0.2" - schema-utils "^3.0.0" - serialize-javascript "^5.0.1" - source-map "^0.6.1" - terser "^5.3.8" + terser "^5.5.1" terser@^4.6.3: version "4.8.0" @@ -7907,16 +7840,7 @@ terser@^4.6.3: source-map "~0.6.1" source-map-support "~0.5.12" -terser@^5.3.5: - version "5.3.7" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.3.7.tgz#798a4ae2e7ff67050c3e99fcc4e00725827d97e2" - integrity sha512-lJbKdfxWvjpV330U4PBZStCT9h3N9A4zZVA5Y4k9sCWXknrpdyxi1oMsRKLmQ/YDMDxSBKIh88v0SkdhdqX06w== - dependencies: - commander "^2.20.0" - source-map "~0.7.2" - source-map-support "~0.5.19" - -terser@^5.3.8: +terser@^5.5.1: version "5.5.1" resolved "https://registry.yarnpkg.com/terser/-/terser-5.5.1.tgz#540caa25139d6f496fdea056e414284886fb2289" integrity sha512-6VGWZNVP2KTUcltUQJ25TtNjx/XgdDsBDKGt8nN0MpydU36LmbPPcMBd2kmtZNNGVVDLg44k7GKeHHj+4zPIBQ== @@ -7950,11 +7874,6 @@ tiny-warning@^1.0.0, tiny-warning@^1.0.2, tiny-warning@^1.0.3: resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== -tinycolor2@^1.4.1: - version "1.4.2" - resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.4.2.tgz#3f6a4d1071ad07676d7fa472e1fac40a719d8803" - integrity sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA== - to-fast-properties@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" @@ -8007,47 +7926,38 @@ trim-newlines@^3.0.0: resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.0.tgz#79726304a6a898aa8373427298d54c2ee8b1cb30" integrity sha512-C4+gOpvmxaSMKuEf9Qc134F1ZuOHVXKRbtEflf4NTtuuJDEIJ9p5PXsalL8SkeRw+qit1Mo+yuvMPAKwWg/1hA== -trim-trailing-lines@^1.0.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/trim-trailing-lines/-/trim-trailing-lines-1.1.3.tgz#7f0739881ff76657b7776e10874128004b625a94" - integrity sha512-4ku0mmjXifQcTVfYDfR5lpgV7zVqPg6zV9rdZmwOPqq0+Zq19xDqEgagqVbc4pOOShbncuAOIs59R3+3gcF3ZA== - -trim@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/trim/-/trim-0.0.1.tgz#5858547f6b290757ee95cccc666fb50084c460dd" - integrity sha1-WFhUf2spB1fulczMZm+1AITEYN0= - trough@^1.0.0: version "1.0.5" resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.5.tgz#b8b639cefad7d0bb2abd37d433ff8293efa5f406" integrity sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA== ts-loader@^8.0.6: - version "8.0.6" - resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-8.0.6.tgz#8f47d203ef8fc95826a292a09f97a02bf1f57565" - integrity sha512-c8XkRbhKxFLbiIwZR7FBGWDq0MIz/QSpx3CGpj0abJxD5YVX8oDhQkJLeGbXUPRIlaX4Ajmr77fOiFVZ3gSU7g== + version "8.0.14" + resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-8.0.14.tgz#e46ac1f8dcb88808d0b1335d2eae65b74bd78fe8" + integrity sha512-Jt/hHlUnApOZjnSjTmZ+AbD5BGlQFx3f1D0nYuNKwz0JJnuDGHJas6az+FlWKwwRTu+26GXpv249A8UAnYUpqA== dependencies: - chalk "^2.3.0" + chalk "^4.1.0" enhanced-resolve "^4.0.0" - loader-utils "^1.0.2" + loader-utils "^2.0.0" micromatch "^4.0.0" - semver "^6.0.0" + semver "^7.3.4" ts-morph@^8.1.2: - version "8.1.2" - resolved "https://registry.yarnpkg.com/ts-morph/-/ts-morph-8.1.2.tgz#f0bee6be6fbd8c3174ac5d6c0423a035e36c2bbd" - integrity sha512-5w4TzmMzECrBunIku1T6/Y0Y2IJ9sHT6hAlfF59WugXnsq1dv6DSioA+s6LgUiswxjgaJgZndOKgu8BrLcYjKw== + version "8.2.0" + resolved "https://registry.yarnpkg.com/ts-morph/-/ts-morph-8.2.0.tgz#41d83cd501cbd897eb029ac489d6d5b927555c57" + integrity sha512-NHHWu+7I2/AOZiTni5w3f+xCfIxrkzPCcQbTGa81Yk3pr23a4h9xLLEE6tIGuYIubWjkjr9QVC3ITqgmA5touQ== dependencies: "@dsherret/to-absolute-glob" "^2.0.2" "@ts-morph/common" "~0.6.0" code-block-writer "^10.1.0" ts-node@^9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-9.0.0.tgz#e7699d2a110cc8c0d3b831715e417688683460b3" - integrity sha512-/TqB4SnererCDR/vb4S/QvSZvzQMJN8daAslg7MeaiHvD8rDZsSfXmNeNumyZZzMned72Xoq/isQljYSt8Ynfg== + version "9.1.1" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-9.1.1.tgz#51a9a450a3e959401bda5f004a72d54b936d376d" + integrity sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg== dependencies: arg "^4.1.0" + create-require "^1.1.0" diff "^4.0.1" make-error "^1.1.1" source-map-support "^0.5.17" @@ -8068,10 +7978,15 @@ tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== +tslib@^2.0.3: + version "2.1.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a" + integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A== + tsutils@^3.17.1: - version "3.17.1" - resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759" - integrity sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g== + version "3.20.0" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.20.0.tgz#ea03ea45462e146b53d70ce0893de453ff24f698" + integrity sha512-RYbuQuvkhuqVeXweWT3tJLKOEJ/UUw9GjNEZGWdrLLlM+611o1gwLHBpxoFJKKl25fLprp2eVthtKs5JOrNeXg== dependencies: tslib "^1.8.1" @@ -8082,10 +7997,10 @@ type-check@^0.4.0, type-check@~0.4.0: dependencies: prelude-ls "^1.2.1" -type-fest@^0.13.1: - version "0.13.1" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.13.1.tgz#0172cb5bce80b0bd542ea348db50c7e21834d934" - integrity sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg== +type-fest@^0.18.0: + version "0.18.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.18.1.tgz#db4bc151a4a2cf4eebf9add5db75508db6cc841f" + integrity sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw== type-fest@^0.6.0: version "0.6.0" @@ -8112,30 +8027,22 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" -typescript@^4.0.3, typescript@~4.0.2: - version "4.0.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.0.3.tgz#153bbd468ef07725c1df9c77e8b453f8d36abba5" - integrity sha512-tEu6DGxGgRJPb/mVPIZ48e69xCn2yRmCgYmDugAVwmJ6o+0u1RI18eO7E7WBTLYLaEVVOhwQmcdhQHweux/WPg== +typescript@^4.0.3: + version "4.1.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.3.tgz#519d582bd94cba0cf8934c7d8e8467e473f53bb7" + integrity sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg== -typical@^5.0.0, typical@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/typical/-/typical-5.2.0.tgz#4daaac4f2b5315460804f0acf6cb69c52bb93066" - integrity sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg== +typescript@~4.0.2: + version "4.0.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.0.5.tgz#ae9dddfd1069f1cb5beb3ef3b2170dd7c1332389" + integrity sha512-ywmr/VrTVCmNTJ6iV2LwIrfG1P+lv6luD8sUJs+2eI9NLGigaN+nUQc13iHqisq7bra9lnmUSYqbJvegraBOPQ== unc-path-regex@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa" integrity sha1-5z3T17DXxe2G+6xrCufYxqadUPo= -unherit@^1.0.4: - version "1.1.3" - resolved "https://registry.yarnpkg.com/unherit/-/unherit-1.1.3.tgz#6c9b503f2b41b262330c80e91c8614abdaa69c22" - integrity sha512-Ft16BJcnapDKp0+J/rqFC3Rrk6Y/Ng4nzsC028k2jdDII/rdZ7Wd3pPT/6+vIIxRagwRc9K0IUX0Ra4fKvw+WQ== - dependencies: - inherits "^2.0.0" - xtend "^4.0.0" - -unified@^9.0.0: +unified@^9.1.0: version "9.2.0" resolved "https://registry.yarnpkg.com/unified/-/unified-9.2.0.tgz#67a62c627c40589edebbf60f53edfd4d822027f8" integrity sha512-vx2Z0vY+a3YoTj8+pttM3tiJHCwY5UFbYdiWrwBEbHmK8pvsPj2rtAX2BFfgXen8T39CJWblWRDT4L5WGXtDdg== @@ -8181,24 +8088,17 @@ unique-slug@^2.0.0: dependencies: imurmurhash "^0.1.4" -unist-util-find-all-after@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/unist-util-find-all-after/-/unist-util-find-all-after-3.0.1.tgz#95cc62f48812d879b4685a0512bf1b838da50e9a" - integrity sha512-0GICgc++sRJesLwEYDjFVJPJttBpVQaTNgc6Jw0Jhzvfs+jtKePEMu+uD+PqkRUrAvGQqwhpDwLGWo1PK8PDEw== +unist-util-find-all-after@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/unist-util-find-all-after/-/unist-util-find-all-after-3.0.2.tgz#fdfecd14c5b7aea5e9ef38d5e0d5f774eeb561f6" + integrity sha512-xaTC/AGZ0rIM2gM28YVRAFPIZpzbpDtU3dRmp7EXlNVA8ziQc4hY3H7BHXM1J49nEmiqc3svnqMReW+PGqbZKQ== dependencies: unist-util-is "^4.0.0" unist-util-is@^4.0.0: - version "4.0.2" - resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-4.0.2.tgz#c7d1341188aa9ce5b3cff538958de9895f14a5de" - integrity sha512-Ofx8uf6haexJwI1gxWMGg6I/dLnF2yE+KibhD3/diOqY2TinLcqHXCV6OI5gFVn3xQqDH+u0M625pfKwIwgBKQ== - -unist-util-remove-position@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/unist-util-remove-position/-/unist-util-remove-position-2.0.1.tgz#5d19ca79fdba712301999b2b73553ca8f3b352cc" - integrity sha512-fDZsLYIe2uT+oGFnuZmy73K6ZxOPG/Qcm+w7jbEjaFcJgbQ6cqjs/eSPzXhsmGpAsWPkqZM9pYjww5QTn3LHMA== - dependencies: - unist-util-visit "^2.0.0" + version "4.0.4" + resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-4.0.4.tgz#3e9e8de6af2eb0039a59f50c9b3e99698a924f50" + integrity sha512-3dF39j/u423v4BBQrk1AQ2Ve1FxY5W3JKwXxVFzBODQ6WEvccguhgp802qQLKSnxPODE6WuRZtV+ohlUg4meBA== unist-util-stringify-position@^2.0.0: version "2.0.3" @@ -8207,32 +8107,15 @@ unist-util-stringify-position@^2.0.0: dependencies: "@types/unist" "^2.0.2" -unist-util-visit-parents@^3.0.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-3.1.1.tgz#65a6ce698f78a6b0f56aa0e88f13801886cdaef6" - integrity sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg== - dependencies: - "@types/unist" "^2.0.0" - unist-util-is "^4.0.0" - -unist-util-visit@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-2.0.3.tgz#c3703893146df47203bb8a9795af47d7b971208c" - integrity sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q== - dependencies: - "@types/unist" "^2.0.0" - unist-util-is "^4.0.0" - unist-util-visit-parents "^3.0.0" - universalify@^0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== -universalify@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-1.0.0.tgz#b61a1da173e8435b2fe3c67d29b9adf8594bd16d" - integrity sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug== +universalify@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" + integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" @@ -8258,9 +8141,9 @@ upath@^1.1.1: integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg== uri-js@^4.2.2: - version "4.4.0" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.0.tgz#aa714261de793e8a82347a7bcc9ce74e86f28602" - integrity sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g== + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== dependencies: punycode "^2.1.0" @@ -8278,7 +8161,7 @@ url-loader@^4.1.1: mime-types "^2.1.27" schema-utils "^3.0.0" -url-parse@^1.4.3: +url-parse@^1.4.3, url-parse@^1.4.7: version "1.4.7" resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278" integrity sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg== @@ -8337,12 +8220,7 @@ uuid@^3.3.2, uuid@^3.4.0: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== -v8-compile-cache@^2.0.3, v8-compile-cache@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz#54bc3cdd43317bca91e35dcaf305b1a7237de745" - integrity sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ== - -v8-compile-cache@^2.2.0: +v8-compile-cache@^2.0.3, v8-compile-cache@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.2.0.tgz#9471efa3ef9128d2f7c6a7ca39c4dd6b5055b132" integrity sha512-gTpR5XQNKFwOd4clxfnhaqvfqMpqEwr4tOtCyz4MtYZX2JYhfr1JvBFKdS+7K/9rfpZR3VLX+YWBbKoxCgS43Q== @@ -8370,11 +8248,6 @@ vendors@^1.0.0: resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.4.tgz#e2b800a53e7a29b93506c3cf41100d16c4c4ad8e" integrity sha512-/juG65kTL4Cy2su4P8HjtkTxk6VmJDiOPBufWniqQ6wknac6jNiXS9vU+hO3wgusiyqWlzTbVHi0dyJqRONg3w== -vfile-location@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-3.1.0.tgz#81cd8a04b0ac935185f4fce16f270503fc2f692f" - integrity sha512-FCZ4AN9xMcjFIG1oGmZKo61PjwJHRVA+0/tPUP2ul4uIwjGGndIxavEMRpWn5p4xwm/ZsdXp9YNygf1ZyE4x8g== - vfile-message@^2.0.0: version "2.0.4" resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-2.0.4.tgz#5b43b88171d409eae58477d13f23dd41d52c371a" @@ -8384,13 +8257,12 @@ vfile-message@^2.0.0: unist-util-stringify-position "^2.0.0" vfile@^4.0.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/vfile/-/vfile-4.2.0.tgz#26c78ac92eb70816b01d4565e003b7e65a2a0e01" - integrity sha512-a/alcwCvtuc8OX92rqqo7PflxiCgXRFjdyoGVuYV+qbgCb0GgZJRvIgCD4+U/Kl1yhaRsaTwksF88xbPyGsgpw== + version "4.2.1" + resolved "https://registry.yarnpkg.com/vfile/-/vfile-4.2.1.tgz#03f1dce28fc625c625bc6514350fbdb00fa9e624" + integrity sha512-O6AE4OskCG5S1emQ/4gl8zK586RqA3srz3nfK/Viy0UPToBc5Trp9BVFb1u0CjsKrAWwnpr4ifM/KBXPWwJbCA== dependencies: "@types/unist" "^2.0.0" is-buffer "^2.0.0" - replace-ext "1.0.0" unist-util-stringify-position "^2.0.0" vfile-message "^2.0.0" @@ -8402,9 +8274,9 @@ warning@^4.0.1, warning@^4.0.3: loose-envify "^1.0.0" watchpack@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.0.1.tgz#2f2192c542c82a3bcde76acd3411470c120426a8" - integrity sha512-vO8AKGX22ZRo6PiOFM9dC0re8IcKh8Kd/aH2zeqUc6w4/jBGlTy2P7fTC6ekT0NjVeGjgU2dGC5rNstKkeLEQg== + version "2.1.0" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.1.0.tgz#e63194736bf3aa22026f7b191cd57907b0f9f696" + integrity sha512-UjgD1mqjkG99+3lgG36at4wPnUXNvis2v1utwTgQ43C22c4LD71LsYMExdWXh4HZ+RmW+B0t1Vrg2GpXAkTOQw== dependencies: glob-to-regexp "^0.4.1" graceful-fs "^4.1.2" @@ -8417,28 +8289,29 @@ wbuf@^1.1.0, wbuf@^1.7.3: minimalistic-assert "^1.0.0" webpack-cli@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-4.2.0.tgz#10a09030ad2bd4d8b0f78322fba6ea43ec56aaaa" - integrity sha512-EIl3k88vaF4fSxWSgtAQR+VwicfLMTZ9amQtqS4o+TDPW9HGaEpbFBbAZ4A3ZOT5SOnMxNOzROsSTPiE8tBJPA== + version "4.4.0" + resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-4.4.0.tgz#38c7fa01ea31510f5c490245dd1bb28018792f1b" + integrity sha512-/Qh07CXfXEkMu5S8wEpjuaw2Zj/CC0hf/qbTDp6N8N7JjdGuaOjZ7kttz+zhuJO/J5m7alQEhNk9lsc4rC6xgQ== dependencies: - "@webpack-cli/info" "^1.1.0" - "@webpack-cli/serve" "^1.1.0" + "@discoveryjs/json-ext" "^0.5.0" + "@webpack-cli/configtest" "^1.0.0" + "@webpack-cli/info" "^1.2.1" + "@webpack-cli/serve" "^1.2.2" colorette "^1.2.1" - command-line-usage "^6.1.0" commander "^6.2.0" enquirer "^2.3.6" - execa "^4.1.0" + execa "^5.0.0" + fastest-levenshtein "^1.0.12" import-local "^3.0.2" interpret "^2.2.0" - leven "^3.1.0" rechoir "^0.7.0" v8-compile-cache "^2.2.0" - webpack-merge "^4.2.2" + webpack-merge "^5.7.3" webpack-dev-middleware@^3.7.2: - version "3.7.2" - resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.7.2.tgz#0019c3db716e3fa5cecbf64f2ab88a74bab331f3" - integrity sha512-1xC42LxbYoqLNAhV6YzTYacicgMZQTqRd27Sim9wn5hJrX3I5nxYy1SxSd4+gjUFsz1dQFj+yEe6zEVmSkeJjw== + version "3.7.3" + resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.7.3.tgz#0639372b143262e2b84ab95d3b91a7597061c2c5" + integrity sha512-djelc/zGiz9nZj/U7PTBi2ViorGJXEWo/3ltkPbDyxCXhhEXkW0ce99falaok4TPj+AsxLiXJR0EBOb0zh9fKQ== dependencies: memory-fs "^0.4.1" mime "^2.4.4" @@ -8447,9 +8320,9 @@ webpack-dev-middleware@^3.7.2: webpack-log "^2.0.0" webpack-dev-server@^3.11.0: - version "3.11.0" - resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-3.11.0.tgz#8f154a3bce1bcfd1cc618ef4e703278855e7ff8c" - integrity sha512-PUxZ+oSTxogFQgkTtFndEtJIPNmml7ExwufBZ9L2/Xyyd5PnOL5UreWe5ZT7IU25DSdykL9p1MLQzmLh2ljSeg== + version "3.11.2" + resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-3.11.2.tgz#695ebced76a4929f0d5de7fd73fafe185fe33708" + integrity sha512-A80BkuHRQfCiNtGBS1EMf2ChTUs0x+B3wGDFmOeT4rmJOHhHTCH2naNxIHhmkr0/UillP4U3yeIyv1pNp+QDLQ== dependencies: ansi-html "0.0.7" bonjour "^3.5.0" @@ -8471,11 +8344,11 @@ webpack-dev-server@^3.11.0: p-retry "^3.0.1" portfinder "^1.0.26" schema-utils "^1.0.0" - selfsigned "^1.10.7" + selfsigned "^1.10.8" semver "^6.3.0" serve-index "^1.9.1" - sockjs "0.3.20" - sockjs-client "1.4.0" + sockjs "^0.3.21" + sockjs-client "^1.5.0" spdy "^4.0.2" strip-ansi "^3.0.1" supports-color "^6.1.0" @@ -8493,17 +8366,10 @@ webpack-log@^2.0.0: ansi-colors "^3.0.0" uuid "^3.3.2" -webpack-merge@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-4.2.2.tgz#a27c52ea783d1398afd2087f547d7b9d2f43634d" - integrity sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g== - dependencies: - lodash "^4.17.15" - -webpack-merge@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.2.0.tgz#31cbcc954f8f89cd4b06ca8d97a38549f7f3f0c9" - integrity sha512-QBglJBg5+lItm3/Lopv8KDDK01+hjdg2azEwi/4vKJ8ZmGPdtJsTpjtNNOW3a4WiqzXdCATtTudOZJngE7RKkA== +webpack-merge@^5.2.0, webpack-merge@^5.7.3: + version "5.7.3" + resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.7.3.tgz#2a0754e1877a25a8bbab3d2475ca70a052708213" + integrity sha512-6/JUQv0ELQ1igjGDzHkXbVDRxkfA57Zw7PfiupdLFJYrgFqY5ZP8xxbpp2lU3EPwYx89ht5Z/aDkD40hFCm5AA== dependencies: clone-deep "^4.0.1" wildcard "^2.0.0" @@ -8525,43 +8391,36 @@ webpack-sources@^2.1.1: source-map "^0.6.1" webpack@^5.10.0: - version "5.10.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.10.0.tgz#6f77c31522a2c525152d9c344f9765d168b3df08" - integrity sha512-P0bHAXmIz0zsNcHNLqFmLY1ZtrT+jtBr7FqpuDtA2o7GiHC+zBsfhgK7SmJ1HG7BAEb3G9JoMdSVi7mEDvG3Zg== + version "5.18.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.18.0.tgz#bbcf13094aa0da0534d513f27d7ee72d74e499c6" + integrity sha512-RmiP/iy6ROvVe/S+u0TrvL/oOmvP+2+Bs8MWjvBwwY/j82Q51XJyDJ75m0QAGntL1Wx6B//Xc0+4VPP/hlNHmw== dependencies: "@types/eslint-scope" "^3.7.0" - "@types/estree" "^0.0.45" - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/helper-module-context" "1.9.0" - "@webassemblyjs/wasm-edit" "1.9.0" - "@webassemblyjs/wasm-parser" "1.9.0" + "@types/estree" "^0.0.46" + "@webassemblyjs/ast" "1.11.0" + "@webassemblyjs/wasm-edit" "1.11.0" + "@webassemblyjs/wasm-parser" "1.11.0" acorn "^8.0.4" browserslist "^4.14.5" chrome-trace-event "^1.0.2" - enhanced-resolve "^5.3.1" + enhanced-resolve "^5.7.0" + es-module-lexer "^0.3.26" eslint-scope "^5.1.1" events "^3.2.0" glob-to-regexp "^0.4.1" graceful-fs "^4.2.4" json-parse-better-errors "^1.0.2" - loader-runner "^4.1.0" + loader-runner "^4.2.0" mime-types "^2.1.27" neo-async "^2.6.2" pkg-dir "^5.0.0" schema-utils "^3.0.0" tapable "^2.1.1" - terser-webpack-plugin "^5.0.3" + terser-webpack-plugin "^5.1.1" watchpack "^2.0.0" webpack-sources "^2.1.1" -websocket-driver@0.6.5: - version "0.6.5" - resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.6.5.tgz#5cb2556ceb85f4373c6d8238aa691c8454e13a36" - integrity sha1-XLJVbOuF9Dc8bYI4qmkchFThOjY= - dependencies: - websocket-extensions ">=0.1.1" - -websocket-driver@>=0.5.1: +websocket-driver@>=0.5.1, websocket-driver@^0.7.4: version "0.7.4" resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760" integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg== @@ -8580,11 +8439,6 @@ which-module@^2.0.0: resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= -which-pm-runs@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/which-pm-runs/-/which-pm-runs-1.0.0.tgz#670b3afbc552e0b55df6b7780ca74615f23ad1cb" - integrity sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs= - which@^1.2.9, which@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" @@ -8609,14 +8463,6 @@ word-wrap@^1.2.3: resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== -wordwrapjs@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/wordwrapjs/-/wordwrapjs-4.0.0.tgz#9aa9394155993476e831ba8e59fb5795ebde6800" - integrity sha512-Svqw723a3R34KvsMgpjFBYCgNOSdcW3mQFK4wIfhGQhtaFVOJmdYoXgi63ne3dTlWgatVcUc7t4HtQ/+bUVIzQ== - dependencies: - reduce-flatten "^2.0.0" - typical "^5.0.0" - wrap-ansi@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" @@ -8641,13 +8487,6 @@ write-file-atomic@^3.0.3: signal-exit "^3.0.2" typedarray-to-buffer "^3.1.5" -write@1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3" - integrity sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig== - dependencies: - mkdirp "^0.5.1" - ws@^6.2.1: version "6.2.1" resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb" @@ -8655,11 +8494,6 @@ ws@^6.2.1: dependencies: async-limiter "~1.0.0" -xtend@^4.0.0, xtend@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" - integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== - y18n@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.1.tgz#8db2b83c31c5d75099bb890b23f3094891e247d4" @@ -8683,13 +8517,10 @@ yargs-parser@^13.1.2: camelcase "^5.0.0" decamelize "^1.2.0" -yargs-parser@^18.1.3: - version "18.1.3" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" - integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== - dependencies: - camelcase "^5.0.0" - decamelize "^1.2.0" +yargs-parser@^20.2.3: + version "20.2.4" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" + integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== yargs@^13.3.2: version "13.3.2" @@ -8711,3 +8542,13 @@ yn@3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== + +zwitch@^1.0.0: + version "1.0.5" + resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-1.0.5.tgz#d11d7381ffed16b742f6af7b3f223d5cd9fe9920" + integrity sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw== From 2638e271fed1937ceafaefeefe8086cb249eaf72 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Thu, 4 Feb 2021 14:15:34 +0300 Subject: [PATCH 087/141] Pull request: all: use http method constants Merge in DNS/adguard-home from method-const to master Squashed commit of the following: commit ae6d6699a25ca04ba92aa53258d46e50233a9e00 Author: Ainar Garipov Date: Fri Jan 29 22:03:59 2021 +0300 all: use http method constants --- internal/dnsfilter/blocked.go | 4 ++-- internal/dnsfilter/rewrites.go | 6 +++--- internal/dnsfilter/safebrowsing.go | 18 +++++++++--------- internal/home/clientshttp.go | 10 +++++----- internal/home/controlfiltering.go | 16 ++++++++-------- internal/home/tls.go | 6 +++--- internal/querylog/http.go | 8 ++++---- internal/stats/http.go | 8 ++++---- scripts/make/go-lint.sh | 24 ++++++++++++++++++++++++ 9 files changed, 62 insertions(+), 38 deletions(-) diff --git a/internal/dnsfilter/blocked.go b/internal/dnsfilter/blocked.go index 48b02932..473010c0 100644 --- a/internal/dnsfilter/blocked.go +++ b/internal/dnsfilter/blocked.go @@ -242,6 +242,6 @@ func (d *DNSFilter) handleBlockedServicesSet(w http.ResponseWriter, r *http.Requ // registerBlockedServicesHandlers - register HTTP handlers func (d *DNSFilter) registerBlockedServicesHandlers() { - d.Config.HTTPRegister("GET", "/control/blocked_services/list", d.handleBlockedServicesList) - d.Config.HTTPRegister("POST", "/control/blocked_services/set", d.handleBlockedServicesSet) + d.Config.HTTPRegister(http.MethodGet, "/control/blocked_services/list", d.handleBlockedServicesList) + d.Config.HTTPRegister(http.MethodPost, "/control/blocked_services/set", d.handleBlockedServicesSet) } diff --git a/internal/dnsfilter/rewrites.go b/internal/dnsfilter/rewrites.go index 8db1fd0b..146be60a 100644 --- a/internal/dnsfilter/rewrites.go +++ b/internal/dnsfilter/rewrites.go @@ -219,7 +219,7 @@ func (d *DNSFilter) handleRewriteDelete(w http.ResponseWriter, r *http.Request) } func (d *DNSFilter) registerRewritesHandlers() { - d.Config.HTTPRegister("GET", "/control/rewrite/list", d.handleRewriteList) - d.Config.HTTPRegister("POST", "/control/rewrite/add", d.handleRewriteAdd) - d.Config.HTTPRegister("POST", "/control/rewrite/delete", d.handleRewriteDelete) + d.Config.HTTPRegister(http.MethodGet, "/control/rewrite/list", d.handleRewriteList) + d.Config.HTTPRegister(http.MethodPost, "/control/rewrite/add", d.handleRewriteAdd) + d.Config.HTTPRegister(http.MethodPost, "/control/rewrite/delete", d.handleRewriteDelete) } diff --git a/internal/dnsfilter/safebrowsing.go b/internal/dnsfilter/safebrowsing.go index ad99bf80..9a14b584 100644 --- a/internal/dnsfilter/safebrowsing.go +++ b/internal/dnsfilter/safebrowsing.go @@ -381,15 +381,15 @@ func (d *DNSFilter) handleParentalStatus(w http.ResponseWriter, r *http.Request) } func (d *DNSFilter) registerSecurityHandlers() { - d.Config.HTTPRegister("POST", "/control/safebrowsing/enable", d.handleSafeBrowsingEnable) - d.Config.HTTPRegister("POST", "/control/safebrowsing/disable", d.handleSafeBrowsingDisable) - d.Config.HTTPRegister("GET", "/control/safebrowsing/status", d.handleSafeBrowsingStatus) + d.Config.HTTPRegister(http.MethodPost, "/control/safebrowsing/enable", d.handleSafeBrowsingEnable) + d.Config.HTTPRegister(http.MethodPost, "/control/safebrowsing/disable", d.handleSafeBrowsingDisable) + d.Config.HTTPRegister(http.MethodGet, "/control/safebrowsing/status", d.handleSafeBrowsingStatus) - d.Config.HTTPRegister("POST", "/control/parental/enable", d.handleParentalEnable) - d.Config.HTTPRegister("POST", "/control/parental/disable", d.handleParentalDisable) - d.Config.HTTPRegister("GET", "/control/parental/status", d.handleParentalStatus) + d.Config.HTTPRegister(http.MethodPost, "/control/parental/enable", d.handleParentalEnable) + d.Config.HTTPRegister(http.MethodPost, "/control/parental/disable", d.handleParentalDisable) + d.Config.HTTPRegister(http.MethodGet, "/control/parental/status", d.handleParentalStatus) - d.Config.HTTPRegister("POST", "/control/safesearch/enable", d.handleSafeSearchEnable) - d.Config.HTTPRegister("POST", "/control/safesearch/disable", d.handleSafeSearchDisable) - d.Config.HTTPRegister("GET", "/control/safesearch/status", d.handleSafeSearchStatus) + d.Config.HTTPRegister(http.MethodPost, "/control/safesearch/enable", d.handleSafeSearchEnable) + d.Config.HTTPRegister(http.MethodPost, "/control/safesearch/disable", d.handleSafeSearchDisable) + d.Config.HTTPRegister(http.MethodGet, "/control/safesearch/status", d.handleSafeSearchStatus) } diff --git a/internal/home/clientshttp.go b/internal/home/clientshttp.go index edeaf585..23930411 100644 --- a/internal/home/clientshttp.go +++ b/internal/home/clientshttp.go @@ -297,9 +297,9 @@ func (clients *clientsContainer) findTemporary(ip net.IP, idStr string) (cj clie // RegisterClientsHandlers registers HTTP handlers func (clients *clientsContainer) registerWebHandlers() { - httpRegister("GET", "/control/clients", clients.handleGetClients) - httpRegister("POST", "/control/clients/add", clients.handleAddClient) - httpRegister("POST", "/control/clients/delete", clients.handleDelClient) - httpRegister("POST", "/control/clients/update", clients.handleUpdateClient) - httpRegister("GET", "/control/clients/find", clients.handleFindClient) + httpRegister(http.MethodGet, "/control/clients", clients.handleGetClients) + httpRegister(http.MethodPost, "/control/clients/add", clients.handleAddClient) + httpRegister(http.MethodPost, "/control/clients/delete", clients.handleDelClient) + httpRegister(http.MethodPost, "/control/clients/update", clients.handleUpdateClient) + httpRegister(http.MethodGet, "/control/clients/find", clients.handleFindClient) } diff --git a/internal/home/controlfiltering.go b/internal/home/controlfiltering.go index 53ca0445..b64423d1 100644 --- a/internal/home/controlfiltering.go +++ b/internal/home/controlfiltering.go @@ -417,14 +417,14 @@ func (f *Filtering) handleCheckHost(w http.ResponseWriter, r *http.Request) { // RegisterFilteringHandlers - register handlers func (f *Filtering) RegisterFilteringHandlers() { - httpRegister("GET", "/control/filtering/status", f.handleFilteringStatus) - httpRegister("POST", "/control/filtering/config", f.handleFilteringConfig) - httpRegister("POST", "/control/filtering/add_url", f.handleFilteringAddURL) - httpRegister("POST", "/control/filtering/remove_url", f.handleFilteringRemoveURL) - httpRegister("POST", "/control/filtering/set_url", f.handleFilteringSetURL) - httpRegister("POST", "/control/filtering/refresh", f.handleFilteringRefresh) - httpRegister("POST", "/control/filtering/set_rules", f.handleFilteringSetRules) - httpRegister("GET", "/control/filtering/check_host", f.handleCheckHost) + httpRegister(http.MethodGet, "/control/filtering/status", f.handleFilteringStatus) + httpRegister(http.MethodPost, "/control/filtering/config", f.handleFilteringConfig) + httpRegister(http.MethodPost, "/control/filtering/add_url", f.handleFilteringAddURL) + httpRegister(http.MethodPost, "/control/filtering/remove_url", f.handleFilteringRemoveURL) + httpRegister(http.MethodPost, "/control/filtering/set_url", f.handleFilteringSetURL) + httpRegister(http.MethodPost, "/control/filtering/refresh", f.handleFilteringRefresh) + httpRegister(http.MethodPost, "/control/filtering/set_rules", f.handleFilteringSetRules) + httpRegister(http.MethodGet, "/control/filtering/check_host", f.handleCheckHost) } func checkFiltersUpdateIntervalHours(i uint32) bool { diff --git a/internal/home/tls.go b/internal/home/tls.go index 1f68cc39..d97207ee 100644 --- a/internal/home/tls.go +++ b/internal/home/tls.go @@ -544,7 +544,7 @@ func marshalTLS(w http.ResponseWriter, data tlsConfig) { // registerWebHandlers registers HTTP handlers for TLS configuration func (t *TLSMod) registerWebHandlers() { - httpRegister("GET", "/control/tls/status", t.handleTLSStatus) - httpRegister("POST", "/control/tls/configure", t.handleTLSConfigure) - httpRegister("POST", "/control/tls/validate", t.handleTLSValidate) + httpRegister(http.MethodGet, "/control/tls/status", t.handleTLSStatus) + httpRegister(http.MethodPost, "/control/tls/configure", t.handleTLSConfigure) + httpRegister(http.MethodPost, "/control/tls/validate", t.handleTLSValidate) } diff --git a/internal/querylog/http.go b/internal/querylog/http.go index 9bc63b7e..8948f9f6 100644 --- a/internal/querylog/http.go +++ b/internal/querylog/http.go @@ -22,10 +22,10 @@ type qlogConfig struct { // Register web handlers func (l *queryLog) initWeb() { - l.conf.HTTPRegister("GET", "/control/querylog", l.handleQueryLog) - l.conf.HTTPRegister("GET", "/control/querylog_info", l.handleQueryLogInfo) - l.conf.HTTPRegister("POST", "/control/querylog_clear", l.handleQueryLogClear) - l.conf.HTTPRegister("POST", "/control/querylog_config", l.handleQueryLogConfig) + l.conf.HTTPRegister(http.MethodGet, "/control/querylog", l.handleQueryLog) + l.conf.HTTPRegister(http.MethodGet, "/control/querylog_info", l.handleQueryLogInfo) + l.conf.HTTPRegister(http.MethodPost, "/control/querylog_clear", l.handleQueryLogClear) + l.conf.HTTPRegister(http.MethodPost, "/control/querylog_config", l.handleQueryLogConfig) } func httpError(r *http.Request, w http.ResponseWriter, code int, format string, args ...interface{}) { diff --git a/internal/stats/http.go b/internal/stats/http.go index 4d7c0b15..1580174a 100644 --- a/internal/stats/http.go +++ b/internal/stats/http.go @@ -113,8 +113,8 @@ func (s *statsCtx) initWeb() { return } - s.conf.HTTPRegister("GET", "/control/stats", s.handleStats) - s.conf.HTTPRegister("POST", "/control/stats_reset", s.handleStatsReset) - s.conf.HTTPRegister("POST", "/control/stats_config", s.handleStatsConfig) - s.conf.HTTPRegister("GET", "/control/stats_info", s.handleStatsInfo) + s.conf.HTTPRegister(http.MethodGet, "/control/stats", s.handleStats) + s.conf.HTTPRegister(http.MethodPost, "/control/stats_reset", s.handleStatsReset) + s.conf.HTTPRegister(http.MethodPost, "/control/stats_config", s.handleStatsConfig) + s.conf.HTTPRegister(http.MethodGet, "/control/stats_info", s.handleStatsInfo) } diff --git a/scripts/make/go-lint.sh b/scripts/make/go-lint.sh index 391e8c51..59c4cbf1 100644 --- a/scripts/make/go-lint.sh +++ b/scripts/make/go-lint.sh @@ -23,6 +23,10 @@ fi # variables. set -f -u + + +# Deferred Helpers + not_found_msg=' looks like a binary not found error. make sure you have installed the linter binaries using: @@ -43,6 +47,10 @@ not_found() { } trap not_found EXIT + + +# Simple Analyzers + # blocklist_imports is a simple check against unwanted packages. # Currently it only looks for package log which is replaced by our own # package github.com/AdguardTeam/golibs/log. @@ -50,6 +58,12 @@ blocklist_imports() { git grep -F -e '"log"' -- '*.go' || exit 0; } +# method_const is a simple check against the usage of some raw strings +# and numbers where one should use named constants. +method_const() { + git grep -F -e '"GET"' -e '"POST"' -- '*.go' || exit 0; +} + # underscores is a simple check against Go filenames with underscores. underscores() { git ls-files '*_*.go' | { grep -F -e '_darwin.go' \ @@ -58,6 +72,10 @@ underscores() { -v || exit 0; } } + + +# Helpers + # exit_on_output exits with a nonzero exit code if there is anything in # the command's combined output. exit_on_output() ( @@ -98,8 +116,14 @@ exit_on_output() ( return "$exitcode" ) + + +# Checks + exit_on_output blocklist_imports +exit_on_output method_const + exit_on_output underscores exit_on_output gofumpt --extra -l -s . From fca566631a4cf7a3703ac51a849c267733b1b4c7 Mon Sep 17 00:00:00 2001 From: Andrey Meshkov Date: Thu, 4 Feb 2021 14:17:20 +0300 Subject: [PATCH 088/141] fix quickMatch for ClientID --- internal/querylog/searchcriteria.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/querylog/searchcriteria.go b/internal/querylog/searchcriteria.go index 4db39bf9..68672eaf 100644 --- a/internal/querylog/searchcriteria.go +++ b/internal/querylog/searchcriteria.go @@ -57,7 +57,7 @@ func (c *searchCriteria) quickMatch(line string) bool { case ctDomainOrClient: return c.quickMatchJSONValue(line, "QH") || c.quickMatchJSONValue(line, "IP") || - c.quickMatchJSONValue(line, "ID") + c.quickMatchJSONValue(line, "CID") default: return true } From 5081ead0f49383a0f1438498d961d258974e6803 Mon Sep 17 00:00:00 2001 From: Andrey Meshkov Date: Thu, 4 Feb 2021 14:28:37 +0300 Subject: [PATCH 089/141] Added cloudron and glinet to README. Closes #2433 --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 6b60d888..41b5ab43 100644 --- a/README.md +++ b/README.md @@ -314,6 +314,8 @@ Here's what you can also do to contribute: * Hass.io add-on (https://github.com/hassio-addons/addon-adguard-home) * OpenWrt LUCI app (https://github.com/kongfl888/luci-app-adguardhome) * Prometheus exporter for AdGuard Home (https://github.com/ebrianne/adguard-exporter) +* Cloudron app (https://git.cloudron.io/cloudron/adguard-home-app) +* AdGuard Home on GLInet routers (https://forum.gl-inet.com/t/adguardhome-on-gl-routers/10664) ## Acknowledgments From 676f8c76712d7baece4c355734e9a07b4e1d6ab3 Mon Sep 17 00:00:00 2001 From: Andrey Meshkov Date: Thu, 4 Feb 2021 14:52:55 +0300 Subject: [PATCH 090/141] Remove 280blocker from Vetted list. Closes #2427 --- client/src/helpers/filters/filters.json | 6 ------ 1 file changed, 6 deletions(-) diff --git a/client/src/helpers/filters/filters.json b/client/src/helpers/filters/filters.json index 93f73854..7bc4c9c2 100644 --- a/client/src/helpers/filters/filters.json +++ b/client/src/helpers/filters/filters.json @@ -126,12 +126,6 @@ "homepage": "https://filtri-dns.ga/", "source": "https://filtri-dns.ga/filtri.txt" }, - "JPN-280blocker": { - "name": "JPN: 280blocker adblock domain lists", - "categoryId": "regional", - "homepage": "https://280blocker.net/", - "source": "https://280blocker.net/files/280blocker_domain.txt" - }, "IRN-unwanted-iranian-domains": { "name": "IRN: Unwanted Iranian domains", "categoryId": "regional", From fa33568fab9889d4467c0c5593d2bea8486e3342 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Thu, 4 Feb 2021 15:12:34 +0300 Subject: [PATCH 091/141] Pull request: all: imp errcheck in tests Merge in DNS/adguard-home from imp-errcheck to master Squashed commit of the following: commit ed046b8ef59a092a27c623cd14b3fc2ef305fc3d Author: Ainar Garipov Date: Thu Feb 4 15:00:04 2021 +0300 stats: imp cleanup more commit e53a9240d3e3eec2417c768b98c267a8cd54d992 Merge: da734317 676f8c76 Author: Ainar Garipov Date: Thu Feb 4 14:59:40 2021 +0300 Merge branch 'master' into imp-errcheck commit da734317035543b52e5a9030813084bdc92ba90a Author: Ainar Garipov Date: Thu Feb 4 14:37:26 2021 +0300 stats: imp cleanup commit 8b4ad150129111a09be6fa2944a21bd06ab8e5a1 Merge: 385c8a6c 5081ead0 Author: Ainar Garipov Date: Thu Feb 4 14:34:26 2021 +0300 Merge branch 'master' into imp-errcheck commit 385c8a6c91e3bf07a457da370c8cc77820b91600 Author: Ainar Garipov Date: Fri Jan 29 20:41:57 2021 +0300 all: imp errcheck in tests --- internal/dhcpd/dhcpd_test.go | 9 ++++++-- internal/dhcpd/v4_test.go | 9 +++++--- internal/dhcpd/v6_test.go | 9 +++++--- internal/dnsfilter/dnsfilter_test.go | 5 +++- internal/dnsforward/dnsforward_test.go | 12 ++++++---- internal/dnsforward/dnsrewrite_test.go | 32 +++++++++++++++----------- internal/home/auth_test.go | 1 - internal/home/middlewares_test.go | 5 +++- internal/stats/stats_test.go | 11 ++++++--- scripts/make/go-lint.sh | 5 ++-- 10 files changed, 65 insertions(+), 33 deletions(-) diff --git a/internal/dhcpd/dhcpd_test.go b/internal/dhcpd/dhcpd_test.go index f65e6823..f5c34154 100644 --- a/internal/dhcpd/dhcpd_test.go +++ b/internal/dhcpd/dhcpd_test.go @@ -45,12 +45,17 @@ func TestDB(t *testing.T) { l.HWAddr, _ = net.ParseMAC("aa:aa:aa:aa:aa:aa") exp1 := time.Now().Add(time.Hour) l.Expiry = exp1 - s.srv4.(*v4Server).addLease(&l) + + srv4, ok := s.srv4.(*v4Server) + assert.True(t, ok) + + srv4.addLease(&l) l2 := Lease{} l2.IP = net.IP{192, 168, 10, 101} l2.HWAddr, _ = net.ParseMAC("aa:aa:aa:aa:aa:bb") - s.srv4.AddStaticLease(l2) + err = s.srv4.AddStaticLease(l2) + assert.Nil(t, err) _ = os.Remove("leases.db") s.dbStore() diff --git a/internal/dhcpd/v4_test.go b/internal/dhcpd/v4_test.go index 74eab484..8edb3113 100644 --- a/internal/dhcpd/v4_test.go +++ b/internal/dhcpd/v4_test.go @@ -69,7 +69,8 @@ func TestV4StaticLeaseAddReplaceDynamic(t *testing.T) { notify: notify4, } sIface, err := v4Create(conf) - s := sIface.(*v4Server) + s, ok := sIface.(*v4Server) + assert.True(t, ok) assert.Nil(t, err) // add dynamic lease @@ -121,7 +122,8 @@ func TestV4StaticLeaseGet(t *testing.T) { notify: notify4, } sIface, err := v4Create(conf) - s := sIface.(*v4Server) + s, ok := sIface.(*v4Server) + assert.True(t, ok) assert.Nil(t, err) s.conf.dnsIPAddrs = []net.IP{{192, 168, 10, 1}} @@ -184,7 +186,8 @@ func TestV4DynamicLeaseGet(t *testing.T) { }, } sIface, err := v4Create(conf) - s := sIface.(*v4Server) + s, ok := sIface.(*v4Server) + assert.True(t, ok) assert.Nil(t, err) s.conf.dnsIPAddrs = []net.IP{{192, 168, 10, 1}} diff --git a/internal/dhcpd/v6_test.go b/internal/dhcpd/v6_test.go index 1c54163a..9cdf3ee4 100644 --- a/internal/dhcpd/v6_test.go +++ b/internal/dhcpd/v6_test.go @@ -64,7 +64,8 @@ func TestV6StaticLeaseAddReplaceDynamic(t *testing.T) { notify: notify6, } sIface, err := v6Create(conf) - s := sIface.(*v6Server) + s, ok := sIface.(*v6Server) + assert.True(t, ok) assert.Nil(t, err) // add dynamic lease @@ -113,7 +114,8 @@ func TestV6GetLease(t *testing.T) { notify: notify6, } sIface, err := v6Create(conf) - s := sIface.(*v6Server) + s, ok := sIface.(*v6Server) + assert.True(t, ok) assert.Nil(t, err) s.conf.dnsIPAddrs = []net.IP{net.ParseIP("2000::1")} s.sid = dhcpv6.Duid{ @@ -173,7 +175,8 @@ func TestV6GetDynamicLease(t *testing.T) { notify: notify6, } sIface, err := v6Create(conf) - s := sIface.(*v6Server) + s, ok := sIface.(*v6Server) + assert.True(t, ok) assert.Nil(t, err) s.conf.dnsIPAddrs = []net.IP{net.ParseIP("2000::1")} s.sid = dhcpv6.Duid{ diff --git a/internal/dnsfilter/dnsfilter_test.go b/internal/dnsfilter/dnsfilter_test.go index 257953ad..6ab4fbc4 100644 --- a/internal/dnsfilter/dnsfilter_test.go +++ b/internal/dnsfilter/dnsfilter_test.go @@ -683,7 +683,10 @@ func TestWhitelist(t *testing.T) { ID: 0, Data: []byte(whiteRules), }} d := newForTest(nil, filters) - d.SetFilters(filters, whiteFilters, false) + + err := d.SetFilters(filters, whiteFilters, false) + assert.Nil(t, err) + t.Cleanup(d.Close) // Matched by white filter. diff --git a/internal/dnsforward/dnsforward_test.go b/internal/dnsforward/dnsforward_test.go index 86356c6b..9b72dd52 100644 --- a/internal/dnsforward/dnsforward_test.go +++ b/internal/dnsforward/dnsforward_test.go @@ -1113,8 +1113,10 @@ func TestPTRResponseFromDHCPLeases(t *testing.T) { assert.Equal(t, dns.TypePTR, resp.Answer[0].Header().Rrtype) assert.Equal(t, "1.0.0.127.in-addr.arpa.", resp.Answer[0].Header().Name) - ptr := resp.Answer[0].(*dns.PTR) - assert.Equal(t, "localhost.", ptr.Ptr) + ptr, ok := resp.Answer[0].(*dns.PTR) + if assert.True(t, ok) { + assert.Equal(t, "localhost.", ptr.Ptr) + } s.Close() } @@ -1158,8 +1160,10 @@ func TestPTRResponseFromHosts(t *testing.T) { assert.Equal(t, dns.TypePTR, resp.Answer[0].Header().Rrtype) assert.Equal(t, "1.0.0.127.in-addr.arpa.", resp.Answer[0].Header().Name) - ptr := resp.Answer[0].(*dns.PTR) - assert.Equal(t, "host.", ptr.Ptr) + ptr, ok := resp.Answer[0].(*dns.PTR) + if assert.True(t, ok) { + assert.Equal(t, "host.", ptr.Ptr) + } s.Close() } diff --git a/internal/dnsforward/dnsrewrite_test.go b/internal/dnsforward/dnsrewrite_test.go index b3f5ecf3..4029038a 100644 --- a/internal/dnsforward/dnsrewrite_test.go +++ b/internal/dnsforward/dnsrewrite_test.go @@ -130,9 +130,11 @@ func TestServer_FilterDNSRewrite(t *testing.T) { assert.Nil(t, err) assert.Equal(t, dns.RcodeSuccess, d.Res.Rcode) if assert.Len(t, d.Res.Answer, 1) { - ans := d.Res.Answer[0].(*dns.MX) - assert.Equal(t, mx.Exchange, ans.Mx) - assert.Equal(t, mx.Preference, ans.Preference) + ans, ok := d.Res.Answer[0].(*dns.MX) + if assert.True(t, ok) { + assert.Equal(t, mx.Exchange, ans.Mx) + assert.Equal(t, mx.Preference, ans.Preference) + } } }) @@ -145,11 +147,13 @@ func TestServer_FilterDNSRewrite(t *testing.T) { assert.Nil(t, err) assert.Equal(t, dns.RcodeSuccess, d.Res.Rcode) if assert.Len(t, d.Res.Answer, 1) { - ans := d.Res.Answer[0].(*dns.SVCB) - assert.Equal(t, dns.SVCB_ALPN, ans.Value[0].Key()) - assert.Equal(t, svcb.Params["alpn"], ans.Value[0].String()) - assert.Equal(t, svcb.Target, ans.Target) - assert.Equal(t, svcb.Priority, ans.Priority) + ans, ok := d.Res.Answer[0].(*dns.SVCB) + if assert.True(t, ok) { + assert.Equal(t, dns.SVCB_ALPN, ans.Value[0].Key()) + assert.Equal(t, svcb.Params["alpn"], ans.Value[0].String()) + assert.Equal(t, svcb.Target, ans.Target) + assert.Equal(t, svcb.Priority, ans.Priority) + } } }) @@ -162,11 +166,13 @@ func TestServer_FilterDNSRewrite(t *testing.T) { assert.Nil(t, err) assert.Equal(t, dns.RcodeSuccess, d.Res.Rcode) if assert.Len(t, d.Res.Answer, 1) { - ans := d.Res.Answer[0].(*dns.HTTPS) - assert.Equal(t, dns.SVCB_ALPN, ans.Value[0].Key()) - assert.Equal(t, svcb.Params["alpn"], ans.Value[0].String()) - assert.Equal(t, svcb.Target, ans.Target) - assert.Equal(t, svcb.Priority, ans.Priority) + ans, ok := d.Res.Answer[0].(*dns.HTTPS) + if assert.True(t, ok) { + assert.Equal(t, dns.SVCB_ALPN, ans.Value[0].Key()) + assert.Equal(t, svcb.Params["alpn"], ans.Value[0].String()) + assert.Equal(t, svcb.Target, ans.Target) + assert.Equal(t, svcb.Priority, ans.Priority) + } } }) } diff --git a/internal/home/auth_test.go b/internal/home/auth_test.go index 4a4a21c8..e44b7e83 100644 --- a/internal/home/auth_test.go +++ b/internal/home/auth_test.go @@ -79,7 +79,6 @@ func TestAuth(t *testing.T) { assert.Equal(t, checkSessionNotFound, a.checkSession(sessStr)) a.Close() - os.Remove(fn) } // implements http.ResponseWriter diff --git a/internal/home/middlewares_test.go b/internal/home/middlewares_test.go index 4d6a33d0..53b7a933 100644 --- a/internal/home/middlewares_test.go +++ b/internal/home/middlewares_test.go @@ -42,7 +42,10 @@ func TestLimitRequestBody(t *testing.T) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var b []byte b, *err = ioutil.ReadAll(r.Body) - w.Write(b) + _, werr := w.Write(b) + if werr != nil { + panic(werr) + } }) } diff --git a/internal/stats/stats_test.go b/internal/stats/stats_test.go index c4fbe191..06163c6c 100644 --- a/internal/stats/stats_test.go +++ b/internal/stats/stats_test.go @@ -34,6 +34,10 @@ func TestStats(t *testing.T) { Filename: "./stats.db", LimitDays: 1, } + t.Cleanup(func() { + assert.Nil(t, os.Remove(conf.Filename)) + }) + s, _ := createObject(conf) e := Entry{} @@ -86,7 +90,6 @@ func TestStats(t *testing.T) { s.clear() s.Close() - os.Remove(conf.Filename) } func TestLargeNumbers(t *testing.T) { @@ -102,7 +105,10 @@ func TestLargeNumbers(t *testing.T) { LimitDays: 1, UnitID: newID, } - os.Remove(conf.Filename) + t.Cleanup(func() { + assert.Nil(t, os.Remove(conf.Filename)) + }) + s, _ := createObject(conf) e := Entry{} @@ -128,7 +134,6 @@ func TestLargeNumbers(t *testing.T) { assert.EqualValues(t, int(hour)*n, d.NumDNSQueries) s.Close() - os.Remove(conf.Filename) } // this code is a chunk copied from getData() that generates aggregate data per day diff --git a/scripts/make/go-lint.sh b/scripts/make/go-lint.sh index 59c4cbf1..e1aeb9ef 100644 --- a/scripts/make/go-lint.sh +++ b/scripts/make/go-lint.sh @@ -140,7 +140,8 @@ ineffassign . unparam ./... -git ls-files -- '*.go' '*.md' '*.yaml' '*.yml' 'Makefile'\ +git ls-files -- '*.go' '*.md' '*.mod' '*.sh' '*.yaml' '*.yml'\ + 'Makefile'\ | xargs misspell --error looppointer ./... @@ -157,7 +158,7 @@ nilness ./... # errcheck ./... exit_on_output sh -c ' errcheck --asserts --ignoregenerated ./... |\ - { grep -e "defer" -e "_test\.go:" -v || exit 0; } + { grep -e "defer" -v || exit 0; } ' staticcheck ./... From 8aec08727cdbc4d626b144dd22d04adecfb42e35 Mon Sep 17 00:00:00 2001 From: Andrey Meshkov Date: Thu, 4 Feb 2021 18:16:01 +0300 Subject: [PATCH 092/141] Added several blocked services Closes #2224 Closes #2401 Squashed commit of the following: commit 8d422091187d03273393775ddc691bdd2a6913f4 Merge: 7a8f598b fa33568f Author: Andrey Meshkov Date: Thu Feb 4 17:57:32 2021 +0300 Merge branch 'master' into fix-2224 commit 7a8f598b19a877c19cb1537047c0ae14fa8a0064 Author: Andrey Meshkov Date: Thu Feb 4 17:34:53 2021 +0300 Review comments commit 181db867fc56d89dd13a4d50a24922604eed4eae Author: Andrey Meshkov Date: Thu Feb 4 17:20:20 2021 +0300 fixed review comments commit fd5b0816d63952664c6e89a91494ca09dc4fc52d Author: Ildar Kamalov Date: Thu Feb 4 16:10:29 2021 +0300 + client: add service icons commit 724e0c26691488fdf7cd8215d200867ed3a86198 Author: Andrey Meshkov Date: Thu Feb 4 15:42:53 2021 +0300 Added several blocked services Closes #2224 Closes #2401 --- CHANGELOG.md | 3 + README.md | 2 +- client/src/components/ui/Icons.js | Bin 44350 -> 66115 bytes client/src/helpers/constants.js | 166 +++++++++++++++++++---------- internal/dnsfilter/blocked.go | 68 +++++++++++- internal/dnsfilter/blocked_test.go | 37 +++++++ 6 files changed, 217 insertions(+), 59 deletions(-) create mode 100644 internal/dnsfilter/blocked_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index b4af0e4a..858d11e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to ### Added +- Added more services to the "Blocked services" list ([#2224], [#2401]). - `ipset` subdomain matching, just like `dnsmasq` does ([#2179]). - Client ID support for DNS-over-HTTPS, DNS-over-QUIC, and DNS-over-TLS ([#1383]). @@ -33,10 +34,12 @@ and this project adheres to [#1383]: https://github.com/AdguardTeam/AdGuardHome/issues/1383 [#2102]: https://github.com/AdguardTeam/AdGuardHome/issues/2102 [#2179]: https://github.com/AdguardTeam/AdGuardHome/issues/2179 +[#2224]: https://github.com/AdguardTeam/AdGuardHome/issues/2224 [#2302]: https://github.com/AdguardTeam/AdGuardHome/issues/2302 [#2304]: https://github.com/AdguardTeam/AdGuardHome/issues/2304 [#2305]: https://github.com/AdguardTeam/AdGuardHome/issues/2305 [#2337]: https://github.com/AdguardTeam/AdGuardHome/issues/2337 +[#2401]: https://github.com/AdguardTeam/AdGuardHome/issues/2401 ### Changed diff --git a/README.md b/README.md index 41b5ab43..5402dc4c 100644 --- a/README.md +++ b/README.md @@ -303,7 +303,7 @@ Here is a link to AdGuard Home project: https://crowdin.com/project/adguard-appl Here's what you can also do to contribute: 1. [Look for issues](https://github.com/AdguardTeam/AdGuardHome/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22+) marked as "help wanted". -2. Actualize the list of *Blocked services*. It it can be found in [dnsfilter/blocked_services.go](https://github.com/AdguardTeam/AdGuardHome/blob/master/internal/dnsfilter/blocked_services.go). +2. Actualize the list of *Blocked services*. It it can be found in [dnsfilter/blocked.go](https://github.com/AdguardTeam/AdGuardHome/blob/master/internal/dnsfilter/blocked.go). 3. Actualize the list of known *trackers*. It it can be found in [client/src/helpers/trackers/adguard.json](https://github.com/AdguardTeam/AdGuardHome/blob/master/client/src/helpers/trackers/adguard.json). 4. Actualize the list of vetted *blocklists*. It it can be found in [client/src/helpers/filters/filters.json](https://github.com/AdguardTeam/AdGuardHome/blob/master/client/src/helpers/filters/filters.json). diff --git a/client/src/components/ui/Icons.js b/client/src/components/ui/Icons.js index ec5bf8a32e76078aa5b971b9b786d312a671d17a..e2186529e6bd6f6552850df2820aa940f64470d2 100644 GIT binary patch delta 21180 zcmZvkJFhKAT81UCtrwnEp|MZKWKK6PQ$4TmuW7`Jf2>-cHn6}_4U$?%edClHER2%45z-o z)N$^HqPo&AYd4+Baw##=bg5HaYE@OY&eJ&2xx4hGTZiSQ@~+!1jM0x8Z;dY9rRxTU zQ)RvMLszHOy`QPR>n{3zSx)_WSgVUMh{Ue=`!+R#nqcl~p*fi1G)u%Ew*59y7x7x#DZvc-BsHI_gGeL%&!)TXeRTTyX(;HNc~8G$V*qS^UE!iIiW#P(Y4|C#EZjk8^~WhK&bcuLJYETZJ~G@zK0QD&uf zG4rXLdy|4$gSl!qcSZ?Fst%U_+uE&F4>TyNWsnkhgA8i&7+l@WJLc}#leeKdt1Uci zOonsG!|_sw&@zOfu^UxLm|))mE%<-xkr4@Nph$FLmV};LS<@m7Ofc36H4Gj_gRGZ6 zw#xRF*?`Ev+7Z9L5GigHLPE8wcAG&k>5&Gg0aEPonOf?acrHz|RmapQtyklY$rxy2 zDECl??;a8J>a!Pnnd>I-alew7RKQQcT`ZyrVV_Jc>|>LvG%fVz_jY5>R)> z{)99!$F2!SV=WXF4}6iVHw;Gr0^VF{Zthh=q-s=urV2%D+*7+P8rmo%TJOF4%M!-z znE08sXo)oF0)i$C4*#y;Ua)%5|FL`ev*Abo_RBy0_phG1KmNU^zq^0$-EUui_VSCTzyHObgy&YvNu*|5 zvLV3NPM{qlcagqf7fXxgwkWw}*pRYK-$ZKa`eHYZ#ieI>)NRZ>T-LOHM^h2mG{80* z5C^TW+B1}Ly2B9bZqSDtx7gWa#3_9aBrS~nF}Vy|cw#JlJ7oHByu^S2iVo$*Rxl4p zn1+u>SYQs_$|XYG@So|!9$S0t;L;MLSwp~YRC3#0PrOTmt{v!oeTB8k8$K;J&hTWz ze8T5Q-5F1Ejq$t#h1g*DD7dUs2-nyxU1p`_rOM8sz)a&)NN0`!idTGje}7Am;PAyCYmE(;eb8 zEVt!HqOEOQm>2n4?=b6qIfDSBe!#&xBeK&-dF`1I->ls8Q0KxA_=9wq`iUQ=xl&%I ziOyx7nHXP-Re&DmOY89W;yr>nd=W|^B!?>^y zLM>_v^{lo|r+YP`hOI!cX{HG%cw2rzDyq?lm~OP=_pTY8n}k#gTxFTY<|NvS;@x>e;GWL^EuDr zSg-uy1saB2tyAox_6DwQML%U(YhMbvZ*_Ou|fCm5{HxUMU%b z&LG2zyulO+<2<=Z-3VLkw& z-Ln$4;3rM+w9V6RfAaC`hwnc7;_KI6eBNf?PE-}T$tTUvsg z`k8so<(05>s@DPIF)+t`MqI$SwZLqsE6S8BC9F_k5wye8Vdys7N$8=x&x?Al-TP(y zuyn7`iJmc0#~y}sXXwa06+x%Om>9{cXbWf-rh}!7SrH@+q;a19dLG~V^~?KLA0K#! zYikRMu?LZHRh@2dcu5(gcS1#GeGcojXRE}@fK+T&Ml@)0IF2tc!Acil(FiRvheSXQ z<-(M+N@Zr!$lyVwBg8f$&7>G1@*nabBPc>ipp83L%^cA&e5YWjwviJk&9smav2wRW)^;= zFa;~_G$O>!lq10(-Jq6S4(uckc8K^!o;tz zNz@ zxpaf6sly0HZWFg>jU>p6(`KnPn-BSQsGKQGYU%C-LQ4@4B}e7~$&?b*&O&ZiT=@#= zgDQLE9DInZuO8e2rQWxx@I2c?k)Vs2Mj{N3iQ*`E2DZ=#qEGv?7#HY3360Av%h7>& z)#F%#L8F$2AvoJRkRH`j3>5j%=9nHmm^l|@_MvdGIQd)q;)eNVaeBxxDea59>98st zF`j-#Dh}cbqB2Qj%I!Tw#u!MYee*XRkdRoAoyy<&<&WQa{i~O|^#Wf&&hS_9#u`qb zA+osEU-{LM#8SkMF9R%K#>H9Y+aUmbPp=sZ>f|FB*wTOq6wR@|neE0&$nalk)dCMB zIlxx{6d@`eR)p~^Covp}V8IF?rCt#uDQXP-U^kKfQ`{d}2Ouo+qQs~IMQJKt49Wjm zHv*c>s)1^ezhmoZ1Z1AM)tYlhRRp}n zB3nZp2|fo6y$a=fRWsm*;qTc;G7B@;tW5=DvHG~h%< z0p;jKj*F?6rYSj`Q<4*J3IO(}tSD<5rxOy8NHS12MDUA* z5+=#W$Wc`5y&o1l0PUE`+SAltK9woL@GT$CLIaec0+5J!1QgPd2qQU+j$1%BZ#&=_ z&E-JCX?ftlWRoc5(JYk4>uoSAwBjX0H<|H>6FW z1=dDhR&$`Ko7=%NgCDjJ|YT4B6;e`6Lhi8ZU-hLQPJ`(JuN_> zX6E=nZh9PNP(wJ#)Flm3GLi^hH?eyjZ?o3G5@v?{Nlq{>@?q3Xk(5L*HQ}a%CoD-s zH!?yY^K^%%vW$31Bn2YBk4}U&M%q%<_S~mNF%wWH40)c&GD~C-(Zm!ltvif68q545 zpYSc?D!Q|6i52RK_r-%PwMLbL9_sg&{@X1?#$)${5$s@3uJL#f7%|K28+BWHG^&i# zTly(Lwd+Hz3c_DSAgC1wqadG@4yO|H7ScrB?v%j%<}}EQar&H=xg*rQ9r!cOFv5RD zGz^|9&6@JOg4%>z>Y8gpGWyu0ljyJ{bF>0J>3KRFc9$W#S1ow70tHCGjW7u4AJ`Z9 zda)?984*po(2v^iLL|6xzk#}zvNfh=FRurWEntNBM~D~`(l>8ls4nJH-4?EiWu_g& zFhT+^@DdtN)65J5)$IV6Un3x;H??6N*-nPL1Cay>VKd-N!9fb(5JeD6HjCbV269W? z@nls9b!=Y!0GI zKm{cZsP=i8@Sq^U{u%C^z=smlF`5^i8moQn1(+4@Qv$ zSN&$Cwa$V@iLO9HM0wb{2p8xD`Pv|GT+t6zjuGgX=_?d`iz2yTDe5uB5p5;yapcwV`jJ)o`Rw40cNFdIX^usr$ANDBEs@vkIY8ety%<@j1qT!Ia9NL|CTb6M-emGVS83Y%Q;`o`$j09EJ z;(KcIUIDeOd-FsQ?ToST+=C-jc?HZX_X0{H2l6{@I+bJuLxY0EU`ZCf1?pm)@=@vV z!X3#+c>Yim0wO6%C?0vprQ1vOL9G}SJ_L2Uaiun0CaG#Hbm+mL)Hg_-AvG*ZU7$-- zE=fe5z+x1oHq?xvWViqsSZO)o@haMFP6f4@R8Vcp5!*l590IdPCn5>EgmiKX6c-EZ zy(VQ{l1%z;X;~t-X0&y-f}j$)>15ooJVZ{<0h-Y#fd@xA-8cN)85<<#K>5%i;=*>^ zjqZe33L6CgY#AozMipO1a)7?+uT$5Y&OO&o-=>wUClfWqzHGu$N)Ad7(hI)#XqdEB zT5R!Hk%UEnxpIDZ80b%Au?M(R2HeW23b!7DCxaO=TsQgZ5hODySI{yjKxV^5mrtB9 z>)NRG0gVV+O|NXqIs#lo zjqr93Z;)}qDxi`aL@E?uPHe6LkIFPbR3*u%%Ul?BE#OcLXgA57Gq6Q}W9$K}LRs3% zBX$IBEp}kz$i{g+)-^7L`ydyg5%ZQ29q1N56??u7_M1b@o^3(2nw(kGi454A6x$*#l%kYD zHz5Whg&^S>hPN9vJf}MF+``MF4rC9>chA;x|NUk(a56Ly``;pcL@+DqzO&<~W!>q9 z%g+kLd82Nh{HFF;<}so-#|+-=hQ)G)g$eH?4p^Du7!F@DWW%+QONLe9EK%1ST)e!@ z3_`cyF9M-UXA)6?Y_s)PfUwd7_$Q{y;_6(*8Fv(LcQX}(g^>p|Q_>YYL~f0`%y437 z(%2sDn0$B37&BE_wkb_Dzy5g$E0yU%L-34Y=?)S&w_rPDMP}~2=p+CWABO*;oHY;(>=>gmz2Sqs>~tZ_QkXs?{#P&s0u)2bi+zAJ-27UMBcV!j)(+) zxwDO^ic{Qu<2vUhq0bqdf+2miJj0>s$ZrVG5sD+YElqYL)Ckrx6qiksjZr9>^pfEv zDS)dPhid2-n->-$`iYLhnegu4s7pSOUdXF#^ksF4P0G@R{~?zKvw(5sOgEhd)|8>L z(uQT(AO+S%cFpN-fmZ+;o@|T1ch3MZZlS7j>((Iq2x-;n-GZ#6CB`6gOx|1ARL)Fz zBHfk?^QldX)A3uwQM0sX22Tk=MKJKHSz`gI(`nQ-Y0rbm7leha3Mz`@Ix|a)&cx}c z5^AlHAf_(S6I2ej74RL<-^~;BspMLf6XFKY! zz{2G!Qj=q^Yf`iB<{@re?pk|Y=9~MOyLp9+br^zS(bcf7;|ADOPE5yDCP_J;GoP}6 z+|7cg+zrG<1H$48;i$`UTucN#xtNIP50!GiEE{*#mcmUeFrAxLVgxmvFCz}zc%+f8 zSrI-j59rgXOKe>obw_KZIa0@uDD+vGYktE`;eHdlSag+8m(x~$q+-#{zn&Xa%t0GQ zTs13Nr`v4Y?IJp&&~g8gVHe#>4Pqx&M#c8dtJZv z$?M0@Uw-%H!#9t+Sl}eA3uMAH6yH;0;RrPA!$`BJk;|Pdf2xm2i#N=#v?t;-cz z053d(F1FH)8-0v7aJBP|(X~Pbhf;Wg)8Lz_+sa)*Mw_+uvihPamqC@h5!tA@GR7$G zV}vDaHzRnqV?4%}dDnGJ(0AbuCc79GGiel=i$!VTisfGB?BUVV&{SK}hV?Vkb59QY zi%eqEbbXHQ>hE#o)yvJC0OaQc=@tQ{9d*J)m=@ z-!H>G&#TyXxB8d)ZsVs9`9T+sX|L)^UT?iVi23QrAqu7DN*R-&yx#=x+98nh+ZCi7dMOinUoSb)(r;T7s_{6CLO- zGq9-p#cNm5f7r-1+@34DGNN3P2F|A_BTv#2WR&MwN&^$j026 zb_~v<-XNJsh#>?PqzxF1dpN#GN`WEl?W$Y1l0_PE8&?sCAXbSVZx7svGRoQeUiT<5 z!#{xj1EY%O`m9!f4h@A6x(?ipYJy{_GOvE=Ygt7U_bLV_%#|70n_=eW;3C*VOY^jJ z0EWUT^@W>!cINdOHM3}27HJ_!u1}3HsuY*SDH8WkK0QK8A@PjCk;@=na=DmG)c7uJ z3usrmfs3ogN?c@#B4o4j_!_eO21qh;T^qtdNgrlV=3c9jfP*}dQFKtzhT+Wo`vX9* ztXedZf_wr%EN&_65mrJ}{x7~hUEQj5s^J=KjLrq#++k-+Q_ycJ!gvg#)BwseA}BVNTuv2QiEvPIA;-C;P(Rpym7SEwXCe4z=4ir ztog(fXHl`tz5K{t%w2FFZQ3Tu3Jg45WjQp9vIq>QcoE&OUD6%qb=MTBj?z&2k@S^j zwMha>S%G)sJ z)uIVEC>P$4zzYHh^@DyDhqXir36t-C2BC88StP&VA+EZ&uyv*Tw0+C&y4Tl@sPC82Gu~$2VRTO z@EDUODkJzKJ_z%J>NvdGirK;Hg+JOg+9ZytXw4K&c;FKz@(I!~XlNXkQ0hB7j0s># zcu5mQ`=CQNpB{OT1Q-`7#|*1Xp!~fJL37N4l;`}(RV~u5KdDFPVlGl@30-^n`eFbb z6ekhm*|Oquw79f&Fz*6bh4Jk~-o97QfW9*hJV+ zfOFogJ{xdFBo@TidldmTZa6F?&!f~`31NTY|0}57K*kbTGJAOp>k8HH=#s$ z()YH+p$~z<9?(T<865t`UM;_-9_Q}zaRSZZIb}@W)se4=SHW?GVK!X_(|$v`MXyi# zny+mY4mrC^50uD-EBJ|C0f}y;{O|6stjC*iDnQS1BL`x;Gez9 z-fmYn&F_;j0=l#YM{(Mo{aGU=sYg1NLJEKedJ7H%p40mkPSWbsb(8ZM4m7!Uq5f?3 z0RbTa!_OEAKAq2){cDHE3fE)XD4_s42ngV{nL2V4lohVA4=)72gOLW@PO9`VD#%qV zz~>bH@`eYmDn}t07j+JLyD3qSY8XQGU*uMP5J1d?cX- z**G;I<*SolqEZ#OdA7HqtnoNtQ9I)E_U#TlD_C?kS2r*AzeUt(5as!pGrm~XM|w3W z7s$6q;-h_M3thEzhEUmG7R58^=5|4fyjILcFHZd3m!%!bP(v5UC9haZNH4Nw<4gAcy20LEk$GIDR}xz>N-*09zswa1nyzik$?!&T#b8 zgIUoylXRx5UlH;*0>H)mV@pcux)b+k#SGo}S4r;*aYrP!?g#O@`%TuZB=#%0pr5e5J!mH@&@7K{m<$Q`S zzn1PqriZ(kNal*98UyLCbnvEX;GOifI!IAY7In5pl2dgt+Mu4O-e{Z)xLgL|XFv8b zmhzDpnw4NQ{aP#Vt|bB+%76s2ftVaC2v#;MTh+_aMv=AmO6EoQ)$>|82HEZ!?>1A8 zKpqeHk%_Pd`c-hj@lgC~OBDVhDFH0ptRFh3R;yo*C--{Q(s+p$-)X z-p-GVqJ0LV~@`*Y`bZ$^tQXRJ_xsP|mi{O9{p&qBn1QA@lAytZa>lG~? zZwLup(WzN~PXQAEqFi|#^!7H+1Xe~jv5*pl+aQIqwB81neofZNS&9f+$bhj_{uF>S zeZ8Y^2{yjaa_1{L2nneOWiFY}EOeGln^i=rZ?dSJ`s=N3WePP92?nyNc4sJ)*s`q2 zT#>X8fo;GmCXQ4Be}Dl`WR2kookA>;e@@rb7GtU3#TXKfcxyV;(7gtI$_XftLOv-m zh%Kia3Z^2b7vFLKPH zE!q9z_}8yL|K#SyMtH&j0g}icau61w;L1a&03-Q(6C^%ZxcoXYq=*t?48n20OWRiV zSKdJor?YwhYS5Tl1MG(@!BLjPEy~7;E*9a@sUZG3i~Uv2V*yns90*OO0~aN*nS`(v zi6j{*pPtFN(E$5673Y+NODuO#l^Y4cY+O_bX>Q>nX?nYKS>|L-B7j1Qvt@JS8VfSK zux^%W*c3`YK8B~I;|aixp=E;a z#Fc;~S6+T*r_yOk{V5DaPlg)_(pzFacab}kCa z(QEC`>ii~naC2|0w*!d7ND;_m<~U9w$VsY*QT$xYPZ=m9We+}MCW1}^v+52TR#b0o z2z`KI=(aqV-7Mq&2gXl1eF74qR zQO{T~Cay-LT|FM1l)L|bL{XZ;oQib_@egIs8^5d9I&Ny^H1LjNbdg4`*V?-`eSW!k Ly?!{J?4A7ro~&a* diff --git a/client/src/helpers/constants.js b/client/src/helpers/constants.js index b50ed007..bab8c925 100644 --- a/client/src/helpers/constants.js +++ b/client/src/helpers/constants.js @@ -200,92 +200,144 @@ export const FILTERS_URLS = { export const SERVICES = [ { - id: 'facebook', - name: 'Facebook', - }, - { - id: 'whatsapp', - name: 'WhatsApp', - }, - { - id: 'instagram', - name: 'Instagram', - }, - { - id: 'twitter', - name: 'Twitter', - }, - { - id: 'youtube', - name: 'YouTube', - }, - { - id: 'netflix', - name: 'Netflix', - }, - { - id: 'snapchat', - name: 'Snapchat', - }, - { - id: 'twitch', - name: 'Twitch', - }, - { - id: 'discord', - name: 'Discord', - }, - { - id: 'skype', - name: 'Skype', + id: '9gag', + name: '9Gag', }, { id: 'amazon', name: 'Amazon', }, - { - id: 'ebay', - name: 'eBay', - }, - { - id: 'origin', - name: 'Origin', - }, { id: 'cloudflare', - name: 'Cloudflare', + name: 'CloudFlare', }, { - id: 'steam', - name: 'Steam', + id: 'dailymotion', + name: 'Dailymotion', + }, + { + id: 'discord', + name: 'Discord', + }, + { + id: 'disneyplus', + name: 'Disney+', + }, + { + id: 'ebay', + name: 'EBay', }, { id: 'epic_games', name: 'Epic Games', }, + { + id: 'facebook', + name: 'Facebook', + }, + { + id: 'hulu', + name: 'Hulu', + }, + { + id: 'imgur', + name: 'Imgur', + }, + { + id: 'instagram', + name: 'Instagram', + }, + { + id: 'mail_ru', + name: 'Mail.ru', + }, + { + id: 'netflix', + name: 'Netflix', + }, + { + id: 'ok', + name: 'OK.ru', + }, + { + id: 'origin', + name: 'Origin', + }, + { + id: 'pinterest', + name: 'Pinterest', + }, + { + id: 'qq', + name: 'QQ', + }, { id: 'reddit', name: 'Reddit', }, { - id: 'ok', - name: 'OK', + id: 'skype', + name: 'Skype', }, { - id: 'vk', - name: 'VK', + id: 'snapchat', + name: 'Snapchat', }, { - id: 'mail_ru', - name: 'mail.ru', + id: 'spotify', + name: 'Spotify', + }, + { + id: 'steam', + name: 'Steam', + }, + { + id: 'telegram', + name: 'Telegram', }, { id: 'tiktok', name: 'TikTok', }, { - id: 'qq', - name: 'QQ', + id: 'tinder', + name: 'Tinder', + }, + { + id: 'twitch', + name: 'Twitch', + }, + { + id: 'twitter', + name: 'Twitter', + }, + { + id: 'viber', + name: 'Viber', + }, + { + id: 'vimeo', + name: 'Vimeo', + }, + { + id: 'vk', + name: 'VK.com', + }, + { + id: 'wechat', + name: 'WeChat', + }, + { + id: 'weibo', + name: 'Weibo', + }, + { + id: 'whatsapp', + name: 'WhatsApp', + }, + { + id: 'youtube', + name: 'YouTube', }, ]; diff --git a/internal/dnsfilter/blocked.go b/internal/dnsfilter/blocked.go index 473010c0..0905933b 100644 --- a/internal/dnsfilter/blocked.go +++ b/internal/dnsfilter/blocked.go @@ -161,7 +161,73 @@ var serviceRulesArray = []svc{ "||douyin.com^", "||tiktokv.com^", }}, - {"qq", []string{"||qq.com^", "||qqzaixian.com^"}}, + {"vimeo", []string{ + "||vimeo.com^", + "||vimeocdn.com^", + "*vod-adaptive.akamaized.net^", + }}, + {"pinterest", []string{ + "||pinterest.*^", + "||pinimg.com^", + }}, + {"imgur", []string{ + "||imgur.com^", + }}, + {"dailymotion", []string{ + "||dailymotion.com^", + "||dm-event.net^", + "||dmcdn.net^", + }}, + {"qq", []string{ + // block qq.com and subdomains excluding WeChat domains + "^(?!weixin|wx)([^.]+\\.)?qq\\.com$", + "||qqzaixian.com^", + }}, + {"wechat", []string{ + "||wechat.com^", + "||weixin.qq.com^", + "||wx.qq.com^", + }}, + {"viber", []string{ + "||viber.com^", + }}, + {"weibo", []string{ + "||weibo.com^", + }}, + {"9gag", []string{ + "||9cache.com^", + "||gag.com^", + }}, + {"telegram", []string{ + "||t.me^", + "||telegram.me^", + "||telegram.org^", + }}, + {"disneyplus", []string{ + "||disney-plus.net^", + "||disneyplus.com^", + }}, + {"hulu", []string{ + "||hulu.com^", + }}, + {"spotify", []string{ + "/_spotify-connect._tcp.local/", + "||spotify.com^", + "||scdn.co^", + "||spotify.com.edgesuite.net^", + "||spotify.map.fastly.net^", + "||spotify.map.fastlylb.net^", + "||spotifycdn.net^", + "||audio-ak-spotify-com.akamaized.net^", + "||audio4-ak-spotify-com.akamaized.net^", + "||heads-ak-spotify-com.akamaized.net^", + "||heads4-ak-spotify-com.akamaized.net^", + }}, + {"tinder", []string{ + "||gotinder.com^", + "||tinder.com^", + "||tindersparks.com^", + }}, } // convert array to map diff --git a/internal/dnsfilter/blocked_test.go b/internal/dnsfilter/blocked_test.go new file mode 100644 index 00000000..5227da6b --- /dev/null +++ b/internal/dnsfilter/blocked_test.go @@ -0,0 +1,37 @@ +// +build ignore + +package dnsfilter + +import ( + "fmt" + "sort" + "testing" +) + +// This is a simple tool that takes a list of services and prints them to the output. +// It is supposed to be used to update: +// client/src/helpers/constants.js +// client/src/components/ui/Icons.js +// +// Usage: +// 1. go run ./internal/dnsfilter/blocked_test.go +// 2. Use the output to replace `SERVICES` array in "client/src/helpers/constants.js". +// 3. You'll need to enter services names manually. +// 4. Don't forget to add missing icons to "client/src/components/ui/Icons.js". +// +// TODO(ameshkov): Rework generator: have a JSON file with all the metadata we need +// then use this JSON file to generate JS and Go code +func TestGenServicesArray(t *testing.T) { + services := make([]svc, len(serviceRulesArray)) + copy(services, serviceRulesArray) + + sort.Slice(services, func(i, j int) bool { + return services[i].name < services[j].name + }) + + fmt.Println("export const SERVICES = [") + for _, s := range services { + fmt.Printf(" {\n id: '%s',\n name: '%s',\n },\n", s.name, s.name) + } + fmt.Println("];") +} From c9d2436d7790ce1ed180d9da31aacadbe46b2aee Mon Sep 17 00:00:00 2001 From: Eugene Burkov Date: Thu, 4 Feb 2021 20:35:13 +0300 Subject: [PATCH 093/141] Pull request: 2574 external tests vol.3 Merge in DNS/adguard-home from 2574-external-tests-3 to master Updates #2574. Squashed commit of the following: commit 29d429c65dee2621ca503710a7ba9522f14f55f9 Author: Eugene Burkov Date: Thu Feb 4 20:06:57 2021 +0300 all: finally fix spacing commit 9e3a3be63b74852a7802e3f1832648444b58e4d0 Author: Eugene Burkov Date: Thu Feb 4 19:59:09 2021 +0300 aghtest: polish spacing commit 8a984159fe813b95b989803f5b8b78d01a41bd39 Author: Eugene Burkov Date: Thu Feb 4 18:44:47 2021 +0300 all: fix linux tests, imp code quality commit 0c1b42bacba1b23fa847e1fa032579c525b3eaa1 Author: Eugene Burkov Date: Thu Feb 4 17:33:12 2021 +0300 all: mv testutil to aghtest package, imp tests --- internal/agherr/agherr_test.go | 5 - .../testutil.go => aghtest/aghtest.go} | 4 +- internal/aghtest/resolver.go | 63 +++ internal/aghtest/upstream.go | 175 +++++++ internal/dhcpd/dhcpd_test.go | 4 +- internal/dhcpd/nclient4/client_test.go | 4 +- internal/dnsfilter/dnsfilter.go | 22 +- internal/dnsfilter/dnsfilter_test.go | 103 ++-- internal/dnsfilter/safebrowsing.go | 22 +- internal/dnsfilter/safebrowsing_test.go | 204 +++----- internal/dnsforward/config.go | 11 +- internal/dnsforward/dnsforward.go | 19 +- internal/dnsforward/dnsforward_test.go | 456 +++++++++--------- internal/dnsforward/http_test.go | 42 +- internal/home/auth_test.go | 4 +- internal/home/rdns.go | 44 +- internal/home/rdns_test.go | 20 +- internal/home/whois_test.go | 5 + internal/querylog/decode_test.go | 6 +- internal/querylog/qlog_test.go | 4 +- internal/stats/stats_test.go | 4 +- internal/sysutil/sysutil_test.go | 4 +- internal/updater/updater_test.go | 4 +- internal/util/autohosts_test.go | 4 +- 24 files changed, 737 insertions(+), 496 deletions(-) rename internal/{testutil/testutil.go => aghtest/aghtest.go} (93%) create mode 100644 internal/aghtest/resolver.go create mode 100644 internal/aghtest/upstream.go diff --git a/internal/agherr/agherr_test.go b/internal/agherr/agherr_test.go index 123c45ef..3ac5aeab 100644 --- a/internal/agherr/agherr_test.go +++ b/internal/agherr/agherr_test.go @@ -5,14 +5,9 @@ import ( "fmt" "testing" - "github.com/AdguardTeam/AdGuardHome/internal/testutil" "github.com/stretchr/testify/assert" ) -func TestMain(m *testing.M) { - testutil.DiscardLogOutput(m) -} - func TestError_Error(t *testing.T) { testCases := []struct { name string diff --git a/internal/testutil/testutil.go b/internal/aghtest/aghtest.go similarity index 93% rename from internal/testutil/testutil.go rename to internal/aghtest/aghtest.go index 69187969..4c453055 100644 --- a/internal/testutil/testutil.go +++ b/internal/aghtest/aghtest.go @@ -1,5 +1,5 @@ -// Package testutil contains utilities for testing. -package testutil +// Package aghtest contains utilities for testing. +package aghtest import ( "io" diff --git a/internal/aghtest/resolver.go b/internal/aghtest/resolver.go new file mode 100644 index 00000000..75fb6ce0 --- /dev/null +++ b/internal/aghtest/resolver.go @@ -0,0 +1,63 @@ +package aghtest + +import ( + "context" + "crypto/sha256" + "net" + "sync" +) + +// TestResolver is a Resolver for tests. +type TestResolver struct { + counter int + counterLock sync.Mutex +} + +// HostToIPs generates IPv4 and IPv6 from host. +// +// TODO(e.burkov): Replace with LookupIP after upgrading go to v1.15. +func (r *TestResolver) HostToIPs(host string) (ipv4, ipv6 net.IP) { + hash := sha256.Sum256([]byte(host)) + + return net.IP(hash[:4]), net.IP(hash[4:20]) +} + +// LookupIPAddr implements Resolver interface for *testResolver. It returns the +// slice of net.IPAddr with IPv4 and IPv6 instances. +func (r *TestResolver) LookupIPAddr(_ context.Context, host string) (ips []net.IPAddr, err error) { + ipv4, ipv6 := r.HostToIPs(host) + addrs := []net.IPAddr{{ + IP: ipv4, + }, { + IP: ipv6, + }} + + r.counterLock.Lock() + defer r.counterLock.Unlock() + r.counter++ + + return addrs, nil +} + +// LookupHost implements Resolver interface for *testResolver. It returns the +// slice of IPv4 and IPv6 instances converted to strings. +func (r *TestResolver) LookupHost(host string) (addrs []string, err error) { + ipv4, ipv6 := r.HostToIPs(host) + + r.counterLock.Lock() + defer r.counterLock.Unlock() + r.counter++ + + return []string{ + ipv4.String(), + ipv6.String(), + }, nil +} + +// Counter returns the number of requests handled. +func (r *TestResolver) Counter() int { + r.counterLock.Lock() + defer r.counterLock.Unlock() + + return r.counter +} diff --git a/internal/aghtest/upstream.go b/internal/aghtest/upstream.go new file mode 100644 index 00000000..78622771 --- /dev/null +++ b/internal/aghtest/upstream.go @@ -0,0 +1,175 @@ +package aghtest + +import ( + "crypto/sha256" + "encoding/hex" + "fmt" + "net" + "strings" + "sync" + + "github.com/AdguardTeam/AdGuardHome/internal/agherr" + "github.com/miekg/dns" +) + +// TestUpstream is a mock of real upstream. +type TestUpstream struct { + // Addr is the address for Address method. + Addr string + // CName is a map of hostname to canonical name. + CName map[string]string + // IPv4 is a map of hostname to IPv4. + IPv4 map[string][]net.IP + // IPv6 is a map of hostname to IPv6. + IPv6 map[string][]net.IP + // Reverse is a map of address to domain name. + Reverse map[string][]string +} + +// Exchange implements upstream.Upstream interface for *TestUpstream. +func (u *TestUpstream) Exchange(m *dns.Msg) (resp *dns.Msg, err error) { + resp = &dns.Msg{} + resp.SetReply(m) + + if len(m.Question) == 0 { + return nil, fmt.Errorf("question should not be empty") + } + name := m.Question[0].Name + + if cname, ok := u.CName[name]; ok { + resp.Answer = append(resp.Answer, &dns.CNAME{ + Hdr: dns.RR_Header{ + Name: name, + Rrtype: dns.TypeCNAME, + }, + Target: cname, + }) + } + + var hasRec bool + var rrType uint16 + var ips []net.IP + switch m.Question[0].Qtype { + case dns.TypeA: + rrType = dns.TypeA + if ipv4addr, ok := u.IPv4[name]; ok { + hasRec = true + ips = ipv4addr + } + case dns.TypeAAAA: + rrType = dns.TypeAAAA + if ipv6addr, ok := u.IPv6[name]; ok { + hasRec = true + ips = ipv6addr + } + case dns.TypePTR: + names, ok := u.Reverse[name] + if !ok { + break + } + + for _, n := range names { + resp.Answer = append(resp.Answer, &dns.PTR{ + Hdr: dns.RR_Header{ + Name: name, + Rrtype: rrType, + }, + Ptr: n, + }) + } + } + + for _, ip := range ips { + resp.Answer = append(resp.Answer, &dns.A{ + Hdr: dns.RR_Header{ + Name: name, + Rrtype: rrType, + }, + A: ip, + }) + } + + if len(resp.Answer) == 0 { + if hasRec { + // Set no error RCode if there are some records for + // given Qname but we didn't apply them. + resp.SetRcode(m, dns.RcodeSuccess) + + return resp, nil + } + // Set NXDomain RCode otherwise. + resp.SetRcode(m, dns.RcodeNameError) + } + + return resp, nil +} + +// Address implements upstream.Upstream interface for *TestUpstream. +func (u *TestUpstream) Address() string { + return u.Addr +} + +// TestBlockUpstream implements upstream.Upstream interface for replacing real +// upstream in tests. +type TestBlockUpstream struct { + Hostname string + Block bool + requestsCount int + lock sync.RWMutex +} + +// Exchange returns a message unique for TestBlockUpstream's Hostname-Block +// pair. +func (u *TestBlockUpstream) Exchange(r *dns.Msg) (*dns.Msg, error) { + u.lock.Lock() + defer u.lock.Unlock() + u.requestsCount++ + + hash := sha256.Sum256([]byte(u.Hostname)) + hashToReturn := hex.EncodeToString(hash[:]) + if !u.Block { + hashToReturn = hex.EncodeToString(hash[:])[:2] + strings.Repeat("ab", 28) + } + + m := &dns.Msg{} + m.Answer = []dns.RR{ + &dns.TXT{ + Hdr: dns.RR_Header{ + Name: r.Question[0].Name, + }, + Txt: []string{ + hashToReturn, + }, + }, + } + + return m, nil +} + +// Address always returns an empty string. +func (u *TestBlockUpstream) Address() string { + return "" +} + +// RequestsCount returns the number of handled requests. It's safe for +// concurrent use. +func (u *TestBlockUpstream) RequestsCount() int { + u.lock.Lock() + defer u.lock.Unlock() + + return u.requestsCount +} + +// TestErrUpstream implements upstream.Upstream interface for replacing real +// upstream in tests. +type TestErrUpstream struct{} + +// Exchange always returns nil Msg and non-nil error. +func (u *TestErrUpstream) Exchange(*dns.Msg) (*dns.Msg, error) { + return nil, agherr.Error("bad") +} + +// Address always returns an empty string. +func (u *TestErrUpstream) Address() string { + return "" +} diff --git a/internal/dhcpd/dhcpd_test.go b/internal/dhcpd/dhcpd_test.go index f5c34154..1aa1b9a6 100644 --- a/internal/dhcpd/dhcpd_test.go +++ b/internal/dhcpd/dhcpd_test.go @@ -9,12 +9,12 @@ import ( "testing" "time" - "github.com/AdguardTeam/AdGuardHome/internal/testutil" + "github.com/AdguardTeam/AdGuardHome/internal/aghtest" "github.com/stretchr/testify/assert" ) func TestMain(m *testing.M) { - testutil.DiscardLogOutput(m) + aghtest.DiscardLogOutput(m) } func testNotify(flags uint32) { diff --git a/internal/dhcpd/nclient4/client_test.go b/internal/dhcpd/nclient4/client_test.go index 99f99640..9ad376fe 100644 --- a/internal/dhcpd/nclient4/client_test.go +++ b/internal/dhcpd/nclient4/client_test.go @@ -17,14 +17,14 @@ import ( "testing" "time" - "github.com/AdguardTeam/AdGuardHome/internal/testutil" + "github.com/AdguardTeam/AdGuardHome/internal/aghtest" "github.com/hugelgupf/socketpair" "github.com/insomniacslk/dhcp/dhcpv4" "github.com/insomniacslk/dhcp/dhcpv4/server4" ) func TestMain(m *testing.M) { - testutil.DiscardLogOutput(m) + aghtest.DiscardLogOutput(m) } type handler struct { diff --git a/internal/dnsfilter/dnsfilter.go b/internal/dnsfilter/dnsfilter.go index 6ade9701..34ab408d 100644 --- a/internal/dnsfilter/dnsfilter.go +++ b/internal/dnsfilter/dnsfilter.go @@ -43,6 +43,12 @@ type RequestFilteringSettings struct { ServicesRules []ServiceEntry } +// Resolver is the interface for net.Resolver to simplify testing. +type Resolver interface { + // TODO(e.burkov): Replace with LookupIP after upgrading go to v1.15. + LookupIPAddr(ctx context.Context, host string) (ips []net.IPAddr, err error) +} + // Config allows you to configure DNS filtering with New() or just change variables directly. type Config struct { ParentalEnabled bool `yaml:"parental_enabled"` @@ -69,6 +75,9 @@ type Config struct { // Register an HTTP handler HTTPRegister func(string, string, func(http.ResponseWriter, *http.Request)) `yaml:"-"` + + // CustomResolver is the resolver used by DNSFilter. + CustomResolver Resolver } // LookupStats store stats collected during safebrowsing or parental checks @@ -92,12 +101,6 @@ type filtersInitializerParams struct { blockFilters []Filter } -// Resolver is the interface for net.Resolver to simplify testing. -type Resolver interface { - // TODO(e.burkov): Replace with LookupIP after upgrading go to v1.15. - LookupIPAddr(ctx context.Context, host string) (ips []net.IPAddr, err error) -} - // DNSFilter matches hostnames and DNS requests against filtering rules. type DNSFilter struct { rulesStorage *filterlist.RuleStorage @@ -796,6 +799,7 @@ func InitModule() { // New creates properly initialized DNS Filter that is ready to be used. func New(c *Config, blockFilters []Filter) *DNSFilter { + var resolver Resolver = net.DefaultResolver if c != nil { cacheConf := cache.Config{ EnableLRU: true, @@ -815,10 +819,14 @@ func New(c *Config, blockFilters []Filter) *DNSFilter { cacheConf.MaxSize = c.ParentalCacheSize gctx.parentalCache = cache.New(cacheConf) } + + if c.CustomResolver != nil { + resolver = c.CustomResolver + } } d := &DNSFilter{ - resolver: net.DefaultResolver, + resolver: resolver, } err := d.initSecurityServices() diff --git a/internal/dnsfilter/dnsfilter_test.go b/internal/dnsfilter/dnsfilter_test.go index 6ab4fbc4..25985dae 100644 --- a/internal/dnsfilter/dnsfilter_test.go +++ b/internal/dnsfilter/dnsfilter_test.go @@ -3,12 +3,11 @@ package dnsfilter import ( "bytes" "context" - "crypto/sha256" "fmt" "net" "testing" - "github.com/AdguardTeam/AdGuardHome/internal/testutil" + "github.com/AdguardTeam/AdGuardHome/internal/aghtest" "github.com/AdguardTeam/golibs/cache" "github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/urlfilter/rules" @@ -17,7 +16,7 @@ import ( ) func TestMain(m *testing.M) { - testutil.DiscardLogOutput(m) + aghtest.DiscardLogOutput(m) } var setts RequestFilteringSettings @@ -37,7 +36,9 @@ func purgeCaches() { } func newForTest(c *Config, filters []Filter) *DNSFilter { - setts = RequestFilteringSettings{} + setts = RequestFilteringSettings{ + FilteringEnabled: true, + } setts.FilteringEnabled = true if c != nil { c.SafeBrowsingCacheSize = 10000 @@ -149,16 +150,16 @@ func TestEtcHostsMatching(t *testing.T) { func TestSafeBrowsing(t *testing.T) { logOutput := &bytes.Buffer{} - testutil.ReplaceLogWriter(t, logOutput) - testutil.ReplaceLogLevel(t, log.DEBUG) + aghtest.ReplaceLogWriter(t, logOutput) + aghtest.ReplaceLogLevel(t, log.DEBUG) d := newForTest(&Config{SafeBrowsingEnabled: true}, nil) t.Cleanup(d.Close) matching := "wmconvirus.narod.ru" - d.safeBrowsingUpstream = &testSbUpstream{ - hostname: matching, - block: true, - } + d.SetSafeBrowsingUpstream(&aghtest.TestBlockUpstream{ + Hostname: matching, + Block: true, + }) d.checkMatch(t, matching) assert.Contains(t, logOutput.String(), "SafeBrowsing lookup for "+matching) @@ -178,10 +179,10 @@ func TestParallelSB(t *testing.T) { d := newForTest(&Config{SafeBrowsingEnabled: true}, nil) t.Cleanup(d.Close) matching := "wmconvirus.narod.ru" - d.safeBrowsingUpstream = &testSbUpstream{ - hostname: matching, - block: true, - } + d.SetSafeBrowsingUpstream(&aghtest.TestBlockUpstream{ + Hostname: matching, + Block: true, + }) t.Run("group", func(t *testing.T) { for i := 0; i < 100; i++ { @@ -228,26 +229,12 @@ func TestCheckHostSafeSearchYandex(t *testing.T) { } } -// testResolver is a Resolver for tests. -type testResolver struct{} - -// LookupIP implements Resolver interface for *testResolver. -func (r *testResolver) LookupIPAddr(_ context.Context, host string) (ips []net.IPAddr, err error) { - hash := sha256.Sum256([]byte(host)) - addrs := []net.IPAddr{{ - IP: net.IP(hash[:4]), - Zone: "somezone", - }, { - IP: net.IP(hash[4:20]), - Zone: "somezone", - }} - return addrs, nil -} - func TestCheckHostSafeSearchGoogle(t *testing.T) { - d := newForTest(&Config{SafeSearchEnabled: true}, nil) + d := newForTest(&Config{ + SafeSearchEnabled: true, + CustomResolver: &aghtest.TestResolver{}, + }, nil) t.Cleanup(d.Close) - d.resolver = &testResolver{} // Check host for each domain. for _, host := range []string{ @@ -299,12 +286,12 @@ func TestSafeSearchCacheYandex(t *testing.T) { } func TestSafeSearchCacheGoogle(t *testing.T) { - d := newForTest(nil, nil) + resolver := &aghtest.TestResolver{} + d := newForTest(&Config{ + CustomResolver: resolver, + }, nil) t.Cleanup(d.Close) - resolver := &testResolver{} - d.resolver = resolver - domain := "www.google.ru" res, err := d.CheckHost(domain, dns.TypeA, &setts) assert.Nil(t, err) @@ -350,16 +337,16 @@ func TestSafeSearchCacheGoogle(t *testing.T) { func TestParentalControl(t *testing.T) { logOutput := &bytes.Buffer{} - testutil.ReplaceLogWriter(t, logOutput) - testutil.ReplaceLogLevel(t, log.DEBUG) + aghtest.ReplaceLogWriter(t, logOutput) + aghtest.ReplaceLogLevel(t, log.DEBUG) d := newForTest(&Config{ParentalEnabled: true}, nil) t.Cleanup(d.Close) matching := "pornhub.com" - d.parentalUpstream = &testSbUpstream{ - hostname: matching, - block: true, - } + d.SetParentalUpstream(&aghtest.TestBlockUpstream{ + Hostname: matching, + Block: true, + }) d.checkMatch(t, matching) assert.Contains(t, logOutput.String(), "Parental lookup for "+matching) @@ -733,14 +720,14 @@ func TestClientSettings(t *testing.T) { }}, ) t.Cleanup(d.Close) - d.parentalUpstream = &testSbUpstream{ - hostname: "pornhub.com", - block: true, - } - d.safeBrowsingUpstream = &testSbUpstream{ - hostname: "wmconvirus.narod.ru", - block: true, - } + d.SetParentalUpstream(&aghtest.TestBlockUpstream{ + Hostname: "pornhub.com", + Block: true, + }) + d.SetSafeBrowsingUpstream(&aghtest.TestBlockUpstream{ + Hostname: "wmconvirus.narod.ru", + Block: true, + }) type testCase struct { name string @@ -801,10 +788,10 @@ func BenchmarkSafeBrowsing(b *testing.B) { d := newForTest(&Config{SafeBrowsingEnabled: true}, nil) b.Cleanup(d.Close) blocked := "wmconvirus.narod.ru" - d.safeBrowsingUpstream = &testSbUpstream{ - hostname: blocked, - block: true, - } + d.SetSafeBrowsingUpstream(&aghtest.TestBlockUpstream{ + Hostname: blocked, + Block: true, + }) for n := 0; n < b.N; n++ { res, err := d.CheckHost(blocked, dns.TypeA, &setts) assert.Nilf(b, err, "Error while matching host %s: %s", blocked, err) @@ -816,10 +803,10 @@ func BenchmarkSafeBrowsingParallel(b *testing.B) { d := newForTest(&Config{SafeBrowsingEnabled: true}, nil) b.Cleanup(d.Close) blocked := "wmconvirus.narod.ru" - d.safeBrowsingUpstream = &testSbUpstream{ - hostname: blocked, - block: true, - } + d.SetSafeBrowsingUpstream(&aghtest.TestBlockUpstream{ + Hostname: blocked, + Block: true, + }) b.RunParallel(func(pb *testing.PB) { for pb.Next() { res, err := d.CheckHost(blocked, dns.TypeA, &setts) diff --git a/internal/dnsfilter/safebrowsing.go b/internal/dnsfilter/safebrowsing.go index 9a14b584..9142c7c4 100644 --- a/internal/dnsfilter/safebrowsing.go +++ b/internal/dnsfilter/safebrowsing.go @@ -30,6 +30,20 @@ const ( pcTXTSuffix = `pc.dns.adguard.com.` ) +// SetParentalUpstream sets the parental upstream for *DNSFilter. +// +// TODO(e.burkov): Remove this in v1 API to forbid the direct access. +func (d *DNSFilter) SetParentalUpstream(u upstream.Upstream) { + d.parentalUpstream = u +} + +// SetSafeBrowsingUpstream sets the safe browsing upstream for *DNSFilter. +// +// TODO(e.burkov): Remove this in v1 API to forbid the direct access. +func (d *DNSFilter) SetSafeBrowsingUpstream(u upstream.Upstream) { + d.safeBrowsingUpstream = u +} + func (d *DNSFilter) initSecurityServices() error { var err error d.safeBrowsingServer = defaultSafebrowsingServer @@ -44,15 +58,17 @@ func (d *DNSFilter) initSecurityServices() error { }, } - d.parentalUpstream, err = upstream.AddressToUpstream(d.parentalServer, opts) + parUps, err := upstream.AddressToUpstream(d.parentalServer, opts) if err != nil { return fmt.Errorf("converting parental server: %w", err) } + d.SetParentalUpstream(parUps) - d.safeBrowsingUpstream, err = upstream.AddressToUpstream(d.safeBrowsingServer, opts) + sbUps, err := upstream.AddressToUpstream(d.safeBrowsingServer, opts) if err != nil { return fmt.Errorf("converting safe browsing server: %w", err) } + d.SetSafeBrowsingUpstream(sbUps) return nil } @@ -227,7 +243,7 @@ func (c *sbCtx) processTXT(resp *dns.Msg) (bool, [][]byte) { func (c *sbCtx) storeCache(hashes [][]byte) { sort.Slice(hashes, func(a, b int) bool { - return bytes.Compare(hashes[a], hashes[b]) < 0 + return bytes.Compare(hashes[a], hashes[b]) == -1 }) var curData []byte diff --git a/internal/dnsfilter/safebrowsing_test.go b/internal/dnsfilter/safebrowsing_test.go index a1f627c4..f94a3c99 100644 --- a/internal/dnsfilter/safebrowsing_test.go +++ b/internal/dnsfilter/safebrowsing_test.go @@ -2,14 +2,11 @@ package dnsfilter import ( "crypto/sha256" - "encoding/hex" "strings" - "sync" "testing" - "github.com/AdguardTeam/AdGuardHome/internal/agherr" + "github.com/AdguardTeam/AdGuardHome/internal/aghtest" "github.com/AdguardTeam/golibs/cache" - "github.com/miekg/dns" "github.com/stretchr/testify/assert" ) @@ -108,27 +105,14 @@ func TestSafeBrowsingCache(t *testing.T) { assert.Empty(t, c.getCached()) } -// testErrUpstream implements upstream.Upstream interface for replacing real -// upstream in tests. -type testErrUpstream struct{} - -// Exchange always returns nil Msg and non-nil error. -func (teu *testErrUpstream) Exchange(*dns.Msg) (*dns.Msg, error) { - return nil, agherr.Error("bad") -} - -func (teu *testErrUpstream) Address() string { - return "" -} - func TestSBPC_checkErrorUpstream(t *testing.T) { d := newForTest(&Config{SafeBrowsingEnabled: true}, nil) t.Cleanup(d.Close) - ups := &testErrUpstream{} + ups := &aghtest.TestErrUpstream{} - d.safeBrowsingUpstream = ups - d.parentalUpstream = ups + d.SetSafeBrowsingUpstream(ups) + d.SetParentalUpstream(ups) _, err := d.checkSafeBrowsing("smthng.com") assert.NotNil(t, err) @@ -137,122 +121,86 @@ func TestSBPC_checkErrorUpstream(t *testing.T) { assert.NotNil(t, err) } -// testSbUpstream implements upstream.Upstream interface for replacing real -// upstream in tests. -type testSbUpstream struct { - hostname string - block bool - requestsCount int - counterLock sync.RWMutex -} - -// Exchange returns a message depending on the upstream settings (hostname, block) -func (u *testSbUpstream) Exchange(r *dns.Msg) (*dns.Msg, error) { - u.counterLock.Lock() - u.requestsCount++ - u.counterLock.Unlock() - - hash := sha256.Sum256([]byte(u.hostname)) - prefix := hash[0:2] - hashToReturn := hex.EncodeToString(prefix) + strings.Repeat("ab", 28) - if u.block { - hashToReturn = hex.EncodeToString(hash[:]) - } - - m := &dns.Msg{} - m.Answer = []dns.RR{ - &dns.TXT{ - Hdr: dns.RR_Header{ - Name: r.Question[0].Name, - }, - Txt: []string{ - hashToReturn, - }, - }, - } - - return m, nil -} - -func (u *testSbUpstream) Address() string { - return "" -} - -func TestSBPC_sbValidResponse(t *testing.T) { +func TestSBPC(t *testing.T) { d := newForTest(&Config{SafeBrowsingEnabled: true}, nil) t.Cleanup(d.Close) - ups := &testSbUpstream{} - d.safeBrowsingUpstream = ups - d.parentalUpstream = ups + const hostname = "example.org" - // Prepare the upstream - ups.hostname = "example.org" - ups.block = false - ups.requestsCount = 0 + testCases := []struct { + name string + block bool + testFunc func(string) (Result, error) + testCache cache.Cache + }{{ + name: "sb_no_block", + block: false, + testFunc: d.checkSafeBrowsing, + testCache: gctx.safebrowsingCache, + }, { + name: "sb_block", + block: true, + testFunc: d.checkSafeBrowsing, + testCache: gctx.safebrowsingCache, + }, { + name: "pc_no_block", + block: false, + testFunc: d.checkParental, + testCache: gctx.parentalCache, + }, { + name: "pc_block", + block: true, + testFunc: d.checkParental, + testCache: gctx.parentalCache, + }} - // First - check that the request is not blocked - res, err := d.checkSafeBrowsing("example.org") - assert.Nil(t, err) - assert.False(t, res.IsFiltered) + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // Prepare the upstream. + ups := &aghtest.TestBlockUpstream{ + Hostname: hostname, + Block: tc.block, + } + d.SetSafeBrowsingUpstream(ups) + d.SetParentalUpstream(ups) - // Check the cache state, check that the response is now cached - assert.Equal(t, 1, gctx.safebrowsingCache.Stats().Count) - assert.Equal(t, 0, gctx.safebrowsingCache.Stats().Hit) + // Firstly, check the request blocking. + hits := 0 + res, err := tc.testFunc(hostname) + assert.Nil(t, err) + if tc.block { + assert.True(t, res.IsFiltered) + assert.Len(t, res.Rules, 1) + hits++ + } else { + assert.False(t, res.IsFiltered) + } - // There was one request to an upstream - assert.Equal(t, 1, ups.requestsCount) + // Check the cache state, check the response is now cached. + assert.Equal(t, 1, tc.testCache.Stats().Count) + assert.Equal(t, hits, tc.testCache.Stats().Hit) - // Now make the same request to check that the cache was used - res, err = d.checkSafeBrowsing("example.org") - assert.Nil(t, err) - assert.False(t, res.IsFiltered) + // There was one request to an upstream. + assert.Equal(t, 1, ups.RequestsCount()) - // Check the cache state, it should've been used - assert.Equal(t, 1, gctx.safebrowsingCache.Stats().Count) - assert.Equal(t, 1, gctx.safebrowsingCache.Stats().Hit) + // Now make the same request to check the cache was used. + res, err = tc.testFunc(hostname) + assert.Nil(t, err) + if tc.block { + assert.True(t, res.IsFiltered) + assert.Len(t, res.Rules, 1) + } else { + assert.False(t, res.IsFiltered) + } - // Check that there were no additional requests - assert.Equal(t, 1, ups.requestsCount) -} - -func TestSBPC_pcBlockedResponse(t *testing.T) { - d := newForTest(&Config{SafeBrowsingEnabled: true}, nil) - t.Cleanup(d.Close) - - ups := &testSbUpstream{} - d.safeBrowsingUpstream = ups - d.parentalUpstream = ups - - // Prepare the upstream - // Make sure that the upstream will return a response that matches the queried domain - ups.hostname = "example.com" - ups.block = true - ups.requestsCount = 0 - - // Make a lookup - res, err := d.checkParental("example.com") - assert.Nil(t, err) - assert.True(t, res.IsFiltered) - assert.Len(t, res.Rules, 1) - - // Check the cache state, check that the response is now cached - assert.Equal(t, 1, gctx.parentalCache.Stats().Count) - assert.Equal(t, 1, gctx.parentalCache.Stats().Hit) - - // There was one request to an upstream - assert.Equal(t, 1, ups.requestsCount) - - // Make a second lookup for the same domain - res, err = d.checkParental("example.com") - assert.Nil(t, err) - assert.True(t, res.IsFiltered) - assert.Len(t, res.Rules, 1) - - // Check the cache state, it should've been used - assert.Equal(t, 1, gctx.parentalCache.Stats().Count) - assert.Equal(t, 2, gctx.parentalCache.Stats().Hit) - - // Check that there were no additional requests - assert.Equal(t, 1, ups.requestsCount) + // Check the cache state, it should've been used. + assert.Equal(t, 1, tc.testCache.Stats().Count) + assert.Equal(t, hits+1, tc.testCache.Stats().Hit) + + // Check that there were no additional requests. + assert.Equal(t, 1, ups.RequestsCount()) + + purgeCaches() + }) + } } diff --git a/internal/dnsforward/config.go b/internal/dnsforward/config.go index 748b14b2..a80e7ad5 100644 --- a/internal/dnsforward/config.go +++ b/internal/dnsforward/config.go @@ -296,12 +296,13 @@ func (s *Server) prepareUpstreamSettings() error { // prepareIntlProxy - initializes DNS proxy that we use for internal DNS queries func (s *Server) prepareIntlProxy() { - intlProxyConfig := proxy.Config{ - CacheEnabled: true, - CacheSizeBytes: 4096, - UpstreamConfig: s.conf.UpstreamConfig, + s.internalProxy = &proxy.Proxy{ + Config: proxy.Config{ + CacheEnabled: true, + CacheSizeBytes: 4096, + UpstreamConfig: s.conf.UpstreamConfig, + }, } - s.internalProxy = &proxy.Proxy{Config: intlProxyConfig} } // prepareTLS - prepares TLS configuration for the DNS proxy diff --git a/internal/dnsforward/dnsforward.go b/internal/dnsforward/dnsforward.go index c24ba62b..68a0b6ae 100644 --- a/internal/dnsforward/dnsforward.go +++ b/internal/dnsforward/dnsforward.go @@ -85,10 +85,11 @@ type DNSCreateParams struct { // NewServer creates a new instance of the dnsforward.Server // Note: this function must be called only once func NewServer(p DNSCreateParams) *Server { - s := &Server{} - s.dnsFilter = p.DNSFilter - s.stats = p.Stats - s.queryLog = p.QueryLog + s := &Server{ + dnsFilter: p.DNSFilter, + stats: p.Stats, + queryLog: p.QueryLog, + } if p.DHCPServer != nil { s.dhcpServer = p.DHCPServer @@ -103,6 +104,16 @@ func NewServer(p DNSCreateParams) *Server { return s } +// NewCustomServer creates a new instance of *Server with custom internal proxy. +func NewCustomServer(internalProxy *proxy.Proxy) *Server { + s := &Server{} + if internalProxy != nil { + s.internalProxy = internalProxy + } + + return s +} + // Close - close object func (s *Server) Close() { s.Lock() diff --git a/internal/dnsforward/dnsforward_test.go b/internal/dnsforward/dnsforward_test.go index 9b72dd52..93b6bcb7 100644 --- a/internal/dnsforward/dnsforward_test.go +++ b/internal/dnsforward/dnsforward_test.go @@ -1,11 +1,9 @@ package dnsforward import ( - "context" "crypto/ecdsa" "crypto/rand" "crypto/rsa" - "crypto/sha256" "crypto/tls" "crypto/x509" "crypto/x509/pkix" @@ -20,7 +18,7 @@ import ( "testing" "time" - "github.com/AdguardTeam/AdGuardHome/internal/testutil" + "github.com/AdguardTeam/AdGuardHome/internal/aghtest" "github.com/AdguardTeam/AdGuardHome/internal/util" "github.com/AdguardTeam/AdGuardHome/internal/dhcpd" @@ -32,7 +30,7 @@ import ( ) func TestMain(m *testing.M) { - testutil.DiscardLogOutput(m) + aghtest.DiscardLogOutput(m) } const ( @@ -53,10 +51,13 @@ func startDeferStop(t *testing.T, s *Server) { } func TestServer(t *testing.T) { - s := createTestServer(t) + s := createTestServer(t, &dnsfilter.Config{}, ServerConfig{ + UDPListenAddr: &net.UDPAddr{}, + TCPListenAddr: &net.TCPAddr{}, + }) s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{ - &testUpstream{ - ipv4: map[string][]net.IP{ + &aghtest.TestUpstream{ + IPv4: map[string][]net.IP{ "google-public-dns-a.google.com.": {{8, 8, 8, 8}}, }, }, @@ -88,11 +89,13 @@ func TestServer(t *testing.T) { } func TestServerWithProtectionDisabled(t *testing.T) { - s := createTestServer(t) - s.conf.ProtectionEnabled = false + s := createTestServer(t, &dnsfilter.Config{}, ServerConfig{ + UDPListenAddr: &net.UDPAddr{}, + TCPListenAddr: &net.TCPAddr{}, + }) s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{ - &testUpstream{ - ipv4: map[string][]net.IP{ + &aghtest.TestUpstream{ + IPv4: map[string][]net.IP{ "google-public-dns-a.google.com.": {{8, 8, 8, 8}}, }, }, @@ -113,7 +116,11 @@ func createTestTLS(t *testing.T, tlsConf TLSConfig) (s *Server, certPem []byte) var keyPem []byte _, certPem, keyPem = createServerTLSConfig(t) - s = createTestServer(t) + + s = createTestServer(t, &dnsfilter.Config{}, ServerConfig{ + UDPListenAddr: &net.UDPAddr{}, + TCPListenAddr: &net.TCPAddr{}, + }) tlsConf.CertificateChainData, tlsConf.PrivateKeyData = certPem, keyPem s.conf.TLSConfig = tlsConf @@ -126,11 +133,11 @@ func createTestTLS(t *testing.T, tlsConf TLSConfig) (s *Server, certPem []byte) func TestDoTServer(t *testing.T) { s, certPem := createTestTLS(t, TLSConfig{ - TLSListenAddr: &net.TCPAddr{Port: 0}, + TLSListenAddr: &net.TCPAddr{}, }) s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{ - &testUpstream{ - ipv4: map[string][]net.IP{ + &aghtest.TestUpstream{ + IPv4: map[string][]net.IP{ "google-public-dns-a.google.com.": {{8, 8, 8, 8}}, }, }, @@ -156,11 +163,11 @@ func TestDoTServer(t *testing.T) { func TestDoQServer(t *testing.T) { s, _ := createTestTLS(t, TLSConfig{ - QUICListenAddr: &net.UDPAddr{Port: 0}, + QUICListenAddr: &net.UDPAddr{}, }) s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{ - &testUpstream{ - ipv4: map[string][]net.IP{ + &aghtest.TestUpstream{ + IPv4: map[string][]net.IP{ "google-public-dns-a.google.com.": {{8, 8, 8, 8}}, }, }, @@ -184,10 +191,27 @@ func TestDoQServer(t *testing.T) { func TestServerRace(t *testing.T) { t.Skip("TODO(e.burkov): inspect the golibs/cache package for locks") - s := createTestServer(t) + filterConf := &dnsfilter.Config{ + SafeBrowsingEnabled: true, + SafeBrowsingCacheSize: 1000, + SafeSearchEnabled: true, + SafeSearchCacheSize: 1000, + ParentalCacheSize: 1000, + CacheTime: 30, + } + forwardConf := ServerConfig{ + UDPListenAddr: &net.UDPAddr{}, + TCPListenAddr: &net.TCPAddr{}, + FilteringConfig: FilteringConfig{ + ProtectionEnabled: true, + UpstreamDNS: []string{"8.8.8.8:53", "8.8.4.4:53"}, + }, + ConfigModified: func() {}, + } + s := createTestServer(t, filterConf, forwardConf) s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{ - &testUpstream{ - ipv4: map[string][]net.IP{ + &aghtest.TestUpstream{ + IPv4: map[string][]net.IP{ "google-public-dns-a.google.com.": {{8, 8, 8, 8}}, }, }, @@ -202,68 +226,74 @@ func TestServerRace(t *testing.T) { sendTestMessagesAsync(t, conn) } -// testResolver is a Resolver for tests. -// -//lint:ignore U1000 TODO(e.burkov): move into aghtest package. -type testResolver struct{} - -// LookupIPAddr implements Resolver interface for *testResolver. -// -//lint:ignore U1000 TODO(e.burkov): move into aghtest package. -func (r *testResolver) LookupIPAddr(_ context.Context, host string) (ips []net.IPAddr, err error) { - hash := sha256.Sum256([]byte(host)) - addrs := []net.IPAddr{{ - IP: net.IP(hash[:4]), - Zone: "somezone", - }, { - IP: net.IP(hash[4:20]), - Zone: "somezone", - }} - return addrs, nil -} - -// LookupHost implements Resolver interface for *testResolver. -// -//lint:ignore U1000 TODO(e.burkov): move into aghtest package. -func (r *testResolver) LookupHost(host string) (addrs []string, err error) { - hash := sha256.Sum256([]byte(host)) - addrs = []string{ - net.IP(hash[:4]).String(), - net.IP(hash[4:20]).String(), - } - return addrs, nil -} - func TestSafeSearch(t *testing.T) { - t.Skip("TODO(e.burkov): substitute the dnsfilter by one with custom resolver from aghtest") - - testUpstreamIP := net.IP{213, 180, 193, 56} - testCases := []string{ - "yandex.com.", - "yandex.by.", - "yandex.kz.", - "yandex.ru.", - "www.google.com.", - "www.google.com.af.", - "www.google.be.", - "www.google.by.", + resolver := &aghtest.TestResolver{} + filterConf := &dnsfilter.Config{ + SafeSearchEnabled: true, + SafeSearchCacheSize: 1000, + CacheTime: 30, + CustomResolver: resolver, } + forwardConf := ServerConfig{ + UDPListenAddr: &net.UDPAddr{}, + TCPListenAddr: &net.TCPAddr{}, + FilteringConfig: FilteringConfig{ + ProtectionEnabled: true, + }, + } + s := createTestServer(t, filterConf, forwardConf) + startDeferStop(t, s) + + addr := s.dnsProxy.Addr(proxy.ProtoUDP).String() + client := dns.Client{Net: proxy.ProtoUDP} + + yandexIP := net.IP{213, 180, 193, 56} + googleIP, _ := resolver.HostToIPs("forcesafesearch.google.com") + + testCases := []struct { + host string + want net.IP + }{{ + host: "yandex.com.", + want: yandexIP, + }, { + host: "yandex.by.", + want: yandexIP, + }, { + host: "yandex.kz.", + want: yandexIP, + }, { + host: "yandex.ru.", + want: yandexIP, + }, { + host: "www.google.com.", + want: googleIP, + }, { + host: "www.google.com.af.", + want: googleIP, + }, { + host: "www.google.be.", + want: googleIP, + }, { + host: "www.google.by.", + want: googleIP, + }} for _, tc := range testCases { - t.Run("safe_search_"+tc, func(t *testing.T) { - s := createTestServer(t) - startDeferStop(t, s) - - addr := s.dnsProxy.Addr(proxy.ProtoUDP) - client := dns.Client{Net: proxy.ProtoUDP} - - exchangeAndAssertResponse(t, &client, addr, tc, testUpstreamIP) + t.Run(tc.host, func(t *testing.T) { + req := createTestMessage(tc.host) + reply, _, err := client.Exchange(req, addr) + assert.Nilf(t, err, "couldn't talk to server %s: %s", addr, err) + assertResponse(t, reply, tc.want) }) } } func TestInvalidRequest(t *testing.T) { - s := createTestServer(t) + s := createTestServer(t, &dnsfilter.Config{}, ServerConfig{ + UDPListenAddr: &net.UDPAddr{}, + TCPListenAddr: &net.TCPAddr{}, + }) startDeferStop(t, s) addr := s.dnsProxy.Addr(proxy.ProtoUDP).String() @@ -284,7 +314,14 @@ func TestInvalidRequest(t *testing.T) { } func TestBlockedRequest(t *testing.T) { - s := createTestServer(t) + forwardConf := ServerConfig{ + UDPListenAddr: &net.UDPAddr{}, + TCPListenAddr: &net.TCPAddr{}, + FilteringConfig: FilteringConfig{ + ProtectionEnabled: true, + }, + } + s := createTestServer(t, &dnsfilter.Config{}, forwardConf) startDeferStop(t, s) addr := s.dnsProxy.Addr(proxy.ProtoUDP) @@ -299,12 +336,19 @@ func TestBlockedRequest(t *testing.T) { } func TestServerCustomClientUpstream(t *testing.T) { - s := createTestServer(t) + forwardConf := ServerConfig{ + UDPListenAddr: &net.UDPAddr{}, + TCPListenAddr: &net.TCPAddr{}, + FilteringConfig: FilteringConfig{ + ProtectionEnabled: true, + }, + } + s := createTestServer(t, &dnsfilter.Config{}, forwardConf) s.conf.GetCustomUpstreamByClient = func(_ string) *proxy.UpstreamConfig { return &proxy.UpstreamConfig{ Upstreams: []upstream.Upstream{ - &testUpstream{ - ipv4: map[string][]net.IP{ + &aghtest.TestUpstream{ + IPv4: map[string][]net.IP{ "host.": {{192, 168, 0, 1}}, }, }, @@ -327,82 +371,6 @@ func TestServerCustomClientUpstream(t *testing.T) { assert.Equal(t, net.IP{192, 168, 0, 1}, reply.Answer[0].(*dns.A).A) } -// testUpstream is a mock of real upstream. specify fields with necessary values -// to simulate real upstream behaviour. -// -// TODO(e.burkov): move into aghtest package. -type testUpstream struct { - // cn is a map of hostname to canonical name. - cn map[string]string - // ipv4 is a map of hostname to IPv4. - ipv4 map[string][]net.IP - // ipv6 is a map of hostname to IPv6. - ipv6 map[string][]net.IP -} - -// Exchange implements upstream.Upstream interface for *testUpstream. -func (u *testUpstream) Exchange(m *dns.Msg) (*dns.Msg, error) { - resp := &dns.Msg{} - resp.SetReply(m) - hasRec := false - - name := m.Question[0].Name - - if cname, ok := u.cn[name]; ok { - resp.Answer = append(resp.Answer, &dns.CNAME{ - Hdr: dns.RR_Header{ - Name: name, - Rrtype: dns.TypeCNAME, - }, - Target: cname, - }) - } - - var rrtype uint16 - var a []net.IP - switch m.Question[0].Qtype { - case dns.TypeA: - rrtype = dns.TypeA - if ipv4addr, ok := u.ipv4[name]; ok { - hasRec = true - a = ipv4addr - } - case dns.TypeAAAA: - rrtype = dns.TypeAAAA - if ipv6addr, ok := u.ipv6[name]; ok { - hasRec = true - a = ipv6addr - } - } - for _, ip := range a { - resp.Answer = append(resp.Answer, &dns.A{ - Hdr: dns.RR_Header{ - Name: name, - Rrtype: rrtype, - }, - A: ip, - }) - } - - if len(resp.Answer) == 0 { - if hasRec { - // Set no error RCode if there are some records for - // given Qname but we didn't apply them. - resp.SetRcode(m, dns.RcodeSuccess) - return resp, nil - } - // Set NXDomain RCode otherwise. - resp.SetRcode(m, dns.RcodeNameError) - } - - return resp, nil -} - -// Address implements upstream.Upstream interface for *testUpstream. -func (u *testUpstream) Address() string { - return "test" -} - func (s *Server) startWithUpstream(u upstream.Upstream) error { s.Lock() defer s.Unlock() @@ -410,30 +378,35 @@ func (s *Server) startWithUpstream(u upstream.Upstream) error { if err != nil { return err } + s.dnsProxy.UpstreamConfig = &proxy.UpstreamConfig{ Upstreams: []upstream.Upstream{u}, } + return s.dnsProxy.Start() } -// testCNAMEs is a simple map of names and CNAMEs necessary for the testUpstream work +// testCNAMEs is a map of names and CNAMEs necessary for the TestUpstream work. var testCNAMEs = map[string]string{ "badhost.": "null.example.org.", "whitelist.example.org.": "null.example.org.", } -// testIPv4 is a simple map of names and IPv4s necessary for the testUpstream work +// testIPv4 is a map of names and IPv4s necessary for the TestUpstream work. var testIPv4 = map[string][]net.IP{ "null.example.org.": {{1, 2, 3, 4}}, "example.org.": {{127, 0, 0, 255}}, } func TestBlockCNAMEProtectionEnabled(t *testing.T) { - s := createTestServer(t) - testUpstm := &testUpstream{ - cn: testCNAMEs, - ipv4: testIPv4, - ipv6: nil, + s := createTestServer(t, &dnsfilter.Config{}, ServerConfig{ + UDPListenAddr: &net.UDPAddr{}, + TCPListenAddr: &net.TCPAddr{}, + }) + testUpstm := &aghtest.TestUpstream{ + CName: testCNAMEs, + IPv4: testIPv4, + IPv6: nil, } s.conf.ProtectionEnabled = false err := s.startWithUpstream(testUpstm) @@ -449,11 +422,18 @@ func TestBlockCNAMEProtectionEnabled(t *testing.T) { } func TestBlockCNAME(t *testing.T) { - s := createTestServer(t) + forwardConf := ServerConfig{ + UDPListenAddr: &net.UDPAddr{}, + TCPListenAddr: &net.TCPAddr{}, + FilteringConfig: FilteringConfig{ + ProtectionEnabled: true, + }, + } + s := createTestServer(t, &dnsfilter.Config{}, forwardConf) s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{ - &testUpstream{ - cn: testCNAMEs, - ipv4: testIPv4, + &aghtest.TestUpstream{ + CName: testCNAMEs, + IPv4: testIPv4, }, } startDeferStop(t, s) @@ -496,14 +476,21 @@ func TestBlockCNAME(t *testing.T) { } func TestClientRulesForCNAMEMatching(t *testing.T) { - s := createTestServer(t) - s.conf.FilterHandler = func(_ net.IP, _ string, settings *dnsfilter.RequestFilteringSettings) { - settings.FilteringEnabled = false + forwardConf := ServerConfig{ + UDPListenAddr: &net.UDPAddr{}, + TCPListenAddr: &net.TCPAddr{}, + FilteringConfig: FilteringConfig{ + ProtectionEnabled: true, + FilterHandler: func(_ net.IP, _ string, settings *dnsfilter.RequestFilteringSettings) { + settings.FilteringEnabled = false + }, + }, } + s := createTestServer(t, &dnsfilter.Config{}, forwardConf) s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{ - &testUpstream{ - cn: testCNAMEs, - ipv4: testIPv4, + &aghtest.TestUpstream{ + CName: testCNAMEs, + IPv4: testIPv4, }, } startDeferStop(t, s) @@ -531,8 +518,15 @@ func TestClientRulesForCNAMEMatching(t *testing.T) { } func TestNullBlockedRequest(t *testing.T) { - s := createTestServer(t) - s.conf.FilteringConfig.BlockingMode = "null_ip" + forwardConf := ServerConfig{ + UDPListenAddr: &net.UDPAddr{}, + TCPListenAddr: &net.TCPAddr{}, + FilteringConfig: FilteringConfig{ + ProtectionEnabled: true, + BlockingMode: "null_ip", + }, + } + s := createTestServer(t, &dnsfilter.Config{}, forwardConf) startDeferStop(t, s) addr := s.dnsProxy.Addr(proxy.ProtoUDP) @@ -568,8 +562,8 @@ func TestBlockedCustomIP(t *testing.T) { DNSFilter: dnsfilter.New(&dnsfilter.Config{}, filters), }) conf := ServerConfig{ - UDPListenAddr: &net.UDPAddr{Port: 0}, - TCPListenAddr: &net.TCPAddr{Port: 0}, + UDPListenAddr: &net.UDPAddr{}, + TCPListenAddr: &net.TCPAddr{}, FilteringConfig: FilteringConfig{ ProtectionEnabled: true, BlockingMode: "custom_ip", @@ -606,7 +600,14 @@ func TestBlockedCustomIP(t *testing.T) { } func TestBlockedByHosts(t *testing.T) { - s := createTestServer(t) + forwardConf := ServerConfig{ + UDPListenAddr: &net.UDPAddr{}, + TCPListenAddr: &net.TCPAddr{}, + FilteringConfig: FilteringConfig{ + ProtectionEnabled: true, + }, + } + s := createTestServer(t, &dnsfilter.Config{}, forwardConf) startDeferStop(t, s) addr := s.dnsProxy.Addr(proxy.ProtoUDP) @@ -623,24 +624,32 @@ func TestBlockedByHosts(t *testing.T) { } func TestBlockedBySafeBrowsing(t *testing.T) { - t.Skip("TODO(e.burkov): substitute the dnsfilter by one with custom safeBrowsingUpstream") - resolver := &testResolver{} - ips, _ := resolver.LookupIPAddr(context.Background(), safeBrowsingBlockHost) - addrs, _ := resolver.LookupHost(safeBrowsingBlockHost) + const hostname = "wmconvirus.narod.ru" - s := createTestServer(t) - s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{ - &testUpstream{ - ipv4: map[string][]net.IP{ - "wmconvirus.narod.ru.": {ips[0].IP}, - }, + sbUps := &aghtest.TestBlockUpstream{ + Hostname: hostname, + Block: true, + } + ans, _ := (&aghtest.TestResolver{}).HostToIPs(hostname) + + filterConf := &dnsfilter.Config{ + SafeBrowsingEnabled: true, + } + forwardConf := ServerConfig{ + UDPListenAddr: &net.UDPAddr{}, + TCPListenAddr: &net.TCPAddr{}, + FilteringConfig: FilteringConfig{ + SafeBrowsingBlockHost: ans.String(), + ProtectionEnabled: true, }, } + s := createTestServer(t, filterConf, forwardConf) + s.dnsFilter.SetSafeBrowsingUpstream(sbUps) startDeferStop(t, s) addr := s.dnsProxy.Addr(proxy.ProtoUDP) // SafeBrowsing blocking. - req := createTestMessage("wmconvirus.narod.ru.") + req := createTestMessage(hostname + ".") reply, err := dns.Exchange(req, addr.String()) assert.Nilf(t, err, "couldn't talk to server %s: %s", addr, err) @@ -648,14 +657,7 @@ func TestBlockedBySafeBrowsing(t *testing.T) { a, ok := reply.Answer[0].(*dns.A) if assert.Truef(t, ok, "dns server %s returned wrong answer type instead of A: %v", addr, reply.Answer[0]) { - found := false - for _, blockAddr := range addrs { - if blockAddr == a.A.String() { - found = true - break - } - } - assert.Truef(t, found, "dns server %s returned wrong answer: %v", addr, a.A) + assert.Equal(t, ans, a.A, "dns server %s returned wrong answer: %v", addr, a.A) } } @@ -679,19 +681,19 @@ func TestRewrite(t *testing.T) { s := NewServer(DNSCreateParams{DNSFilter: f}) err := s.Prepare(&ServerConfig{ - UDPListenAddr: &net.UDPAddr{Port: 0}, - TCPListenAddr: &net.TCPAddr{Port: 0}, + UDPListenAddr: &net.UDPAddr{}, + TCPListenAddr: &net.TCPAddr{}, FilteringConfig: FilteringConfig{ ProtectionEnabled: true, UpstreamDNS: []string{"8.8.8.8:53"}, }, }) s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{ - &testUpstream{ - cn: map[string]string{ + &aghtest.TestUpstream{ + CName: map[string]string{ "example.org": "somename", }, - ipv4: map[string][]net.IP{ + IPv4: map[string][]net.IP{ "example.org.": {{4, 3, 2, 1}}, }, }, @@ -724,13 +726,14 @@ func TestRewrite(t *testing.T) { req = createTestMessageWithType("my.alias.example.org.", dns.TypeA) reply, err = dns.Exchange(req, addr.String()) assert.Nil(t, err) - assert.Equal(t, "my.alias.example.org.", reply.Question[0].Name) // the original question is restored + // The original question is restored. + assert.Equal(t, "my.alias.example.org.", reply.Question[0].Name) assert.Len(t, reply.Answer, 2) assert.Equal(t, "example.org.", reply.Answer[0].(*dns.CNAME).Target) assert.Equal(t, dns.TypeA, reply.Answer[1].Header().Rrtype) } -func createTestServer(t *testing.T) *Server { +func createTestServer(t *testing.T, filterConf *dnsfilter.Config, forwardConf ServerConfig) *Server { rules := `||nxdomain.example.org ||null.example.org^ 127.0.0.1 host.example.org @@ -739,30 +742,13 @@ func createTestServer(t *testing.T) *Server { filters := []dnsfilter.Filter{{ ID: 0, Data: []byte(rules), }} - c := dnsfilter.Config{ - SafeBrowsingEnabled: true, - SafeBrowsingCacheSize: 1000, - SafeSearchEnabled: true, - SafeSearchCacheSize: 1000, - ParentalCacheSize: 1000, - CacheTime: 30, - } - f := dnsfilter.New(&c, filters) + f := dnsfilter.New(filterConf, filters) s := NewServer(DNSCreateParams{DNSFilter: f}) - s.conf = ServerConfig{ - UDPListenAddr: &net.UDPAddr{Port: 0}, - TCPListenAddr: &net.TCPAddr{Port: 0}, - FilteringConfig: FilteringConfig{ - ProtectionEnabled: true, - UpstreamDNS: []string{"8.8.8.8:53", "8.8.4.4:53"}, - }, - ConfigModified: func() {}, - } + s.conf = forwardConf + assert.Nil(t, s.Prepare(nil)) - err := s.Prepare(nil) - assert.Nil(t, err) return s } @@ -849,15 +835,6 @@ func sendTestMessages(t *testing.T, conn *dns.Conn) { } } -func exchangeAndAssertResponse(t *testing.T, client *dns.Client, addr net.Addr, host string, ip net.IP) { - t.Helper() - - req := createTestMessage(host) - reply, _, err := client.Exchange(req, addr.String()) - assert.Nilf(t, err, "couldn't talk to server %s: %s", addr, err) - assertResponse(t, reply, ip) -} - func createGoogleATestMessage() *dns.Msg { return createTestMessage("google-public-dns-a.google.com.") } @@ -879,6 +856,7 @@ func createTestMessage(host string) *dns.Msg { func createTestMessageWithType(host string, qtype uint16) *dns.Msg { req := createTestMessage(host) req.Question[0].Qtype = qtype + return req } @@ -889,7 +867,10 @@ func assertGoogleAResponse(t *testing.T, reply *dns.Msg) { func assertResponse(t *testing.T, reply *dns.Msg, ip net.IP) { t.Helper() - assert.Lenf(t, reply.Answer, 1, "dns server returned reply with wrong number of answers - %d", len(reply.Answer)) + if !assert.Lenf(t, reply.Answer, 1, "dns server returned reply with wrong number of answers - %d", len(reply.Answer)) { + return + } + a, ok := reply.Answer[0].(*dns.A) if assert.Truef(t, ok, "dns server returned wrong answer type instead of A: %v", reply.Answer[0]) { assert.Truef(t, a.A.Equal(ip), "dns server returned wrong answer instead of %s: %s", ip, a.A) @@ -900,8 +881,10 @@ func publicKey(priv interface{}) interface{} { switch k := priv.(type) { case *rsa.PrivateKey: return &k.PublicKey + case *ecdsa.PrivateKey: return &k.PublicKey + default: return nil } @@ -1082,6 +1065,7 @@ func (d *testDHCP) Leases(flags int) []dhcpd.Lease { HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, Hostname: "localhost", } + return []dhcpd.Lease{l} } func (d *testDHCP) SetOnLeaseChanged(onLeaseChanged dhcpd.OnLeaseChangedT) {} @@ -1094,8 +1078,8 @@ func TestPTRResponseFromDHCPLeases(t *testing.T) { DHCPServer: dhcp, }) - s.conf.UDPListenAddr = &net.UDPAddr{Port: 0} - s.conf.TCPListenAddr = &net.TCPAddr{Port: 0} + s.conf.UDPListenAddr = &net.UDPAddr{} + s.conf.TCPListenAddr = &net.TCPAddr{} s.conf.UpstreamDNS = []string{"127.0.0.1:53"} s.conf.FilteringConfig.ProtectionEnabled = true err := s.Prepare(nil) @@ -1143,8 +1127,8 @@ func TestPTRResponseFromHosts(t *testing.T) { t.Cleanup(c.AutoHosts.Close) s := NewServer(DNSCreateParams{DNSFilter: dnsfilter.New(&c, nil)}) - s.conf.UDPListenAddr = &net.UDPAddr{Port: 0} - s.conf.TCPListenAddr = &net.TCPAddr{Port: 0} + s.conf.UDPListenAddr = &net.UDPAddr{} + s.conf.TCPListenAddr = &net.TCPAddr{} s.conf.UpstreamDNS = []string{"127.0.0.1:53"} s.conf.FilteringConfig.ProtectionEnabled = true assert.Nil(t, s.Prepare(nil)) diff --git a/internal/dnsforward/http_test.go b/internal/dnsforward/http_test.go index c8e2f9c5..f1ba7031 100644 --- a/internal/dnsforward/http_test.go +++ b/internal/dnsforward/http_test.go @@ -2,16 +2,35 @@ package dnsforward import ( "io/ioutil" + "net" "net/http" "net/http/httptest" "strings" "testing" + "github.com/AdguardTeam/AdGuardHome/internal/dnsfilter" "github.com/stretchr/testify/assert" ) func TestDNSForwardHTTTP_handleGetConfig(t *testing.T) { - s := createTestServer(t) + filterConf := &dnsfilter.Config{ + SafeBrowsingEnabled: true, + SafeBrowsingCacheSize: 1000, + SafeSearchEnabled: true, + SafeSearchCacheSize: 1000, + ParentalCacheSize: 1000, + CacheTime: 30, + } + forwardConf := ServerConfig{ + UDPListenAddr: &net.UDPAddr{}, + TCPListenAddr: &net.TCPAddr{}, + FilteringConfig: FilteringConfig{ + ProtectionEnabled: true, + UpstreamDNS: []string{"8.8.8.8:53", "8.8.4.4:53"}, + }, + ConfigModified: func() {}, + } + s := createTestServer(t, filterConf, forwardConf) err := s.Start() assert.Nil(t, err) defer assert.Nil(t, s.Stop()) @@ -35,6 +54,7 @@ func TestDNSForwardHTTTP_handleGetConfig(t *testing.T) { conf: func() ServerConfig { conf := defaultConf conf.FastestAddr = true + return conf }, want: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"fastest_addr\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}\n", @@ -43,6 +63,7 @@ func TestDNSForwardHTTTP_handleGetConfig(t *testing.T) { conf: func() ServerConfig { conf := defaultConf conf.AllServers = true + return conf }, want: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"parallel\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}\n", @@ -61,7 +82,24 @@ func TestDNSForwardHTTTP_handleGetConfig(t *testing.T) { } func TestDNSForwardHTTTP_handleSetConfig(t *testing.T) { - s := createTestServer(t) + filterConf := &dnsfilter.Config{ + SafeBrowsingEnabled: true, + SafeBrowsingCacheSize: 1000, + SafeSearchEnabled: true, + SafeSearchCacheSize: 1000, + ParentalCacheSize: 1000, + CacheTime: 30, + } + forwardConf := ServerConfig{ + UDPListenAddr: &net.UDPAddr{}, + TCPListenAddr: &net.TCPAddr{}, + FilteringConfig: FilteringConfig{ + ProtectionEnabled: true, + UpstreamDNS: []string{"8.8.8.8:53", "8.8.4.4:53"}, + }, + ConfigModified: func() {}, + } + s := createTestServer(t, filterConf, forwardConf) defaultConf := s.conf diff --git a/internal/home/auth_test.go b/internal/home/auth_test.go index e44b7e83..4dbd07b6 100644 --- a/internal/home/auth_test.go +++ b/internal/home/auth_test.go @@ -9,12 +9,12 @@ import ( "testing" "time" - "github.com/AdguardTeam/AdGuardHome/internal/testutil" + "github.com/AdguardTeam/AdGuardHome/internal/aghtest" "github.com/stretchr/testify/assert" ) func TestMain(m *testing.M) { - testutil.DiscardLogOutput(m) + aghtest.DiscardLogOutput(m) } func prepareTestDir() string { diff --git a/internal/home/rdns.go b/internal/home/rdns.go index dad75e44..c21a6f6e 100644 --- a/internal/home/rdns.go +++ b/internal/home/rdns.go @@ -27,18 +27,18 @@ type RDNS struct { // InitRDNS - create module context func InitRDNS(dnsServer *dnsforward.Server, clients *clientsContainer) *RDNS { - r := RDNS{} - r.dnsServer = dnsServer - r.clients = clients + r := &RDNS{ + dnsServer: dnsServer, + clients: clients, + ipAddrs: cache.New(cache.Config{ + EnableLRU: true, + MaxCount: 10000, + }), + ipChannel: make(chan net.IP, 256), + } - cconf := cache.Config{} - cconf.EnableLRU = true - cconf.MaxCount = 10000 - r.ipAddrs = cache.New(cconf) - - r.ipChannel = make(chan net.IP, 256) go r.workerLoop() - return &r + return r } // Begin - add IP address to rDNS queue @@ -75,23 +75,23 @@ func (r *RDNS) Begin(ip net.IP) { func (r *RDNS) resolve(ip net.IP) string { log.Tracef("Resolving host for %s", ip) - req := dns.Msg{} - req.Id = dns.Id() - req.RecursionDesired = true - req.Question = []dns.Question{ - { - Qtype: dns.TypePTR, - Qclass: dns.ClassINET, - }, - } - var err error - req.Question[0].Name, err = dns.ReverseAddr(ip.String()) + name, err := dns.ReverseAddr(ip.String()) if err != nil { log.Debug("Error while calling dns.ReverseAddr(%s): %s", ip, err) return "" } - resp, err := r.dnsServer.Exchange(&req) + resp, err := r.dnsServer.Exchange(&dns.Msg{ + MsgHdr: dns.MsgHdr{ + Id: dns.Id(), + RecursionDesired: true, + }, + Question: []dns.Question{{ + Name: name, + Qtype: dns.TypePTR, + Qclass: dns.ClassINET, + }}, + }) if err != nil { log.Debug("Error while making an rDNS lookup for %s: %s", ip, err) return "" diff --git a/internal/home/rdns_test.go b/internal/home/rdns_test.go index 53dd093d..b17efdd8 100644 --- a/internal/home/rdns_test.go +++ b/internal/home/rdns_test.go @@ -4,16 +4,26 @@ import ( "net" "testing" + "github.com/AdguardTeam/AdGuardHome/internal/aghtest" "github.com/AdguardTeam/AdGuardHome/internal/dnsforward" + "github.com/AdguardTeam/dnsproxy/proxy" + "github.com/AdguardTeam/dnsproxy/upstream" "github.com/stretchr/testify/assert" ) func TestResolveRDNS(t *testing.T) { - dns := &dnsforward.Server{} - conf := &dnsforward.ServerConfig{} - conf.UpstreamDNS = []string{"8.8.8.8"} - err := dns.Prepare(conf) - assert.Nil(t, err) + ups := &aghtest.TestUpstream{ + Reverse: map[string][]string{ + "1.1.1.1.in-addr.arpa.": {"one.one.one.one"}, + }, + } + dns := dnsforward.NewCustomServer(&proxy.Proxy{ + Config: proxy.Config{ + UpstreamConfig: &proxy.UpstreamConfig{ + Upstreams: []upstream.Upstream{ups}, + }, + }, + }) clients := &clientsContainer{} rdns := InitRDNS(dns, clients) diff --git a/internal/home/whois_test.go b/internal/home/whois_test.go index a160cdda..ed72740d 100644 --- a/internal/home/whois_test.go +++ b/internal/home/whois_test.go @@ -13,9 +13,14 @@ func prepareTestDNSServer() error { Context.dnsServer = dnsforward.NewServer(dnsforward.DNSCreateParams{}) conf := &dnsforward.ServerConfig{} conf.UpstreamDNS = []string{"8.8.8.8"} + return Context.dnsServer.Prepare(conf) } +// TODO(e.burkov): It's kind of complicated to get rid of network access in this +// test. The thing is that *Whois creates new *net.Dialer each time it requests +// the server, so it becomes hard to simulate handling of request from test even +// with substituted upstream. However, it must be done. func TestWhois(t *testing.T) { assert.Nil(t, prepareTestDNSServer()) diff --git a/internal/querylog/decode_test.go b/internal/querylog/decode_test.go index 58ca5851..fe64a624 100644 --- a/internal/querylog/decode_test.go +++ b/internal/querylog/decode_test.go @@ -8,8 +8,8 @@ import ( "testing" "time" + "github.com/AdguardTeam/AdGuardHome/internal/aghtest" "github.com/AdguardTeam/AdGuardHome/internal/dnsfilter" - "github.com/AdguardTeam/AdGuardHome/internal/testutil" "github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/urlfilter/rules" "github.com/miekg/dns" @@ -19,8 +19,8 @@ import ( func TestDecodeLogEntry(t *testing.T) { logOutput := &bytes.Buffer{} - testutil.ReplaceLogWriter(t, logOutput) - testutil.ReplaceLogLevel(t, log.DEBUG) + aghtest.ReplaceLogWriter(t, logOutput) + aghtest.ReplaceLogLevel(t, log.DEBUG) t.Run("success", func(t *testing.T) { const ansStr = `Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==` diff --git a/internal/querylog/qlog_test.go b/internal/querylog/qlog_test.go index 24d9064e..07fac8ff 100644 --- a/internal/querylog/qlog_test.go +++ b/internal/querylog/qlog_test.go @@ -10,14 +10,14 @@ import ( "github.com/AdguardTeam/dnsproxy/proxyutil" + "github.com/AdguardTeam/AdGuardHome/internal/aghtest" "github.com/AdguardTeam/AdGuardHome/internal/dnsfilter" - "github.com/AdguardTeam/AdGuardHome/internal/testutil" "github.com/miekg/dns" "github.com/stretchr/testify/assert" ) func TestMain(m *testing.M) { - testutil.DiscardLogOutput(m) + aghtest.DiscardLogOutput(m) } func prepareTestDir() string { diff --git a/internal/stats/stats_test.go b/internal/stats/stats_test.go index 06163c6c..7643b31a 100644 --- a/internal/stats/stats_test.go +++ b/internal/stats/stats_test.go @@ -7,12 +7,12 @@ import ( "sync/atomic" "testing" - "github.com/AdguardTeam/AdGuardHome/internal/testutil" + "github.com/AdguardTeam/AdGuardHome/internal/aghtest" "github.com/stretchr/testify/assert" ) func TestMain(m *testing.M) { - testutil.DiscardLogOutput(m) + aghtest.DiscardLogOutput(m) } func UIntArrayEquals(a, b []uint64) bool { diff --git a/internal/sysutil/sysutil_test.go b/internal/sysutil/sysutil_test.go index 0cddbf42..8ca0ff66 100644 --- a/internal/sysutil/sysutil_test.go +++ b/internal/sysutil/sysutil_test.go @@ -3,9 +3,9 @@ package sysutil import ( "testing" - "github.com/AdguardTeam/AdGuardHome/internal/testutil" + "github.com/AdguardTeam/AdGuardHome/internal/aghtest" ) func TestMain(m *testing.M) { - testutil.DiscardLogOutput(m) + aghtest.DiscardLogOutput(m) } diff --git a/internal/updater/updater_test.go b/internal/updater/updater_test.go index a8923ee2..197e2142 100644 --- a/internal/updater/updater_test.go +++ b/internal/updater/updater_test.go @@ -11,7 +11,7 @@ import ( "strconv" "testing" - "github.com/AdguardTeam/AdGuardHome/internal/testutil" + "github.com/AdguardTeam/AdGuardHome/internal/aghtest" "github.com/AdguardTeam/AdGuardHome/internal/version" "github.com/stretchr/testify/assert" ) @@ -19,7 +19,7 @@ import ( // TODO(a.garipov): Rewrite these tests. func TestMain(m *testing.M) { - testutil.DiscardLogOutput(m) + aghtest.DiscardLogOutput(m) } func startHTTPServer(data string) (l net.Listener, portStr string) { diff --git a/internal/util/autohosts_test.go b/internal/util/autohosts_test.go index b9632855..c5f26934 100644 --- a/internal/util/autohosts_test.go +++ b/internal/util/autohosts_test.go @@ -8,13 +8,13 @@ import ( "testing" "time" - "github.com/AdguardTeam/AdGuardHome/internal/testutil" + "github.com/AdguardTeam/AdGuardHome/internal/aghtest" "github.com/miekg/dns" "github.com/stretchr/testify/assert" ) func TestMain(m *testing.M) { - testutil.DiscardLogOutput(m) + aghtest.DiscardLogOutput(m) } func prepareTestDir() string { From 245f197f95aff20b7178ca8dd4d35d97f61b1d49 Mon Sep 17 00:00:00 2001 From: Andrey Meshkov Date: Fri, 5 Feb 2021 00:10:43 +0300 Subject: [PATCH 094/141] Actualize vetted blocklists --- client/src/helpers/filters/filters.json | 28 ++++++++++++++++++------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/client/src/helpers/filters/filters.json b/client/src/helpers/filters/filters.json index 7bc4c9c2..192973a9 100644 --- a/client/src/helpers/filters/filters.json +++ b/client/src/helpers/filters/filters.json @@ -54,12 +54,6 @@ "homepage": "https://github.com/Perflyst/PiHoleBlocklist", "source": "https://raw.githubusercontent.com/Perflyst/PiHoleBlocklist/master/SmartTV-AGH.txt" }, - "malwaredomainlist-com-hosts-list": { - "name": "MalwareDomainList.com Hosts List", - "categoryId": "security", - "homepage": "https://www.malwaredomainlist.com/", - "source": "https://www.malwaredomainlist.com/hostslist/hosts.txt" - }, "spam404": { "name": "Spam404", "categoryId": "security", @@ -76,7 +70,7 @@ "name": "The Big List of Hacked Malware Web Sites", "categoryId": "security", "homepage": "https://github.com/mitchellkrogza/The-Big-List-of-Hacked-Malware-Web-Sites", - "source": "https://raw.githubusercontent.com/mitchellkrogza/The-Big-List-of-Hacked-Malware-Web-Sites/master/hacked-domains.list" + "source": "https://raw.githubusercontent.com/mitchellkrogza/The-Big-List-of-Hacked-Malware-Web-Sites/master/hosts" }, "scam-blocklist-by-durable-napkin": { "name": "Scam Blocklist by DurableNapkin", @@ -84,6 +78,12 @@ "homepage": "https://github.com/durablenapkin/scamblocklist", "source": "https://raw.githubusercontent.com/durablenapkin/scamblocklist/master/adguard.txt" }, + "urlhaus-filter-online": { + "name": "Online Malicious URL Blocklist", + "categoryId": "security", + "homepage": "https://gitlab.com/curben/urlhaus-filter", + "source": "https://curben.gitlab.io/malware-filter/urlhaus-filter-agh-online.txt" + }, "NOR-dandelion-sprouts-nordiske-filtre": { "name": "NOR: Dandelion Sprouts nordiske filtre", "categoryId": "regional", @@ -144,11 +144,23 @@ "homepage": "https://anti-ad.net/", "source": "https://anti-ad.net/easylist.txt" }, - "BarbBlock": { + "IDN-abpindo": { + "name": "IDN: ABPindo", + "categoryId": "regional", + "homepage": "https://github.com/ABPindo/indonesianadblockrules/", + "source": "https://raw.githubusercontent.com/ABPindo/indonesianadblockrules/master/subscriptions/abpindo.txt" + }, + "barb-block": { "name": "BarbBlock", "categoryId": "other", "homepage": "https://github.com/paulgb/BarbBlock/", "source": "https://paulgb.github.io/BarbBlock/blacklists/hosts-file.txt" + }, + "windows-spy-blocker" : { + "name": "WindowsSpyBlocker - Hosts spy rules", + "categoryId": "other", + "homepage": "https://github.com/crazy-max/WindowsSpyBlocker", + "source": "https://raw.githubusercontent.com/crazy-max/WindowsSpyBlocker/master/data/hosts/spy.txt" } } } From cbceab0f65a84426e123d684d3c671d6533bd4a2 Mon Sep 17 00:00:00 2001 From: Andrey Meshkov Date: Fri, 5 Feb 2021 00:16:20 +0300 Subject: [PATCH 095/141] Change category for WindowsSpyBlocker to general --- client/src/helpers/filters/filters.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/client/src/helpers/filters/filters.json b/client/src/helpers/filters/filters.json index 192973a9..6cd61084 100644 --- a/client/src/helpers/filters/filters.json +++ b/client/src/helpers/filters/filters.json @@ -54,6 +54,12 @@ "homepage": "https://github.com/Perflyst/PiHoleBlocklist", "source": "https://raw.githubusercontent.com/Perflyst/PiHoleBlocklist/master/SmartTV-AGH.txt" }, + "windows-spy-blocker" : { + "name": "WindowsSpyBlocker - Hosts spy rules", + "categoryId": "general", + "homepage": "https://github.com/crazy-max/WindowsSpyBlocker", + "source": "https://raw.githubusercontent.com/crazy-max/WindowsSpyBlocker/master/data/hosts/spy.txt" + }, "spam404": { "name": "Spam404", "categoryId": "security", @@ -155,12 +161,6 @@ "categoryId": "other", "homepage": "https://github.com/paulgb/BarbBlock/", "source": "https://paulgb.github.io/BarbBlock/blacklists/hosts-file.txt" - }, - "windows-spy-blocker" : { - "name": "WindowsSpyBlocker - Hosts spy rules", - "categoryId": "other", - "homepage": "https://github.com/crazy-max/WindowsSpyBlocker", - "source": "https://raw.githubusercontent.com/crazy-max/WindowsSpyBlocker/master/data/hosts/spy.txt" } } } From bc01f4f8bcf6acef8dbe40d082a881c82cff42cf Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Fri, 5 Feb 2021 14:11:53 +0300 Subject: [PATCH 096/141] Pull request: home: don't allow all origins Merge in DNS/adguard-home from 2484-access-control to master Updates #2484. Squashed commit of the following: commit 4f0c6ddae35b55a9251f4d5e14c4d92fd2107443 Author: Ainar Garipov Date: Fri Feb 5 12:42:22 2021 +0300 home: don't allow all origins --- CHANGELOG.md | 3 ++ internal/home/control.go | 89 ++++++++++++++++++++++++++++---------- internal/home/home_test.go | 2 +- 3 files changed, 71 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 858d11e4..e9da24ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,8 @@ and this project adheres to ### Changed +- `Access-Control-Allow-Origin` is now only set to the same origin as the + domain, but with an HTTP scheme as opposed to `*` ([#2484]). - `workDir` now supports symlinks. - Stopped mounting together the directories `/opt/adguardhome/conf` and `/opt/adguardhome/work` in our Docker images ([#2589]). @@ -67,6 +69,7 @@ and this project adheres to [#2358]: https://github.com/AdguardTeam/AdGuardHome/issues/2358 [#2391]: https://github.com/AdguardTeam/AdGuardHome/issues/2391 [#2394]: https://github.com/AdguardTeam/AdGuardHome/issues/2394 +[#2484]: https://github.com/AdguardTeam/AdGuardHome/issues/2484 [#2509]: https://github.com/AdguardTeam/AdGuardHome/issues/2509 [#2552]: https://github.com/AdguardTeam/AdGuardHome/issues/2552 [#2589]: https://github.com/AdguardTeam/AdGuardHome/issues/2589 diff --git a/internal/home/control.go b/internal/home/control.go index 49d6b31c..71bf52e5 100644 --- a/internal/home/control.go +++ b/internal/home/control.go @@ -2,6 +2,7 @@ package home import ( "encoding/json" + "errors" "fmt" "net" "net/http" @@ -202,37 +203,81 @@ func preInstallHandler(handler http.Handler) http.Handler { return &preInstallHandlerStruct{handler} } -// postInstall lets the handler run only if firstRun is false, and redirects to /install.html otherwise -// it also enforces HTTPS if it is enabled and configured +const defaultHTTPSPort = 443 + +// handleHTTPSRedirect redirects the request to HTTPS, if needed. If ok is +// true, the middleware must continue handling the request. +func handleHTTPSRedirect(w http.ResponseWriter, r *http.Request) (ok bool) { + web := Context.web + if web.httpsServer.server == nil { + return true + } + + host, _, err := net.SplitHostPort(r.Host) + if err != nil { + // Check for the missing port error. If it is that error, just + // use the host as is. + // + // See the source code for net.SplitHostPort. + const missingPort = "missing port in address" + + addrErr := &net.AddrError{} + if !errors.As(err, &addrErr) || addrErr.Err != missingPort { + httpError(w, http.StatusBadRequest, "bad host: %s", err) + + return false + } + + host = r.Host + } + + if r.TLS == nil && web.forceHTTPS { + hostPort := host + if port := web.conf.PortHTTPS; port != defaultHTTPSPort { + portStr := strconv.Itoa(port) + hostPort = net.JoinHostPort(host, portStr) + } + + httpsURL := &url.URL{ + Scheme: "https", + Host: hostPort, + Path: r.URL.Path, + RawQuery: r.URL.RawQuery, + } + http.Redirect(w, r, httpsURL.String(), http.StatusTemporaryRedirect) + + return false + } + + // Allow the frontend from the HTTP origin to send requests to the HTTPS + // server. This can happen when the user has just set up HTTPS with + // redirects. + originURL := &url.URL{ + Scheme: "http", + Host: r.Host, + } + w.Header().Set("Access-Control-Allow-Origin", originURL.String()) + + return true +} + +// postInstall lets the handler to run only if firstRun is false. Otherwise, it +// redirects to /install.html. It also enforces HTTPS if it is enabled and +// configured and sets appropriate access control headers. func postInstall(handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { - if Context.firstRun && - !strings.HasPrefix(r.URL.Path, "/install.") && - !strings.HasPrefix(r.URL.Path, "/assets/") { + path := r.URL.Path + if Context.firstRun && !strings.HasPrefix(path, "/install.") && + !strings.HasPrefix(path, "/assets/") { http.Redirect(w, r, "/install.html", http.StatusFound) + return } - // enforce https? - if r.TLS == nil && Context.web.forceHTTPS && Context.web.httpsServer.server != nil { - // yes, and we want host from host:port - host, _, err := net.SplitHostPort(r.Host) - if err != nil { - // no port in host - host = r.Host - } - // construct new URL to redirect to - newURL := url.URL{ - Scheme: "https", - Host: net.JoinHostPort(host, strconv.Itoa(Context.web.conf.PortHTTPS)), - Path: r.URL.Path, - RawQuery: r.URL.RawQuery, - } - http.Redirect(w, r, newURL.String(), http.StatusTemporaryRedirect) + if !handleHTTPSRedirect(w, r) { return } - w.Header().Set("Access-Control-Allow-Origin", "*") handler(w, r) } } diff --git a/internal/home/home_test.go b/internal/home/home_test.go index 5f3f3d81..02613b7c 100644 --- a/internal/home/home_test.go +++ b/internal/home/home_test.go @@ -133,7 +133,7 @@ func TestHome(t *testing.T) { h := http.Client{} for i := 0; i != 50; i++ { resp, err = h.Get("http://127.0.0.1:3000/") - if err == nil && resp.StatusCode != 404 { + if err == nil && resp.StatusCode != http.StatusNotFound { break } time.Sleep(100 * time.Millisecond) From e84effffc386fccfb3165279ff959f4537c3c6d0 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Fri, 5 Feb 2021 15:17:18 +0300 Subject: [PATCH 097/141] Pull request #976: all: imp cyclomatic complexity, minor improvements Merge in DNS/adguard-home from less-cyclo to master Updates #2646. Squashed commit of the following: commit 42f81c9f716ca7a2878d481b96d31d86f7c4151b Merge: f61e2366 42b88c32 Author: Ainar Garipov Date: Fri Feb 5 15:07:04 2021 +0300 Merge branch 'master' into less-cyclo commit f61e2366ea10a289cec2f76b700e7117850cd4e2 Author: Ainar Garipov Date: Fri Feb 5 15:06:02 2021 +0300 home: imp docs commit e3c2310ce09cb78dd53ebbe728de4d2e72e6caf7 Author: Ainar Garipov Date: Wed Jan 27 21:14:30 2021 +0300 all: imp cyclomatic complexity, minor improvements --- internal/dhcpd/dhcphttp.go | 52 ++++++++++++++++++++++++++++---------- internal/home/whois.go | 23 ++++++++--------- scripts/make/go-lint.sh | 2 +- 3 files changed, 49 insertions(+), 28 deletions(-) diff --git a/internal/dhcpd/dhcphttp.go b/internal/dhcpd/dhcphttp.go index e38afbca..b6b5c729 100644 --- a/internal/dhcpd/dhcphttp.go +++ b/internal/dhcpd/dhcphttp.go @@ -90,6 +90,28 @@ type dhcpServerConfigJSON struct { V6 v6ServerConfJSON `json:"v6"` } +func (s *Server) enableDHCP(ifaceName string) (code int, err error) { + var hasStaticIP bool + hasStaticIP, err = sysutil.IfaceHasStaticIP(ifaceName) + if err != nil { + return http.StatusInternalServerError, fmt.Errorf("checking static ip: %w", err) + } + + if !hasStaticIP { + err = sysutil.IfaceSetStaticIP(ifaceName) + if err != nil { + return http.StatusInternalServerError, fmt.Errorf("setting static ip: %w", err) + } + } + + err = s.Start() + if err != nil { + return http.StatusBadRequest, fmt.Errorf("starting dhcp server: %w", err) + } + + return 0, nil +} + func (s *Server) handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) { newconfig := dhcpServerConfigJSON{} newconfig.Enabled = s.conf.Enabled @@ -98,6 +120,7 @@ func (s *Server) handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) { js, err := jsonutil.DecodeObject(&newconfig, r.Body) if err != nil { httpError(r, w, http.StatusBadRequest, "Failed to parse new DHCP config json: %s", err) + return } @@ -112,6 +135,7 @@ func (s *Server) handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) { if len(v4conf.RangeStart) == 0 { v4conf.Enabled = false } + v4Enabled = v4conf.Enabled v4conf.InterfaceName = newconfig.InterfaceName @@ -122,7 +146,8 @@ func (s *Server) handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) { s4, err = v4Create(v4conf) if err != nil { - httpError(r, w, http.StatusBadRequest, "Invalid DHCPv4 configuration: %s", err) + httpError(r, w, http.StatusBadRequest, "invalid dhcpv4 configuration: %s", err) + return } } @@ -133,18 +158,22 @@ func (s *Server) handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) { if len(v6conf.RangeStart) == 0 { v6conf.Enabled = false } + v6Enabled = v6conf.Enabled v6conf.InterfaceName = newconfig.InterfaceName v6conf.notify = s.onNotify + s6, err = v6Create(v6conf) if err != nil { - httpError(r, w, http.StatusBadRequest, "Invalid DHCPv6 configuration: %s", err) + httpError(r, w, http.StatusBadRequest, "invalid dhcpv6 configuration: %s", err) + return } } if newconfig.Enabled && !v4Enabled && !v6Enabled { - httpError(r, w, http.StatusBadRequest, "DHCPv4 or DHCPv6 configuration must be complete") + httpError(r, w, http.StatusBadRequest, "dhcpv4 or dhcpv6 configuration must be complete") + return } @@ -161,25 +190,20 @@ func (s *Server) handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) { if s4 != nil { s.srv4 = s4 } + if s6 != nil { s.srv6 = s6 } + s.conf.ConfigModified() s.dbLoad() if s.conf.Enabled { - staticIP, err := sysutil.IfaceHasStaticIP(newconfig.InterfaceName) - if !staticIP && err == nil { - err = sysutil.IfaceSetStaticIP(newconfig.InterfaceName) - if err != nil { - httpError(r, w, http.StatusInternalServerError, "Failed to configure static IP: %s", err) - return - } - } - - err = s.Start() + var code int + code, err = s.enableDHCP(newconfig.InterfaceName) if err != nil { - httpError(r, w, http.StatusBadRequest, "Failed to start DHCP server: %s", err) + httpError(r, w, code, "enabling dhcp: %s", err) + return } } diff --git a/internal/home/whois.go b/internal/home/whois.go index 26c674dc..20f035e2 100644 --- a/internal/home/whois.go +++ b/internal/home/whois.go @@ -83,23 +83,16 @@ func whoisParse(data string) map[string]string { switch k { case "org-name": m["orgname"] = trimValue(v) - case "orgname": - fallthrough - case "city": - fallthrough - case "country": + case "city", "country", "orgname": m[k] = trimValue(v) - case "descr": if len(descr) == 0 { descr = v } case "netname": netname = v - case "whois": // "whois: whois.arin.net" m["whois"] = v - case "referralserver": // "ReferralServer: whois://whois.ripe.net" if strings.HasPrefix(v, "whois://") { m["whois"] = v[len("whois://"):] @@ -107,12 +100,16 @@ func whoisParse(data string) map[string]string { } } - // descr or netname -> orgname _, ok := m["orgname"] - if !ok && len(descr) != 0 { - m["orgname"] = trimValue(descr) - } else if !ok && len(netname) != 0 { - m["orgname"] = trimValue(netname) + if !ok { + // Set orgname from either descr or netname for the frontent. + // + // TODO(a.garipov): Perhaps don't do that in the V1 HTTP API? + if descr != "" { + m["orgname"] = trimValue(descr) + } else if netname != "" { + m["orgname"] = trimValue(netname) + } } return m diff --git a/scripts/make/go-lint.sh b/scripts/make/go-lint.sh index e1aeb9ef..30ed6141 100644 --- a/scripts/make/go-lint.sh +++ b/scripts/make/go-lint.sh @@ -132,7 +132,7 @@ golint --set_exit_status ./... "$GO" vet ./... -gocyclo --over 20 . +gocyclo --over 19 . gosec --quiet . From c9d883daad089d29598b99f92e543d5ca111dc63 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Fri, 5 Feb 2021 18:36:37 +0300 Subject: [PATCH 098/141] Pull request: all: imp docs, define more conventions Merge in DNS/adguard-home from imp-md to master Squashed commit of the following: commit bb3eba8b248c644672b48c7ccfa206b620dfb0a0 Author: Ainar Garipov Date: Fri Feb 5 18:26:19 2021 +0300 all: imp docs, define more conventions --- CHANGELOG.md | 66 ++++++++++++++++++++++----------------------- HACKING.md | 76 ++++++++++++++++++++++++++++++++++------------------ 2 files changed, 82 insertions(+), 60 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e9da24ab..eac27727 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ and this project adheres to ## [Unreleased] ### Added @@ -30,17 +30,6 @@ and this project adheres to - `$dnstype` modifier for filters ([#2337]). - HTTP API request body size limit ([#2305]). -[#1361]: https://github.com/AdguardTeam/AdGuardHome/issues/1361 -[#1383]: https://github.com/AdguardTeam/AdGuardHome/issues/1383 -[#2102]: https://github.com/AdguardTeam/AdGuardHome/issues/2102 -[#2179]: https://github.com/AdguardTeam/AdGuardHome/issues/2179 -[#2224]: https://github.com/AdguardTeam/AdGuardHome/issues/2224 -[#2302]: https://github.com/AdguardTeam/AdGuardHome/issues/2302 -[#2304]: https://github.com/AdguardTeam/AdGuardHome/issues/2304 -[#2305]: https://github.com/AdguardTeam/AdGuardHome/issues/2305 -[#2337]: https://github.com/AdguardTeam/AdGuardHome/issues/2337 -[#2401]: https://github.com/AdguardTeam/AdGuardHome/issues/2401 - ### Changed - `Access-Control-Allow-Origin` is now only set to the same origin as the @@ -60,20 +49,6 @@ and this project adheres to - New build system and various internal improvements ([#2271], [#2276], [#2297], [#2509], [#2552]). -[#2231]: https://github.com/AdguardTeam/AdGuardHome/issues/2231 -[#2271]: https://github.com/AdguardTeam/AdGuardHome/issues/2271 -[#2276]: https://github.com/AdguardTeam/AdGuardHome/issues/2276 -[#2297]: https://github.com/AdguardTeam/AdGuardHome/issues/2297 -[#2306]: https://github.com/AdguardTeam/AdGuardHome/issues/2306 -[#2343]: https://github.com/AdguardTeam/AdGuardHome/issues/2343 -[#2358]: https://github.com/AdguardTeam/AdGuardHome/issues/2358 -[#2391]: https://github.com/AdguardTeam/AdGuardHome/issues/2391 -[#2394]: https://github.com/AdguardTeam/AdGuardHome/issues/2394 -[#2484]: https://github.com/AdguardTeam/AdGuardHome/issues/2484 -[#2509]: https://github.com/AdguardTeam/AdGuardHome/issues/2509 -[#2552]: https://github.com/AdguardTeam/AdGuardHome/issues/2552 -[#2589]: https://github.com/AdguardTeam/AdGuardHome/issues/2589 - ### Deprecated - Go 1.14 support. v0.106.0 will require at least Go 1.15 to build. @@ -93,14 +68,6 @@ and this project adheres to - Incorrect detection of the IPv6 address of an interface as well as another infinite loop in the `/dhcp/find_active_dhcp` HTTP API ([#2355]). -[#2225]: https://github.com/AdguardTeam/AdGuardHome/issues/2225 -[#2293]: https://github.com/AdguardTeam/AdGuardHome/issues/2293 -[#2345]: https://github.com/AdguardTeam/AdGuardHome/issues/2345 -[#2355]: https://github.com/AdguardTeam/AdGuardHome/issues/2355 -[#2459]: https://github.com/AdguardTeam/AdGuardHome/issues/2459 -[#2508]: https://github.com/AdguardTeam/AdGuardHome/issues/2508 -[#2630]: https://github.com/AdguardTeam/AdGuardHome/issues/2630 - ### Removed - The undocumented ability to use hostnames as any of `bind_host` values in @@ -110,6 +77,37 @@ and this project adheres to `scripts/make/build-docker.sh` which uses `scripts/make/Dockerfile`. - Support for pre-v0.99.3 format of query logs ([#2102]). +[#1361]: https://github.com/AdguardTeam/AdGuardHome/issues/1361 +[#1383]: https://github.com/AdguardTeam/AdGuardHome/issues/1383 +[#2102]: https://github.com/AdguardTeam/AdGuardHome/issues/2102 +[#2179]: https://github.com/AdguardTeam/AdGuardHome/issues/2179 +[#2224]: https://github.com/AdguardTeam/AdGuardHome/issues/2224 +[#2225]: https://github.com/AdguardTeam/AdGuardHome/issues/2225 +[#2231]: https://github.com/AdguardTeam/AdGuardHome/issues/2231 +[#2271]: https://github.com/AdguardTeam/AdGuardHome/issues/2271 +[#2276]: https://github.com/AdguardTeam/AdGuardHome/issues/2276 +[#2293]: https://github.com/AdguardTeam/AdGuardHome/issues/2293 +[#2297]: https://github.com/AdguardTeam/AdGuardHome/issues/2297 +[#2302]: https://github.com/AdguardTeam/AdGuardHome/issues/2302 +[#2304]: https://github.com/AdguardTeam/AdGuardHome/issues/2304 +[#2305]: https://github.com/AdguardTeam/AdGuardHome/issues/2305 +[#2306]: https://github.com/AdguardTeam/AdGuardHome/issues/2306 +[#2337]: https://github.com/AdguardTeam/AdGuardHome/issues/2337 +[#2343]: https://github.com/AdguardTeam/AdGuardHome/issues/2343 +[#2345]: https://github.com/AdguardTeam/AdGuardHome/issues/2345 +[#2355]: https://github.com/AdguardTeam/AdGuardHome/issues/2355 +[#2358]: https://github.com/AdguardTeam/AdGuardHome/issues/2358 +[#2391]: https://github.com/AdguardTeam/AdGuardHome/issues/2391 +[#2394]: https://github.com/AdguardTeam/AdGuardHome/issues/2394 +[#2401]: https://github.com/AdguardTeam/AdGuardHome/issues/2401 +[#2459]: https://github.com/AdguardTeam/AdGuardHome/issues/2459 +[#2484]: https://github.com/AdguardTeam/AdGuardHome/issues/2484 +[#2508]: https://github.com/AdguardTeam/AdGuardHome/issues/2508 +[#2509]: https://github.com/AdguardTeam/AdGuardHome/issues/2509 +[#2552]: https://github.com/AdguardTeam/AdGuardHome/issues/2552 +[#2589]: https://github.com/AdguardTeam/AdGuardHome/issues/2589 +[#2630]: https://github.com/AdguardTeam/AdGuardHome/issues/2630 + ## [v0.104.3] - 2020-11-19 ### Fixed diff --git a/HACKING.md b/HACKING.md index 5e9e4e76..f3c42eb8 100644 --- a/HACKING.md +++ b/HACKING.md @@ -1,17 +1,33 @@ # AdGuard Home Developer Guidelines -As of **December 2020**, this document is partially a work-in-progress, but +As of **February 2021**, this document is partially a work-in-progress, but should still be followed. Some of the rules aren't enforced as thoroughly or remain broken in old code, but this is still the place to find out about what we **want** our code to look like. The rules are mostly sorted in the alphabetical order. -## *Git* +## Contents + + * [Git](#git) + * [Go](#go) + * [Code And Naming](#code-and-naming) + * [Commenting](#commenting) + * [Formatting](#formatting) + * [Recommended Reading](#recommended-reading) + * [Markdown](#markdown) + * [Shell Scripting](#shell-scripting) + * [Text, Including Comments](#text-including-comments) + * [YAML](#yaml) + + + +## Git * Call your branches either `NNNN-fix-foo` (where `NNNN` is the ID of the - *GitHub* issue you worked on in this branch) or just `fix-foo` if there was - no *GitHub* issue. + GitHub issue you worked on in this branch) or just `fix-foo` if there was no + GitHub issue. * Follow the commit message header format: @@ -31,14 +47,14 @@ The rules are mostly sorted in the alphabetical order. The only exceptions are direct mentions of identifiers from the source code and filenames like `HACKING.md`. -## *Go* +## Go > Not Golang, not GO, not GOLANG, not GoLang. It is Go in natural language, > golang for others. — [@rakyll](https://twitter.com/rakyll/status/1229850223184269312) - ### Code And Naming + ### Code And Naming * Avoid `goto`. @@ -116,12 +132,9 @@ The rules are mostly sorted in the alphabetical order. * Write logs and error messages in lowercase only to make it easier to `grep` logs and error messages without using the `-i` flag. -[constant errors]: https://dave.cheney.net/2016/04/07/constant-errors -[Linus said]: https://www.kernel.org/doc/html/v4.17/process/coding-style.html#indentation + ### Commenting - ### Commenting - - * See also the *Text, Including Comments* section below. + * See also the “[Text, Including Comments]” section below. * Document everything, including unexported top-level identifiers, to build a habit of writing documentation. @@ -150,7 +163,7 @@ The rules are mostly sorted in the alphabetical order. } ``` - ### Formatting + ### Formatting * Add an empty line before `break`, `continue`, `fallthrough`, and `return`, unless it's the only statement in that block. @@ -172,7 +185,7 @@ The rules are mostly sorted in the alphabetical order. }} ``` - ### Recommended Reading + ### Recommended Reading * . @@ -180,13 +193,24 @@ The rules are mostly sorted in the alphabetical order. * -## *Markdown* +[constant errors]: https://dave.cheney.net/2016/04/07/constant-errors +[Linus said]: https://www.kernel.org/doc/html/v4.17/process/coding-style.html#indentation +[Text, Including Comments]: #text-including-comments - * **TODO(a.garipov):** Define our *Markdown* conventions. +## Markdown -## Shell Scripting + * **TODO(a.garipov):** Define more Markdown conventions. - * Avoid bashisms and GNUisms, prefer *POSIX* features only. + * Prefer triple-backtick preformatted code blocks to indented code blocks. + + * Use asterisks and not underscores for bold and italic. + + * Use either link references or link destinations only. Put all link + reference definitions at the end of the second-level block. + +## Shell Scripting + + * Avoid bashisms and GNUisms, prefer POSIX features only. * Prefer `'raw strings'` to `"double quoted strings"` whenever possible. @@ -225,7 +249,7 @@ The rules are mostly sorted in the alphabetical order. dir="${TOP_DIR}"/sub ``` -## Text, Including Comments +## Text, Including Comments * End sentences with appropriate punctuation. @@ -246,7 +270,7 @@ The rules are mostly sorted in the alphabetical order. * Use double spacing between sentences to make sentence borders more clear. - * Use the serial comma (a.k.a. *Oxford* comma) to improve comprehension, + * Use the serial comma (a.k.a. Oxford comma) to improve comprehension, decrease ambiguity, and use a common standard. * Write todos like this: @@ -261,16 +285,16 @@ The rules are mostly sorted in the alphabetical order. // TODO(usr1, usr2): Fix the frobulation issue. ``` -## *YAML* +## YAML * **TODO(a.garipov):** Define naming conventions for schema names in our - *OpenAPI* *YAML* file. And just generally OpenAPI conventions. + OpenAPI YAML file. And just generally OpenAPI conventions. - * **TODO(a.garipov):** Find a *YAML* formatter or write our own. + * **TODO(a.garipov):** Find a YAML formatter or write our own. - * All strings, including keys, must be quoted. Reason: the [*NO-rway Law*]. + * All strings, including keys, must be quoted. Reason: the “[NO-rway Law]”. - * Indent with two (**2**) spaces. *YAML* documents can get pretty + * Indent with two (**2**) spaces. YAML documents can get pretty deeply-nested. * No extra indentation in multiline arrays: @@ -284,8 +308,8 @@ The rules are mostly sorted in the alphabetical order. * Prefer single quotes for strings to prevent accidental escaping, unless escaping is required or there are single quotes inside the string (e.g. for - *GitHub Actions*). + GitHub Actions). * Use `>` for multiline strings, unless you need to keep the line breaks. -[*NO-rway Law*]: https://news.ycombinator.com/item?id=17359376 +[NO-rway Law]: https://news.ycombinator.com/item?id=17359376 From dd64d19c3d61179186df5a9c89dc5e8d89ad93da Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Fri, 5 Feb 2021 18:55:46 +0300 Subject: [PATCH 099/141] Pull request: client: upd locales Merge in DNS/adguard-home from upd-locales to master Updates #2643. Squashed commit of the following: commit 5a1e04c252b7cf0091f4471f278de49d885bf05a Author: Ainar Garipov Date: Fri Feb 5 18:42:32 2021 +0300 client: upd locales --- .gitattributes | 5 +- client/src/__locales/be.json | 3 + client/src/__locales/de.json | 12 +-- client/src/__locales/en.json | 16 +--- client/src/__locales/es.json | 4 +- client/src/__locales/fr.json | 12 +-- client/src/__locales/hu.json | 4 +- client/src/__locales/it.json | 50 +++++----- client/src/__locales/ja.json | 12 +++ client/src/__locales/ko.json | 4 +- client/src/__locales/nl.json | 82 ++++++++-------- client/src/__locales/no.json | 2 + client/src/__locales/pt-br.json | 44 ++++----- client/src/__locales/pt-pt.json | 74 +++++++-------- client/src/__locales/ro.json | 20 ++-- client/src/__locales/ru.json | 2 +- client/src/__locales/si-lk.json | 71 ++++++++------ client/src/__locales/sk.json | 2 +- client/src/__locales/tr.json | 124 ++++++++++++++----------- client/src/__locales/vi.json | 10 ++ client/src/__locales/zh-cn.json | 2 +- client/src/__locales/zh-hk.json | 60 ++++++------ client/src/__locales/zh-tw.json | 10 +- scripts/translations/package-lock.json | 2 +- 24 files changed, 343 insertions(+), 284 deletions(-) diff --git a/.gitattributes b/.gitattributes index b2b017bd..0e3eb755 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,4 @@ -client/* linguist-vendored +client/* linguist-vendored +client/src/__locales/*.json binary +# Make an exception for the english locale, as it is the base one. +client/src/__locales/en.json -binary diff --git a/client/src/__locales/be.json b/client/src/__locales/be.json index a1a340e5..f34bd8d9 100644 --- a/client/src/__locales/be.json +++ b/client/src/__locales/be.json @@ -249,6 +249,8 @@ "blocking_ipv6": "Блакаванне IPv6", "dns_over_https": "DNS-over-HTTPS", "dns_over_tls": "DNS-over-TLS", + "download_mobileconfig_doh": "Спампаваць .mobileconfig для DNS-over-HTTPS", + "download_mobileconfig_dot": "Спампаваць .mobileconfig для DNS-over-TLS", "plain_dns": "Нешыфраваны DNS", "form_enter_rate_limit": "Увядзіце rate limit", "rate_limit": "Ограничение скорости", @@ -416,6 +418,7 @@ "setup_dns_privacy_1": "<0>DNS-over-TLS: Ужывайце радок <1>{{address}}.", "setup_dns_privacy_2": "<0>DNS-over-HTTPS: Ужывайце радок <1>{{address}}.", "setup_dns_privacy_3": "<0>Вось спіс ПА, якое вы можаце выкарыстоўваць.", + "setup_dns_privacy_4": "На прыладах з iOS 14 і macOS Big Sur вы можаце спампаваць адмысловы файл '.mobileconfig', які дадае DNS-over-HTTPS ці DNS-over-TLS серверы ў налады DNS.", "setup_dns_privacy_android_1": "Android 9 натыўна падтрымвае DNS-over-TLS. Для налады, перайдзіце ў Налады → Сеціва і Інтэрнэт → Дадаткова → Персанальны DNS сервер, і ўвядзіце туды ваша даменавае імя.", "setup_dns_privacy_android_2": "<0>AdGuard для Android падтрымвае <1>DNS-over-HTTPS і <1>DNS-over-TLS.", "setup_dns_privacy_android_3": "<0>Intra дадае падтрымка <1>DNS-over-HTTPS на Android.", diff --git a/client/src/__locales/de.json b/client/src/__locales/de.json index 35a126b2..2a63b548 100644 --- a/client/src/__locales/de.json +++ b/client/src/__locales/de.json @@ -91,16 +91,16 @@ "disabled_protection": "Schutz deaktiviert", "refresh_statics": "Statistiken aktualisieren", "dns_query": "DNS-Anfragen", - "blocked_by": "<0>Blockiert durch die Filter", - "stats_malware_phishing": "Blockierte Malware/Phishing", - "stats_adult": "Blockierte Webseiten für Erwachsene", + "blocked_by": "<0>Durch Filter gesperrt", + "stats_malware_phishing": "Gesperrte Schädliche/Phishing-Webseiten", + "stats_adult": "Gesperrte jugendgefährdende Webseiten", "stats_query_domain": "Am häufigsten angefragte Domains", "for_last_24_hours": "für die letzten 24 Stunden", "for_last_days": "am letzten {{count}} Tag", "for_last_days_plural": "in den letzten {{count}} Tage", "no_domains_found": "Keine Domains gefunden", "requests_count": "Anzahl der Anfragen", - "top_blocked_domains": "Am häufigsten blockierte Domains", + "top_blocked_domains": "Am häufigsten gesperrte Domains", "top_clients": "Top Clients", "no_clients_found": "Keine Clients gefunden", "general_statistics": "Allgemeine Statistiken", @@ -109,7 +109,7 @@ "number_of_dns_query_24_hours": "Anzahl der in den letzten 24 Stunden durchgeführten DNS-Anfragen", "number_of_dns_query_blocked_24_hours": "Anzahl der durch Werbefilter und Host-Blocklisten geblockten DNS-Anfragen", "number_of_dns_query_blocked_24_hours_by_sec": "Anzahl der durch das AdGuard-Modul „Internetsicherheit” gesperrten DNS-Anfragen", - "number_of_dns_query_blocked_24_hours_adult": "Anzahl der blockierten Webseiten für Erwachsene", + "number_of_dns_query_blocked_24_hours_adult": "Anzahl der gesperrten Webseiten mit jugendgefährdenden Inhalten", "enforced_save_search": "SafeSearch erzwungen", "number_of_dns_query_to_safe_search": "Anzahl der DNS-Anfragen bei denen SafeSearch für Suchanfragen erzwungen wurde", "average_processing_time": "Durchschnittliche Bearbeitungsdauer", @@ -498,7 +498,7 @@ "descr": "Beschreibung", "whois": "Whois", "filtering_rules_learn_more": "<0>Erfahren Sie mehr über die Erstellung eigener Hosts-Listen.", - "blocked_by_response": "Nach CNAME oder IP-Antwort blockiert", + "blocked_by_response": "Gesperrt nach Antwort von CNAME oder IP", "blocked_by_cname_or_ip": "Gesperrt durch CNAME oder IP", "try_again": "Erneut versuchen", "domain_desc": "Geben Sie den Domain-Namen oder den Platzhalter ein, der umgeschrieben werden soll.", diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json index a13f6cb4..5f37ac36 100644 --- a/client/src/__locales/en.json +++ b/client/src/__locales/en.json @@ -32,7 +32,6 @@ "form_error_ip_format": "Invalid IP format", "form_error_mac_format": "Invalid MAC format", "form_error_client_id_format": "Invalid client ID format", - "form_error_server_name": "Invalid server name", "form_error_positive": "Must be greater than 0", "form_error_negative": "Must be equal to 0 or greater", "range_end_error": "Must be greater than range start", @@ -250,13 +249,8 @@ "blocking_ipv6": "Blocking IPv6", "dns_over_https": "DNS-over-HTTPS", "dns_over_tls": "DNS-over-TLS", - "dns_over_quic": "DNS-over-QUIC", - "client_id": "Client ID", - "client_id_placeholder": "Enter client ID", - "client_id_desc": "Different clients can be identified by a special client ID. Here you can learn more about how to identify clients.", "download_mobileconfig_doh": "Download .mobileconfig for DNS-over-HTTPS", "download_mobileconfig_dot": "Download .mobileconfig for DNS-over-TLS", - "download_mobileconfig": "Download configuration file", "plain_dns": "Plain DNS", "form_enter_rate_limit": "Enter rate limit", "rate_limit": "Rate limit", @@ -275,7 +269,7 @@ "source_label": "Source", "found_in_known_domain_db": "Found in the known domains database.", "category_label": "Category", - "rule_label": "Rule(s)", + "rule_label": "Rule", "list_label": "List", "unknown_filter": "Unknown filter {{filterId}}", "known_tracker": "Known tracker", @@ -336,7 +330,7 @@ "encryption_config_saved": "Encryption config saved", "encryption_server": "Server name", "encryption_server_enter": "Enter your domain name", - "encryption_server_desc": "In order to use HTTPS, you need to enter the server name that matches your SSL certificate or wildcard certificate. If the field is not set, it will accept TLS connections for any domain.", + "encryption_server_desc": "In order to use HTTPS, you need to enter the server name that matches your SSL certificate.", "encryption_redirect": "Redirect to HTTPS automatically", "encryption_redirect_desc": "If checked, AdGuard Home will automatically redirect you from HTTP to HTTPS addresses.", "encryption_https": "HTTPS port", @@ -392,7 +386,7 @@ "client_edit": "Edit Client", "client_identifier": "Identifier", "ip_address": "IP address", - "client_identifier_desc": "Clients can be identified by the IP address, CIDR, MAC address or a special client ID (can be used for DoT/DoH/DoQ). <0>Here you can learn more about how to identify clients.", + "client_identifier_desc": "Clients can be identified by the IP address, CIDR, MAC address. Please note that using MAC as identifier is possible only if AdGuard Home is also a <0>DHCP server", "form_enter_ip": "Enter IP", "form_enter_mac": "Enter MAC", "form_enter_id": "Enter identifier", @@ -436,7 +430,6 @@ "setup_dns_privacy_other_3": "<0>dnscrypt-proxy supports <1>DNS-over-HTTPS.", "setup_dns_privacy_other_4": "<0>Mozilla Firefox supports <1>DNS-over-HTTPS.", "setup_dns_privacy_other_5": "You will find more implementations <0>here and <1>here.", - "setup_dns_privacy_ioc_mac": "iOS and macOS configuration", "setup_dns_notice": "In order to use <1>DNS-over-HTTPS or <1>DNS-over-TLS, you need to <0>configure Encryption in AdGuard Home settings.", "rewrite_added": "DNS rewrite for \"{{key}}\" successfully added", "rewrite_deleted": "DNS rewrite for \"{{key}}\" successfully deleted", @@ -536,6 +529,7 @@ "check_ip": "IP addresses: {{ip}}", "check_cname": "CNAME: {{cname}}", "check_reason": "Reason: {{reason}}", + "check_rule": "Rule: {{rule}}", "check_service": "Service name: {{service}}", "service_name": "Service name", "check_not_found": "Not found in your filter lists", @@ -593,4 +587,4 @@ "adg_will_drop_dns_queries": "AdGuard Home will be dropping all DNS queries from this client.", "client_not_in_allowed_clients": "The client is not allowed because it is not in the \"Allowed clients\" list.", "experimental": "Experimental" -} +} \ No newline at end of file diff --git a/client/src/__locales/es.json b/client/src/__locales/es.json index 9035b6d0..8b335431 100644 --- a/client/src/__locales/es.json +++ b/client/src/__locales/es.json @@ -50,7 +50,7 @@ "dhcp_table_expires": "Expira", "dhcp_warning": "Si de todos modos deseas habilitar el servidor DHCP, asegúrate de que no hay otro servidor DHCP activo en tu red. ¡De lo contrario, puedes dejar sin Internet a los dispositivos conectados!", "dhcp_error": "No pudimos determinar si hay otro servidor DHCP en la red.", - "dhcp_static_ip_error": "Para poder utilizar el servidor DHCP se debe establecer una dirección IP estática. No hemos podido determinar si esta interfaz de red está configurada utilizando una dirección IP estática. Por favor establezca una dirección IP estática manualmente.", + "dhcp_static_ip_error": "Para poder utilizar el servidor DHCP se debe establecer una dirección IP estática. No hemos podido determinar si esta interfaz de red está configurada utilizando una dirección IP estática. Por favor establece una dirección IP estática manualmente.", "dhcp_dynamic_ip_found": "Tu sistema utiliza la configuración de dirección IP dinámica para la interfaz <0>{{interfaceName}}. Para poder utilizar el servidor DHCP se debe establecer una dirección IP estática. Tu dirección IP actual es <0>{{ipAddress}}. Si presionas el botón Habilitar servidor DHCP, estableceremos automáticamente esta dirección IP como estática.", "dhcp_lease_added": "Asignación estática \"{{key}}\" añadido correctamente", "dhcp_lease_deleted": "Asignación estática \"{{key}}\" eliminado correctamente", @@ -294,7 +294,7 @@ "install_devices_title": "Configura tus dispositivos", "install_devices_desc": "Para comenzar a utilizar AdGuard Home, debes configurar tus dispositivos para usarlo.", "install_submit_title": "¡Felicitaciones!", - "install_submit_desc": "El proceso de configuración ha finalizado y está listo para comenzar a usar AdGuard Home.", + "install_submit_desc": "El proceso de configuración ha finalizado y estás listo para comenzar a usar AdGuard Home.", "install_devices_router": "Router", "install_devices_router_desc": "Esta configuración cubrirá automáticamente todos los dispositivos conectados a tu router doméstico y no necesitarás configurar cada uno de ellos manualmente.", "install_devices_address": "El servidor DNS de AdGuard Home está escuchando en las siguientes direcciones", diff --git a/client/src/__locales/fr.json b/client/src/__locales/fr.json index fa636342..589acb71 100644 --- a/client/src/__locales/fr.json +++ b/client/src/__locales/fr.json @@ -22,7 +22,7 @@ "dhcp_found": "Il y a plusieurs serveurs DHCP actifs sur le réseau. Ce n'est pas prudent d'activer le serveur DHCP intégré en ce moment.", "dhcp_leases": "Locations des serveurs DHCP", "dhcp_static_leases": "Baux statiques DHCP", - "dhcp_leases_not_found": "Aucune location des serveurs DHCP trouvée", + "dhcp_leases_not_found": "Aucun bail DHCP trouvé", "dhcp_config_saved": "La configuration du serveur DHCP est sauvegardée", "dhcp_ipv4_settings": "Paramètres IPv4 du DHCP", "dhcp_ipv6_settings": "Paramètres IPv6 du DHCP", @@ -92,7 +92,7 @@ "refresh_statics": "Renouveler les statistiques", "dns_query": "Requêtes DNS", "blocked_by": "<0>Bloqué par Filtres", - "stats_malware_phishing": "Tentative de malware/hammeçonnage bloquée", + "stats_malware_phishing": "Tentative de malware/hameçonnage bloquée", "stats_adult": "Sites à contenu adulte bloqués", "stats_query_domain": "Domaines les plus recherchés", "for_last_24_hours": "pendant les dernières 24 heures", @@ -178,13 +178,13 @@ "custom_filter_rules": "Règles de filtrage d'utilisateur", "custom_filter_rules_hint": "Saisissez la règle en une ligne. C'est possible d'utiliser les règles de blocage ou la syntaxe des fichiers hosts.", "examples_title": "Exemples", - "example_meaning_filter_block": "bloquer l'accés au domaine exemple.org et à tous ses sous-domaines", - "example_meaning_filter_whitelist": "débloquer l'accés au domaine exemple.org et à tous ses sous-domaines", + "example_meaning_filter_block": "bloque l’accès au domaine example.org et à tous ses sous-domaines", + "example_meaning_filter_whitelist": "débloque l’accès au domaine example.org et à tous ses sous-domaines", "example_meaning_host_block": "AdGuard Home va retourner l'adresse 127.0.0.1 au domaine example.org (mais pas aux sous-domaines).", "example_comment": "! Voici comment ajouter une déscription", "example_comment_meaning": "commentaire", "example_comment_hash": "# Et comme ça aussi on peut laisser des commentaires", - "example_regex_meaning": "bloquer l'accés aux domaines correspondants à l'expression régulière spécifiée", + "example_regex_meaning": "bloque l’accès aux domaines correspondants à l'expression régulière spécifiée", "example_upstream_regular": "DNS classique (au-dessus de UDP)", "example_upstream_dot": "<0>DNS-over-TLS chiffré", "example_upstream_doh": "<0>DNS-over-HTTPS chiffré", @@ -430,7 +430,7 @@ "setup_dns_privacy_other_3": "<0>dnscrypt-proxy supporte le <1>DNS-over-HTTPS.", "setup_dns_privacy_other_4": "<0>Mozilla Firefox supporte le <1>DNS-over-HTTPS.", "setup_dns_privacy_other_5": "Vous trouverez plus d'implémentations <0>ici et <1>ici.", - "setup_dns_notice": "Pour utiliser le <1>DNS-over-HTTPS ou le <1>DNS-over-TLS, vous devez <0>configurer le Cryptage dans les paramètres de AdGuard Home.", + "setup_dns_notice": "Pour utiliser le <1>DNS-over-HTTPS ou le <1>DNS-over-TLS, vous devez <0>configurer le Chiffrement dans les paramètres de AdGuard Home.", "rewrite_added": "Réécriture DNS pour \"{{key}}\" ajoutée", "rewrite_deleted": "Réécriture DNS pour \"{{key}}\" supprimée", "rewrite_add": "Ajouter une réécriture DNS", diff --git a/client/src/__locales/hu.json b/client/src/__locales/hu.json index 5899e30a..bda17f80 100644 --- a/client/src/__locales/hu.json +++ b/client/src/__locales/hu.json @@ -88,7 +88,7 @@ "enable_protection": "Védelem engedélyezése", "enabled_protection": "Védelem engedélyezve", "disable_protection": "Védelem letiltása", - "disabled_protection": "Letiltott védelem", + "disabled_protection": "Védelem letiltva", "refresh_statics": "Statisztikák frissítése", "dns_query": "DNS lekérdezés", "blocked_by": "<0>Szűrők által blokkolt", @@ -409,7 +409,7 @@ "access_disallowed_title": "Nem engedélyezett kliensek", "access_disallowed_desc": "A CIDR vagy IP címek listája. Ha konfigurálva van, az AdGuard Home eldobja a lekérdezéseket ezekről az IP-címekről.", "access_blocked_title": "Nem engedélyezett domainek", - "access_blocked_desc": "Ne keverje össze ezt a szűrőkkel. Az AdGuard Home az összes DNS kérést el fogja dobni, ami ezekkel a domainekkel kapcsolatos. Itt megadhatja a pontos domainneveket, a helyettesítő karaktereket és az urlfilter-szabályokat, pl. 'example.org', '*.example.org' or '||example.org^'.", + "access_blocked_desc": "Ne keverje össze ezt a szűrőkkel. Az AdGuard Home az összes DNS kérést el fogja dobni, ami ezekkel a domainekkel kapcsolatos. Itt megadhatja a pontos domainneveket, a helyettesítő karaktereket és az urlfilter-szabályokat, pl. 'example.org', '*.example.org' vagy '||example.org^'.", "access_settings_saved": "A hozzáférési beállítások sikeresen mentésre kerültek", "updates_checked": "A frissítések sikeresen ellenőrizve lettek", "updates_version_equal": "Az AdGuard Home naprakész", diff --git a/client/src/__locales/it.json b/client/src/__locales/it.json index 05827058..47f8aa71 100644 --- a/client/src/__locales/it.json +++ b/client/src/__locales/it.json @@ -186,10 +186,10 @@ "example_comment_hash": "# Un altro commento", "example_regex_meaning": "blocca l'accesso ai domini che corrispondono alla specifica espressione regolare", "example_upstream_regular": "DNS regolari (via UDP)", - "example_upstream_dot": "<0>DNS_over_TLS crittografato", - "example_upstream_doh": "<0>DNS-over-HTTPS crittografato", - "example_upstream_doq": "<0>DNS_over_QUIC crittografato", - "example_upstream_sdns": "puoi utilizzare <0>DNS Stamps per <1>DNSCrypt oppure dei resolver con <2>DNS-over-HTTPS", + "example_upstream_dot": "<0>DNS su TLS crittografato", + "example_upstream_doh": "<0>DNS su HTTPS crittografato", + "example_upstream_doq": "<0>DNS su QUIC crittografato", + "example_upstream_sdns": "puoi utilizzare <0>DNS Stamps per <1>DNSCrypt oppure dei resolver con <2>DNS su HTTPS", "example_upstream_tcp": "DNS regolari (via TCP)", "all_lists_up_to_date_toast": "Tutte le liste sono aggiornate", "updated_upstream_dns_toast": "Server DNS upstream aggiornati", @@ -247,10 +247,10 @@ "custom_ip": "IP personalizzato", "blocking_ipv4": "Blocca IPv4", "blocking_ipv6": "Blocca IPv6", - "dns_over_https": "DNS-over-HTTPS", - "dns_over_tls": "DNS-over-TLS", - "download_mobileconfig_doh": "Scarica .mobileconfig per DNS-over-HTTPS", - "download_mobileconfig_dot": "Scarica .mobileconfig per DNS-over-TLS", + "dns_over_https": "DNS su HTTPS", + "dns_over_tls": "DNS su TLS", + "download_mobileconfig_doh": "Scarica .mobileconfig per DNS su HTTPS", + "download_mobileconfig_dot": "Scarica .mobileconfig per DNS su TLS", "plain_dns": "DNS semplice", "form_enter_rate_limit": "Imposta limite delle richieste", "rate_limit": "Limite delle richieste", @@ -334,10 +334,10 @@ "encryption_redirect": "Reindirizza automaticamente a HTTPS", "encryption_redirect_desc": "Se selezionato, AdGuard Home ti reindirizzerà automaticamente da indirizzi HTTP a HTTPS.", "encryption_https": "Porta HTTPS", - "encryption_https_desc": "Se la porta HTTPS è configurata, l'interfaccia di amministrazione di AdGuard Home sarà accessibile tramite HTTPS e fornirà anche DNS-over-HTTPS nella posizione \"/ dns-query\".", - "encryption_dot": "DNS-su porta-TLS", - "encryption_dot_desc": "Se questa porta è configurata, AdGuard Home eseguirà un server DNS-over-TLS su questa porta.", - "encryption_doq": "DNS-su porta-QUIC", + "encryption_https_desc": "Se la porta HTTPS è configurata, l'interfaccia di amministrazione di AdGuard Home sarà accessibile tramite HTTPS e fornirà anche DNS su HTTPS nella posizione \"/ dns-query\".", + "encryption_dot": "DNS su porta TLS", + "encryption_dot_desc": "Se questa porta è configurata, AdGuard Home eseguirà un server DNS su TLS su questa porta.", + "encryption_doq": "DNS su porta QUIC", "encryption_doq_desc": "Se questa porta è configurata, AdGuard Home eseguirà un server DNS su porta QUIC. Questa opzione è sperimentale e potrebbe non risultare affidabile. Inoltre, al momento non sono molti i client a supportarla.", "encryption_certificates": "Certificati", "encryption_certificates_desc": "Per utilizzare la crittografia, è necessario fornire una catena di certificati SSL valida per il proprio dominio. Puoi ottenere un certificato gratuito su <0> {{link}} o puoi acquistarlo da una delle Autorità di certificazione attendibili.", @@ -346,8 +346,8 @@ "encryption_expire": "Scaduto", "encryption_key": "Chiave privata", "encryption_key_input": "Copia/Incolla qui la tua chiave privata codificata PEM per il tuo certificato.", - "encryption_enable": "Attiva crittografia (HTTPS, DNS-su-HTTPS e DNS-su-TLS)", - "encryption_enable_desc": "Se la crittografia è attiva, l'interfaccia di amministrazione di AdGuard Home funzionerà su HTTPS e il server DNS ascolterà le richieste su DNS-over-HTTPS e DNS-over-TLS.", + "encryption_enable": "Attiva crittografia (HTTPS, DNS su HTTPS e DNS su TLS)", + "encryption_enable_desc": "Se la crittografia è attiva, l'interfaccia di amministrazione di AdGuard Home funzionerà su HTTPS e il server DNS ascolterà le richieste su DNS su HTTPS e DNS su TLS.", "encryption_chain_valid": "La catena di certificati è valida", "encryption_chain_invalid": "La catena di certificati non è valida", "encryption_key_valid": "Questa è una chiave privata {{type}} valida", @@ -415,22 +415,22 @@ "updates_version_equal": "AdGuard Home è aggiornato", "check_updates_now": "Controlla aggiornamenti adesso", "dns_privacy": "Privacy DNS", - "setup_dns_privacy_1": "<0>DNS-over-TLS: Utilizza la stringa <1>{{address}}.", - "setup_dns_privacy_2": "<0>DNS-over-HTTPS: Utilizza la stringa <1>{{address}}.", + "setup_dns_privacy_1": "<0>DNS su TLS: Utilizza la stringa <1>{{address}}.", + "setup_dns_privacy_2": "<0>DNS su HTTPS: Utilizza la stringa <1>{{address}}.", "setup_dns_privacy_3": "<0>Ecco un elenco di software che è possibile utilizzare.", - "setup_dns_privacy_4": "Si usa un dispositivo iOS 14 o macOS Big Sur puoi scaricare uno file speciale.mobileconfig' che aggiunge i server DNS-over-HTTPS or DNS-over-TLS alle configurazioni DNS.", - "setup_dns_privacy_android_1": "Android 9 supporta DNS-over-TLS in modo nativo. Per configurarlo, vai su Impostazioni → Rete e Internet → Avanzate → DNS privato e inserisci qui il tuo nome di dominio.", - "setup_dns_privacy_android_2": "<0>AdGuard per Android supporta <1>DNS-over-HTTPS e <1>DNS-over-TLS.", - "setup_dns_privacy_android_3": "<0>Intra aggiunge <1>DNS-over-HTTPS il supporto ad Android.", - "setup_dns_privacy_ios_1": "<0>DNSCloak supporta <1>DNS-over-HTTPS, ma per configurarlo per l'utilizzo del proprio server, è necessario generare un <2> DNS Stamp apposito.", - "setup_dns_privacy_ios_2": "<0>AdGuard per iOSsupporta l'impostazione <1>DNS-over-HTTPS e <1>DNS-over-TLS.", + "setup_dns_privacy_4": "Si usa un dispositivo iOS 14 o macOS Big Sur puoi scaricare uno file speciale.mobileconfig' che aggiunge i server DNS su HTTPS or DNS su TLS alle configurazioni DNS.", + "setup_dns_privacy_android_1": "Android 9 supporta DNS su TLS in modo nativo. Per configurarlo, vai su Impostazioni → Rete e Internet → Avanzate → DNS privato e inserisci qui il tuo nome di dominio.", + "setup_dns_privacy_android_2": "<0>AdGuard per Android supporta <1>DNS su HTTPS e <1>DNS su TLS.", + "setup_dns_privacy_android_3": "<0>Intra aggiunge <1>DNS su HTTPS il supporto ad Android.", + "setup_dns_privacy_ios_1": "<0>DNSCloak supporta <1>DNS su HTTPS, ma per configurarlo per l'utilizzo del proprio server, è necessario generare un <2> DNS Stamp apposito.", + "setup_dns_privacy_ios_2": "<0>AdGuard per iOSsupporta l'impostazione <1>DNS su HTTPS e <1>DNS su TLS.", "setup_dns_privacy_other_title": "Altre implementazion", "setup_dns_privacy_other_1": "AdGuard Home può essere un client DNS sicuro su qualsiasi piattaforma.", "setup_dns_privacy_other_2": "<0>dnsproxy supporta tutti i protocolli DNS sicuri noti.", - "setup_dns_privacy_other_3": "<0>dnscrypt-proxy supporta <1>DNS-over-HTTPS.", - "setup_dns_privacy_other_4": "<0>Mozilla Firefox supporta <1>DNS-over-HTTPS.", + "setup_dns_privacy_other_3": "<0>dnscrypt-proxy supporta <1>DNS su HTTPS.", + "setup_dns_privacy_other_4": "<0>Mozilla Firefox supporta <1>DNS su HTTPS.", "setup_dns_privacy_other_5": "Troverai più implementazioni <0>qui e <1>qui.", - "setup_dns_notice": "Per utilizzare <1>DNS-over-HTTPS o <1>DNS-over-TLS, è necessario <0>configurare la crittografia nelle impostazioni di AdGuard Home.", + "setup_dns_notice": "Per utilizzare <1>DNS su HTTPS o <1>DNS su TLS, è necessario <0>configurare la crittografia nelle impostazioni di AdGuard Home.", "rewrite_added": "Riscrittura DNS per \"{{key}}\" aggiunta correttamente", "rewrite_deleted": "La riscrittura DNS per \"{{key}}\" è stata eliminata correttamente", "rewrite_add": "Aggiungi la riscrittura DNS", diff --git a/client/src/__locales/ja.json b/client/src/__locales/ja.json index 0d83676f..c40264c8 100644 --- a/client/src/__locales/ja.json +++ b/client/src/__locales/ja.json @@ -1,6 +1,7 @@ { "client_settings": "クライアント設定", "example_upstream_reserved": "<0>特定のドメインに対してDNSアップストリームを指定できます", + "example_upstream_comment": "コメントを指定できます", "upstream_parallel": "並列リクエストを使用する(すべてのアップストリームサーバーを同時に照会することで解決スピードが向上します)", "parallel_requests": "並列リクエスト", "load_balancing": "ロードバランシング", @@ -248,6 +249,8 @@ "blocking_ipv6": "ブロック中のIPv6", "dns_over_https": "DNS-over-HTTPS", "dns_over_tls": "DNS-over-TLS", + "download_mobileconfig_doh": "DNS-over-HTTPS用の .mobileconfig をダウンロード", + "download_mobileconfig_dot": "DNS-over-TLS用の .mobileconfig をダウンロード", "plain_dns": "通常のDNS", "form_enter_rate_limit": "頻度制限を入力してください", "rate_limit": "頻度制限", @@ -414,6 +417,7 @@ "dns_privacy": "DNSプライバシー", "setup_dns_privacy_1": "<0>DNS-over-TLS: <1>{{address}}という文字列を使用してください。", "setup_dns_privacy_2": "<0>DNS-over-HTTPS: <1>{{address}}という文字列を使用してください。", + "setup_dns_privacy_3": "<0>使用できるソフトウェアのリストは次の通りです。", "setup_dns_privacy_4": "iOS 14 または macOS Big Sur デバイスにて、DNS-over-HTTPSまたはDNS-over-TLSサーバをDNS設定へ追加する特別な「.mobileconfig」ファイルをダウンロードできます。", "setup_dns_privacy_android_1": "Android 9はDNS-over-TLSをネイティブにサポートします。設定するには、設定 → ネットワークとインターネット → 詳細設定 → プライベートDNS へ遷移し、そこにドメイン名を入力してください。", "setup_dns_privacy_android_2": "<0>AdGuard for Androidは、<1>DNS-over-HTTPSと<1>DNS-over-TLSをサポートしています。", @@ -527,6 +531,7 @@ "check_reason": "理由: {{reason}}", "check_rule": "ルール: {{rule}}", "check_service": "サービス名: {{service}}", + "service_name": "サービス名", "check_not_found": "フィルタ一覧には見つかりません", "client_confirm_block": "クライアント\"{{ip}}\"をブロックしてもよろしいですか?", "client_confirm_unblock": "クライアント\"{{ip}}\"のブロックを解除してもよろしいですか?", @@ -561,6 +566,11 @@ "cache_size_desc": "DNSキャッシュサイズ(バイト単位)", "cache_ttl_min_override": "最小TTLの上書き(秒単位)", "cache_ttl_max_override": "最大TTLの上書き(秒単位)", + "enter_cache_size": "キャッシュサイズ(バイト単位)を入力してください", + "enter_cache_ttl_min_override": "最小TTL(秒単位)を入力してください", + "enter_cache_ttl_max_override": "最大TTL(秒単位)を入力してください", + "cache_ttl_min_override_desc": "DNS応答をキャッシュするとき、上流サーバから受信した短いTTL(秒単位)を延長します", + "cache_ttl_max_override_desc": "DNSキャッシュ内のエントリの最大TTL(秒単位)を設定します", "ttl_cache_validation": "最小キャッシュTTL値は最大値以下にする必要があります", "filter_category_general": "一般", "filter_category_security": "セキュリティ", @@ -570,9 +580,11 @@ "filter_category_security_desc": "マルウェア、フィッシング、詐欺ドメインのブロック専用リストです。", "filter_category_regional_desc": "それぞれの地域の広告と追跡サーバをターゲットするリストです。", "filter_category_other_desc": "その他のブロックリストです。", + "setup_config_to_enable_dhcp_server": "DHCPサーバを有効にするには構成を設定してください", "original_response": "当初の応答", "click_to_view_queries": "クエリを表示するにはクリックしてください", "port_53_faq_link": "多くの場合、ポート53は \"DNSStubListener\" または \"systemd-resolved\" サービスによって利用されています。これを解決する方法については、<0>この手順をお読みください。", "adg_will_drop_dns_queries": "AdGuard Homeは、このクライアントからすべてのDNSクエリを落とします。", + "client_not_in_allowed_clients": "「許可されたクライアント」リストにないため、このクライアントは許可されていません。", "experimental": "実験用" } \ No newline at end of file diff --git a/client/src/__locales/ko.json b/client/src/__locales/ko.json index 16c37a6a..eb80de35 100644 --- a/client/src/__locales/ko.json +++ b/client/src/__locales/ko.json @@ -301,7 +301,7 @@ "install_devices_router_list_1": "라우터의 환경 설정을 여세요. 환경 설정은 다음의 주소(http://192.168.0.1/ 혹은 http://192.168.1.1/)를 통해 브라우저로 접근 가능합니다. 비밀번호를 입력해야할 수 있습니다. 비밀번호를 잊었다면 대개 라우터 기기에 있는 버튼을 눌러 비밀번호를 초기화할 수 있습니다. 어떤 라우터들은 당신의 컴퓨터/핸드폰에 설치할 수 있는 특정 어플리케이션을 필요로합니다.", "install_devices_router_list_2": "각각 1~3자리 숫자의 네 그룹으로 분할된 두 세트의 숫자를 허용하는 필드 옆에 있는 DNS 문자를 찾으세요.", "install_devices_router_list_3": "AdGuard Home 서버 주소를 입력하세요", - "install_devices_router_list_4": "일부 라우터는 DNS서버의 커스텀 설정이 불가합니다. 간혹 AdGuard Home을 DHCP서버로 이용하여 문제를 해결하는 경우가 있지만 문제가 지속될 경우 사용하시는 라우터 모델의 매뉴얼을 참고하시어 <0>DNS서버 커스텀 설정 방법을 직접 살펴보셔야 합니다.", + "install_devices_router_list_4": "일부 라우터 유형에서는 사용자 정의 DNS 서버를 설정할 수 없습니다. 이 경우에는 AdGuard Home을 <0>DHCP 서버로 설정할 수 있습니다. 그렇지 않으면 특정 라우터 모델에 맞게 DNS 서버를 설정하는 방법을 찾아야 합니다.", "install_devices_windows_list_1": "시작 메뉴 또는 윈도우 검색을 통해 제어판을 여세요", "install_devices_windows_list_2": "네트워크 및 인터넷 카테고리로 이동한 다음 네트워크 및 공유 센터로 이동하세요.", "install_devices_windows_list_3": "화면 왼쪽에서 어댑터 설정 변경을 찾아 클릭하세요.", @@ -370,7 +370,7 @@ "dns_status_error": "DNS 서버 상태를 가져오는 도중 오류가 발생했습니다", "down": "다운로드", "fix": "수정", - "dns_providers": "여기에 선택가능한 DNS 목록 이 있습니다.", + "dns_providers": "다음은 선택할 수 있는 <0>알려진 DNS 공급자 목록입니다.", "update_now": "지금 업데이트", "update_failed": "자동 업데이트 실패 되었습니다. 단계를 따라 수동으로 업데이트하세요", "processing_update": "잠시만 기다려주세요, AdGuard Home가 업데이트 중입니다.", diff --git a/client/src/__locales/nl.json b/client/src/__locales/nl.json index 224d1b3e..14f3fc58 100644 --- a/client/src/__locales/nl.json +++ b/client/src/__locales/nl.json @@ -6,7 +6,7 @@ "parallel_requests": "Parallelle verzoeken", "load_balancing": "Volume balanceren", "load_balancing_desc": "Eén server per keer bevragen. AdGuard Home gebruikt hiervoor een gewogen willekeurig algoritme om de server te kiezen zodat de snelste server meer zal gebruikt worden.", - "bootstrap_dns": "Bootstrap DNS servers", + "bootstrap_dns": "Bootstrap DNS-servers", "bootstrap_dns_desc": "Bootstrap DNS-servers worden gebruikt om IP-adressen op te lossen van de DoH / DoT-resolvers die u opgeeft als upstreams.", "check_dhcp_servers": "Zoek achter DHCP servers", "save_config": "Configuratie opslaan", @@ -23,7 +23,7 @@ "dhcp_leases": "DHCP lease overzicht", "dhcp_static_leases": "DHCP statische lease", "dhcp_leases_not_found": "Geen DHCP lease gevonden", - "dhcp_config_saved": "DHCP server configuratie opgeslagen", + "dhcp_config_saved": "DHCP configuratie succesvol opgeslagen", "dhcp_ipv4_settings": "DHCP IPv4 instellingen", "dhcp_ipv6_settings": "DHCP IPv6 instellingen", "form_error_required": "Vereist veld", @@ -132,8 +132,8 @@ "custom_filtering_rules": "Aangepaste filter regels", "encryption_settings": "Encryptie Instellingen", "dhcp_settings": "DHCP Instellingen", - "upstream_dns": "Upstream DNS servers", - "upstream_dns_help": "Server adressen invoeren, een per regel. Meer weten over het configureren van upstream DNS servers.", + "upstream_dns": "Upstream DNS-servers", + "upstream_dns_help": "Server adressen invoeren, een per regel. Meer weten over het configureren van upstream DNS-servers.", "upstream_dns_configured_in_file": "Geconfigureerd in {{path}}", "test_upstream_btn": "Test upstream", "upstreams": "Upstreams", @@ -186,15 +186,15 @@ "example_comment_hash": "# Nog een opmerking", "example_regex_meaning": "blokkeer de toegang tot de domeinen die overeenkomen met de opgegeven reguliere expressie", "example_upstream_regular": "standaard DNS (over UDP)", - "example_upstream_dot": "versleutelde <0>DNS_over_TLS", - "example_upstream_doh": "versleutelde <0>DNS_over_HTTPS", + "example_upstream_dot": "versleutelde <0>DNS-via-TLS", + "example_upstream_doh": "versleutelde <0>DNS-via-HTTPS", "example_upstream_doq": "versleutelde <0>DNS-via-QUIC", - "example_upstream_sdns": "je kunt <0>DNS Stamps voor <1>DNSCrypt of <2>DNS-over-HTTPS resolvers", + "example_upstream_sdns": "je kunt <0>DNS Stamps voor <1>DNSCrypt of <2>DNS-via-HTTPS oplossingen gebruiken", "example_upstream_tcp": "standaard DNS (over TCP)", "all_lists_up_to_date_toast": "Alle lijsten zijn reeds up-to-date", "updated_upstream_dns_toast": "De upstream DNS-servers zijn bijgewerkt", "dns_test_ok_toast": "Opgegeven DNS-servers werken correct", - "dns_test_not_ok_toast": "Server \"{{key}}\": kon niet worden gebruikt, controleer of u het correct hebt geschreven", + "dns_test_not_ok_toast": "Server \"{{key}}\": kon niet worden gebruikt, controleer of je het correct hebt geschreven", "unblock": "Deblokkeren", "block": "Blokkeren", "disallow_this_client": "Toepassing/systeem niet toelaten", @@ -212,7 +212,7 @@ "empty_response_status": "Leeg", "show_all_filter_type": "Toon alles", "show_filtered_type": "Toon gefilterde", - "no_logs_found": "Geen log bestanden gevonden", + "no_logs_found": "Geen logboeken gevonden", "refresh_btn": "Verversen", "previous_btn": "Vorige", "next_btn": "Volgende", @@ -235,8 +235,8 @@ "query_log_strict_search": "Gebruik dubbele aanhalingstekens voor strikt zoeken", "query_log_retention_confirm": "Weet u zeker dat u de bewaartermijn van het query logboek wilt wijzigen? Als u de intervalwaarde verlaagt, gaan sommige gegevens verloren", "anonymize_client_ip": "Cliënt IP anonimiseren", - "anonymize_client_ip_desc": "Het volledige IP-adres van de cliënt niet opnemen in log- en statistiekbestanden", - "dns_config": "DNS server configuratie", + "anonymize_client_ip_desc": "Het volledige IP-adres van de cliënt niet opnemen in logboeken en statistiekbestanden", + "dns_config": "DNS-server configuratie", "dns_cache_config": "DNS cache configuratie", "dns_cache_config_desc": "Hier kan de DNS cache geconfigureerd worden", "blocking_mode": "Blocking modus", @@ -274,14 +274,14 @@ "unknown_filter": "Onbekend filter {{filterId}}", "known_tracker": "Bekende volger", "install_welcome_title": "Welkom bij AdGuard Home!", - "install_welcome_desc": "AdGuard Home is een netwerk DNS server die advertenties en trackers blokkeert. Het doel is om jou controle te geven over je gehele netwerk en al je apparaten, en er hoeft geen client-side programma te worden gebruikt.", + "install_welcome_desc": "AdGuard Home is een netwerk DNS-server die advertenties en trackers blokkeert. Het doel is om jou controle te geven over je gehele netwerk en al je apparaten, en er hoeft geen client-side programma te worden gebruikt.", "install_settings_title": "Admin webinterface", "install_settings_listen": "Luister interface", "install_settings_port": "Poort", "install_settings_interface_link": "De webinterface van AdGuard Home admin is beschikbaar op de volgende adressen:", "form_error_port": "Voer geldige poortwaarde in", - "install_settings_dns": "DNS server", - "install_settings_dns_desc": "U moet uw apparaten of router configureren om de DNS-server te gebruiken op de volgende adressen:", + "install_settings_dns": "DNS-server", + "install_settings_dns_desc": "Je moet jouw apparaten of router configureren om de DNS-server te gebruiken op de volgende adressen:", "install_settings_all_interfaces": "Alle interfaces", "install_auth_title": "Authenticatie", "install_auth_desc": "Het wordt ten zeerste aanbevolen om wachtwoordverificatie te configureren voor de AdGuard Home admin webinterface. Zelfs als het alleen toegankelijk is in uw lokale netwerk, is het nog steeds belangrijk om het te beschermen tegen onbeperkte toegang.", @@ -301,26 +301,26 @@ "install_devices_router_list_1": "Open de instellingen pagina voor uw router. Meestal kunt u deze vanuit uw browser openen via een URL (zoals http://192.168.0.1/ of http://192.168.1.1/). Mogelijk wordt u gevraagd om het wachtwoord in te voeren. Als u het niet meer weet, kunt u het wachtwoord vaak opnieuw instellen door op een knop op de router zelf te drukken. Voor sommige routers is een specifieke toepassing vereist, die in dat geval al op uw computer / telefoon moet zijn geïnstalleerd.", "install_devices_router_list_2": "Zoek de DHCP/DNS-instellingen. Zoek naar de DNS-letters naast een veld dat twee of drie reeksen nummers toestaat, elk verdeeld in vier groepen van één tot drie cijfers.", "install_devices_router_list_3": "Voer je AdGuard Home server adressen daar in.", - "install_devices_router_list_4": "Je kan de DNS server niet aanpassen op sommige routers. In dat geval kan het een oplossing zijn om AdGuard Home te definiëren als een <0>DHCP server. Je kan ook in de handleiding van je router kijken hoe je een DNS server aanpast.", + "install_devices_router_list_4": "Je kan de DNS-server niet aanpassen op sommige routers. In dat geval kan het een oplossing zijn om AdGuard Home te definiëren als een <0>DHCP-server. Je kan ook in de handleiding van je router kijken hoe je een DNS-server aanpast.", "install_devices_windows_list_1": "Open het Configuratiescherm via het menu Start of Windows zoeken.", "install_devices_windows_list_2": "Ga naar de categorie Netwerk en Internet en vervolgens naar Netwerkcentrum.", "install_devices_windows_list_3": "Zoek aan de linkerkant van het scherm Adapter-instellingen wijzigen en klik erop.", "install_devices_windows_list_4": "Selecteer jouw actieve verbinding, klik er met de rechtermuisknop op en kies Eigenschappen.", "install_devices_windows_list_5": "Zoek Internet Protocol versie 4 (TCP / IP) in de lijst, selecteer het en klik vervolgens opnieuw op Eigenschappen.", - "install_devices_windows_list_6": "Kies Gebruik de volgende DNS-serveradressen en voer uw AdGuard Home-serveradressen in.", + "install_devices_windows_list_6": "Kies Gebruik de volgende DNS-server adressen en voer jouw AdGuard Home server adressen in.", "install_devices_macos_list_1": "Klik op het Apple-pictogram en ga naar Systeemvoorkeuren.", "install_devices_macos_list_2": "Klik op Netwerk.", "install_devices_macos_list_3": "Selecteer de eerste verbinding in jouw lijst en klik op Geavanceerd.", - "install_devices_macos_list_4": "Selecteer het tabblad DNS en voer uw AdGuard Home-serveradressen in.", + "install_devices_macos_list_4": "Selecteer het tabblad DNS en voer jouw AdGuard Home server adressen in.", "install_devices_android_list_1": "Tik op het startscherm van het Android-menu op Instellingen.", "install_devices_android_list_2": "Tik op wifi in het menu. Het scherm met alle beschikbare netwerken wordt getoond (het is niet mogelijk om een aangepaste DNS in te stellen voor een mobiele verbinding).", "install_devices_android_list_3": "Druk lang op het netwerk waarmee je bent verbonden en tik op Netwerk instellingen aanpassen.", "install_devices_android_list_4": "Op sommige apparaten moet u het vakje aanvinken voor Geavanceerd om verdere instellingen te bekijken. Om uw Android DNS-instellingen aan te passen, moet u de IP-instellingen wijzigen van DHCP in Statisch.", - "install_devices_android_list_5": "Wijzig de DNS 1-waarden en DNS 2-waarden in uw AdGuard Home-serveradressen.", + "install_devices_android_list_5": "Wijzig de DNS 1-waarden en DNS 2-waarden in jouw AdGuard Home server adressen.", "install_devices_ios_list_1": "Tik op het startscherm op Instellingen.", "install_devices_ios_list_2": "Kies Wi-Fi in het linkermenu (DNS kan niet worden geconfigureerd voor mobiele netwerken).", "install_devices_ios_list_3": "Tik op de naam van het momenteel actieve netwerk.", - "install_devices_ios_list_4": "Voer in het DNS-veld uw AdGuard Home-serveradressen in.", + "install_devices_ios_list_4": "Voer in het DNS veld jouw AdGuard Home server adressen in.", "get_started": "Beginnen", "next": "Volgende", "open_dashboard": "Open Dashboard", @@ -334,9 +334,9 @@ "encryption_redirect": "Herleid automatisch naar HTTPS", "encryption_redirect_desc": "Indien ingeschakeld, zal AdGuard Home je automatisch herleiden van HTTP naar HTTPS.", "encryption_https": "HTTPS poort", - "encryption_https_desc": "Als de HTTPS-poort is geconfigureerd, is de AdGuard Home beheerders interface toegankelijk via HTTPS en biedt deze ook DNS-over-HTTPS op de locatie '/ dns-query'.", - "encryption_dot": "DNS-over-TLS poort", - "encryption_dot_desc": "Indien deze poort is geconfigureerd, zal AdGuard Home gebruik maken van een DNS-over-TLS server via deze poort.", + "encryption_https_desc": "Als de HTTPS-poort is geconfigureerd, is de AdGuard Home beheerders interface toegankelijk via HTTPS en biedt deze ook DNS-via-HTTPS op de locatie '/ dns-query'.", + "encryption_dot": "DNS-via-TLS poort", + "encryption_dot_desc": "Indien deze poort is geconfigureerd, zal AdGuard Home gebruik maken van een DNS-via-TLS server via deze poort.", "encryption_doq": "DNS-via-QUIC poort", "encryption_doq_desc": "Als deze poort is geconfigureerd, zal AdGuard Home een DNS-via-QUIC server gebruiken via deze poort. Dit is experimenteel en kan onbetrouwbaar zijn. Er zijn overigens nog niet veel systemen die dit nu al ondersteunen.", "encryption_certificates": "Certificaten", @@ -346,8 +346,8 @@ "encryption_expire": "Verloopt", "encryption_key": "Prive sleutel", "encryption_key_input": "Kopieër en plak je PEM-gecodeerde prive sleutel voor je certificaat hier.", - "encryption_enable": "Activeer encryptie (HTTPS, DNS-over-HTTPS, en DNS-over-TLS)", - "encryption_enable_desc": "Als encryptie is geactiveerd, is de AdGuard Home beheerders interface toegankelijk via HTTPS en de DNS-server zal luisteren naar aanvragen via DNS-over-HTTPS en DNS-over-TLS.", + "encryption_enable": "Activeer encryptie (HTTPS, DNS-via-HTTPS, en DNS-via-TLS)", + "encryption_enable_desc": "Als encryptie is geactiveerd, is de AdGuard Home beheerders interface toegankelijk via HTTPS en de DNS-server zal luisteren naar aanvragen via DNS-via-HTTPS en DNS-via-TLS.", "encryption_chain_valid": "certificaatketen is geldig", "encryption_chain_invalid": "certificaatketen is ongeldig", "encryption_key_valid": "Dit is een geldig {{type}} privé sleutel", @@ -366,8 +366,8 @@ "update_announcement": "AdGuard Home{{version}} is nu beschikbaar! <0>klik hier voor meer info.", "setup_guide": "Installatie gids", "dns_addresses": "DNS adressen", - "dns_start": "DNS server aan het opstarten", - "dns_status_error": "Fout bij het oproepen van de DNS server status", + "dns_start": "DNS-server aan het opstarten", + "dns_status_error": "Fout bij het oproepen van de DNS-server status", "down": "Uitgeschakeld", "fix": "Los op", "dns_providers": "hier is een <0>lijst of gekende DNS providers waarvan je kan kiezen.", @@ -380,7 +380,7 @@ "settings_custom": "Aangepast", "table_client": "Gebruiker", "table_name": "Naam", - "save_btn": "Bewaar", + "save_btn": "Opslaan", "client_add": "Voeg gebruiker toe", "client_new": "Nieuwe gebruiker", "client_edit": "Wijzig gebruiker", @@ -403,34 +403,34 @@ "auto_clients_title": "Gebruikers (runtime)", "auto_clients_desc": "Data over gebruikers die AdGuard Home gebruiken, maar niet geconfigureerd zijn", "access_title": "Toegangs instellingen", - "access_desc": "Hier kan je toegangsregels voor de AdGuard Home DNS server instellen.", + "access_desc": "Hier kan je toegangsregels voor de AdGuard Home DNS-server instellen.", "access_allowed_title": "Toegestane gebruikers", "access_allowed_desc": "Een lijst van CIDR of IP adressen. Indien ingesteld, zal AdGuard Home alleen van deze IP adressen aanvragen accepteren.", "access_disallowed_title": "Verworpen gebruikers", "access_disallowed_desc": "Een lijst van CIDR of IP adressen. Indien ingesteld, zal AdGuard Home aanvragen van deze IP adressen verwerpen.", "access_blocked_title": "Niet toegelaten domeinen", "access_blocked_desc": "Verwar dit niet met filters. AdGuard Home zal deze DNS-zoekopdrachten niet uitvoeren die deze domeinen in de zoekopdracht bevatten. Hier kan je de domeinnamen, wildcards en url-filter-regels specifiëren, bijv. 'example.org', '*.example.org' or '||example.org^'.", - "access_settings_saved": "Toegangsinstellingen met succes opgeslagen", + "access_settings_saved": "Toegangsinstellingen succesvol opgeslagen", "updates_checked": "Met succes op updates gecontroleerd", "updates_version_equal": "AdGuard Home is up-to-date", "check_updates_now": "Controleer op updates", "dns_privacy": "DNS Privacy", - "setup_dns_privacy_1": "<0>DNS-over-TLS: Gebruik <1>{{address}} string.", - "setup_dns_privacy_2": "<0>DNS-over-HTTPS: Gebruik <1>{{address}} string.", + "setup_dns_privacy_1": "<0>DNS-via-TLS: Gebruik <1>{{address}} string.", + "setup_dns_privacy_2": "<0>DNS-via-HTTPS: Gebruik <1>{{address}} string.", "setup_dns_privacy_3": "<0>Hou er rekening mee dat het beveiligde DNS protocol alleen beschikbaar is voor Android 9. U moet dus extra software installeren voor andere besturingssystemen.<0>Hier is een lijst van te gebruiken software.", "setup_dns_privacy_4": "Op een iOS 14 of macOS Big Sur apparaat kan je een speciaal '.mobileconfig'-bestand downloaden dat DNS-via-HTTPS of DNS-via-TLS servers aan de DNS-instellingen toevoegt.", - "setup_dns_privacy_android_1": "Android 9 ondersteunt native DNS-over-TLS. Om het te configureren, ga naar Instellingen → Netwerk & internet → Geavanceerd → Privé DNS en voer daar uw domeinnaam in.", - "setup_dns_privacy_android_2": "<0>AdGuard voor Androidondersteunt<1>DNS-over-HTTPS en<1>DNS-over-TLS.", - "setup_dns_privacy_android_3": "<0> Intra voegt <1> DNS-over-HTTPS ondersteuning toe aan Android.", - "setup_dns_privacy_ios_1": "<0>DNSCloak ondersteunt <1> DNS-over-HTTPS , maar om het te configureren om op uw eigen server te gebruiken moet er een <2> DNS-stempel gegenereerd worden.", - "setup_dns_privacy_ios_2": "<0> AdGuard voor iOS ondersteunt de instellingen <1> DNS-over-HTTPS en <1> DNS-over-TLS .", + "setup_dns_privacy_android_1": "Android 9 ondersteunt native DNS-via-TLS. Om het te configureren, ga naar Instellingen → Netwerk & internet → Geavanceerd → Privé DNS en voer daar je domeinnaam in.", + "setup_dns_privacy_android_2": "<0>AdGuard voor Androidondersteunt<1>DNS-via-HTTPS en<1>DNS-via-TLS.", + "setup_dns_privacy_android_3": "<0> Intra voegt <1> DNS-via-HTTPS ondersteuning toe aan Android.", + "setup_dns_privacy_ios_1": "<0>DNSCloak ondersteunt <1> DNS-via-HTTPS , maar om het te configureren op jouw eigen server moet er een <2> DNS-stempel gegenereerd worden.", + "setup_dns_privacy_ios_2": "<0> AdGuard voor iOS ondersteunt de instellingen <1> DNS-via-HTTPS en <1> DNS-via-TLS .", "setup_dns_privacy_other_title": "Overig gebruik", "setup_dns_privacy_other_1": "AdGuard Home kan op elk platform een ​​veilige DNS-client zijn.", "setup_dns_privacy_other_2": "<0>dnsproxy ondersteunt alle bekende beveiligde DNS-protocollen.", - "setup_dns_privacy_other_3": "<0> dnscrypt-proxy ondersteunt <1> DNS-over-HTTPS .", - "setup_dns_privacy_other_4": "<0> Mozilla Firefox ondersteunt <1> DNS-over-HTTPS .", + "setup_dns_privacy_other_3": "<0>dnscrypt-proxy ondersteunt <1>DNS-via-HTTPS.", + "setup_dns_privacy_other_4": "<0>Mozilla Firefox ondersteunt <1>DNS-via-HTTPS.", "setup_dns_privacy_other_5": "U vindt meer implementaties <0> hier en <1> hier .", - "setup_dns_notice": "Om <1> DNS-over-HTTPS of <1> DNS-over-TLS te gebruiken, moet u <0> Codering configureren in de AdGuard Home-instellingen.", + "setup_dns_notice": "Om <1>DNS-via-HTTPS of <1>DNS-via-TLS te gebruiken, moet je <0>Versleuteling configureren in de AdGuard Home instellingen.", "rewrite_added": "DNS-herschrijving voor \"{{key}}\" met succes toegevoegd", "rewrite_deleted": "DNS-herschrijving voor \"{{key}}\" met succes verwijderd", "rewrite_add": "DNS-herschrijving toevoegen", @@ -511,8 +511,8 @@ "disable_ipv6": "Zet IPv6 uit", "disable_ipv6_desc": "Als deze functie is ingeschakeld, worden alle DNS-query's voor IPv6-adressen (type AAAA) verwijderd.", "fastest_addr": "Snelste IP adres", - "fastest_addr_desc": "Alle DNS servers bevragen en het snelste IP adres terugkoppelen. Dit zal de DNS verzoeken vertragen omdat we moeten wachten op de antwoorden van alles DNS servers, maar verbetert wel de connectiviteit.", - "autofix_warning_text": "Als je op \"Repareren\" klikt, configureert AdGuard Home uw systeem om de AdGuard Home DNS-server te gebruiken.", + "fastest_addr_desc": "Alle DNS-servers bevragen en het snelste IP adres terugkoppelen. Dit zal de DNS verzoeken vertragen omdat we moeten wachten op de antwoorden van alles DNS-servers, maar verbetert wel de connectiviteit.", + "autofix_warning_text": "Als je op \"Repareren\" klikt, configureert AdGuard Home jouw systeem om de AdGuard Home DNS-server te gebruiken.", "autofix_warning_list": "De volgende taken worden uitgevoerd: <0> Deactiveren van Systeem DNSStubListener <0> DNS-serveradres instellen op 127.0.0.1 <0> Symbolisch koppelingsdoel van /etc/resolv.conf vervangen door /run/systemd/resolve/resolv.conf <0> Stop DNSStubListener (herlaad systemd-resolved service) ", "autofix_warning_result": "Als gevolg hiervan worden alle DNS-verzoeken van je systeem standaard door AdGuard Home verwerkt.", "tags_title": "Labels", diff --git a/client/src/__locales/no.json b/client/src/__locales/no.json index 6f38a145..c876eb69 100644 --- a/client/src/__locales/no.json +++ b/client/src/__locales/no.json @@ -259,6 +259,7 @@ "rate_limit_desc": "Antallet forespørsler per sekund som én enkelt klient har lov til å be om (0: ubegrenset)", "blocking_ipv4_desc": "IP-adressen som det skal svares med for blokkerte A-forespørsler", "blocking_ipv6_desc": "IP-adressen som det skal svares med for blokkerte AAAA-forespørsler", + "blocking_mode_default": "Standard: Svar med null-IP-adresse (0.0.0.0 for A; :: for AAAA) når den blokkeres av adblock-aktige oppføringer; svar med IP-adressen som er spesifisert i oppføringen når den blokkeres av /etc/hosts-typeoppføringer", "blocking_mode_refused": "REFUSED: Svar med REFUSED-koden", "blocking_mode_nxdomain": "NXDOMAIN: Svar med NXDOMAIN-koden", "blocking_mode_null_ip": "Null IP: Svar med en 0-IP-adresse (0.0.0.0 for A; :: for AAAA)", @@ -416,6 +417,7 @@ "dns_privacy": "DNS-privatliv", "setup_dns_privacy_1": "<0>DNS-over-TLS: Benytt <1>{{address}}-strengen.", "setup_dns_privacy_2": "<0>DNS-over-HTTPS: Benytt <1>{{address}}-strengen.", + "setup_dns_privacy_3": "<0>Her er en liste over programvarer du kan bruke.", "setup_dns_privacy_android_1": "Android 9 har innebygd støtte for DNS-over-TLS. For å sette det opp, gå til Innstillinger → Nettverk og internett → Avansert → Privat DNS, og skriv inn domenenavnet ditt der.", "setup_dns_privacy_android_2": "<0>AdGuard for Android støtter <1>DNS-over-HTTPS og <1>DNS-over-TLS.", "setup_dns_privacy_android_3": "<0>Intra legger til <1>DNS-over-HTTPS-støtte i Android.", diff --git a/client/src/__locales/pt-br.json b/client/src/__locales/pt-br.json index e6932800..0452b5fd 100644 --- a/client/src/__locales/pt-br.json +++ b/client/src/__locales/pt-br.json @@ -1,6 +1,6 @@ { "client_settings": "Configurações do cliente", - "example_upstream_reserved": "Você pode especificar o DNS upstream <0>para o domínio(s) especifico", + "example_upstream_reserved": "Você pode especificar o DNS primário <0>para o domínio(s) especifico", "example_upstream_comment": "Você pode especificar o comentário", "upstream_parallel": "Usar consultas paralelas para acelerar a resolução consultando simultaneamente todos os servidores upstream", "parallel_requests": "Solicitações paralelas", @@ -125,18 +125,18 @@ "no_servers_specified": "Nenhum servidor especificado", "general_settings": "Configurações gerais", "dns_settings": "Configurações de DNS", - "dns_blocklists": "Listas negra de DNS", - "dns_allowlists": "Listas branca de DNS", - "dns_blocklists_desc": "O AdGuard Home bloqueará domínios que correspondam às listas negras.", - "dns_allowlists_desc": "Os domínios das listas branca de DNS serão permitidos mesmo que estejam em qualquer uma das listas negra.", + "dns_blocklists": "Listas de bloqueio de DNS", + "dns_allowlists": "Listas de permissões de DNS", + "dns_blocklists_desc": "O AdGuard Home bloqueará domínios que correspondam às listas de bloqueio.", + "dns_allowlists_desc": "Os domínios das listas de permissões de DNS serão permitidos mesmo que estejam em qualquer uma das listas de bloqueio.", "custom_filtering_rules": "Regras de filtragem personalizadas", "encryption_settings": "Configurações de criptografia", "dhcp_settings": "Configurações de DHCP", - "upstream_dns": "Servidores DNS upstream", + "upstream_dns": "Servidores DNS primário", "upstream_dns_help": "Insira os endereços dos servidores, um por linha. Saber mais sobre a configuração de servidores DNS primários.", "upstream_dns_configured_in_file": "Configurado em {{path}}", - "test_upstream_btn": "Testar upstreams", - "upstreams": "Upstreams", + "test_upstream_btn": "Testar DNS primário", + "upstreams": "DNS primário", "apply_btn": "Aplicar", "disabled_filtering_toast": "Filtragem desativada", "enabled_filtering_toast": "Filtragem ativada", @@ -157,22 +157,22 @@ "delete_table_action": "Excluir", "elapsed": "Tempo decorrido", "filters_and_hosts_hint": "O AdGuard Home entende regras básicas de bloqueio de anúncios e a sintaxe de arquivos de hosts.", - "no_blocklist_added": "Nenhuma lista negra foi adicionada", - "no_whitelist_added": "Nenhuma lista branca foi adicionada", - "add_blocklist": "Adicionar lista negra", - "add_allowlist": "Adicionar lista branca", + "no_blocklist_added": "Nenhuma lista de bloqueio adicionada", + "no_whitelist_added": "Nenhuma lista de permissões foi adicionada", + "add_blocklist": "Adicionar lista de bloqueio", + "add_allowlist": "Adicionar lista de permissões", "cancel_btn": "Cancelar", "enter_name_hint": "Digite o nome", "enter_url_or_path_hint": "Digite a URL ou o local da lista", "check_updates_btn": "Verificar atualizações", - "new_blocklist": "Nova lista negra", - "new_allowlist": "Nova lista branca", - "edit_blocklist": "Editar lista negra", - "edit_allowlist": "Editar lista branca", - "choose_blocklist": "Escolher as listas negras", - "choose_allowlist": "Escolher as listas brancas", - "enter_valid_blocklist": "Digite uma URL válida para a lista negra.", - "enter_valid_allowlist": "Digite uma URL válida para a lista branca.", + "new_blocklist": "Nova lista de bloqueio", + "new_allowlist": "Nova lista de permissão", + "edit_blocklist": "Editar lista de bloqueio", + "edit_allowlist": "Editar lista de permissões", + "choose_blocklist": "Escolher as listasde bloqueio", + "choose_allowlist": "Escolher as listas de permissões", + "enter_valid_blocklist": "Digite um URL válido para a lista de bloqueio.", + "enter_valid_allowlist": "Digite uma URL válida para a lista de permissões.", "form_error_url_format": "Formato da URL inválida", "form_error_url_or_path_format": "URL ou local da lista inválida", "custom_filter_rules": "Regras de filtragem personalizadas", @@ -560,7 +560,7 @@ "filtered": "Filtrado", "rewritten": "Reescrito", "safe_search": "Pesquisa segura", - "blocklist": "Lista negra", + "blocklist": "Lista de bloqueio", "milliseconds_abbreviation": "ms", "cache_size": "Tamanho do cache", "cache_size_desc": "Tamanho do cache do DNS (em bytes)", @@ -579,7 +579,7 @@ "filter_category_general_desc": "Listas que bloqueiam o rastreamento e a publicidade na maioria dos dispositivos", "filter_category_security_desc": "Listas especializadas em bloquear domínios de malware, phishing ou fraude", "filter_category_regional_desc": "Listas focadas em anúncios regionais e servidores de rastreamento", - "filter_category_other_desc": "Outras listas negras", + "filter_category_other_desc": "Outras listas de bloqueio", "setup_config_to_enable_dhcp_server": "Configure a configuração para habilitar o servidor DHCP", "original_response": "Resposta original", "click_to_view_queries": "Clique para ver as consultas", diff --git a/client/src/__locales/pt-pt.json b/client/src/__locales/pt-pt.json index 13151fb3..63929d92 100644 --- a/client/src/__locales/pt-pt.json +++ b/client/src/__locales/pt-pt.json @@ -1,12 +1,12 @@ { "client_settings": "Definições do cliente", - "example_upstream_reserved": "pode especificar um DNS upstream <0>para domínio(s) especifico(s)", + "example_upstream_reserved": "Podes especificar um DNS primário <0>para domínio(s) especifico(s)", "example_upstream_comment": "Tu podes especificar o comentário", "upstream_parallel": "Usar consultas paralelas para acelerar a resolução consultando simultaneamente todos os servidores upstream", "parallel_requests": "Solicitações paralelas", "load_balancing": "Balanceamento de carga", "load_balancing_desc": "Consulta um servidor de cada vez. O AdGuard Home usará o algoritmo aleatório ponderado para escolher o servidor, para que o servidor mais rápido seja usado com mais frequência.", - "bootstrap_dns": "Servidores DNS de inicialização", + "bootstrap_dns": "Servidores DNS de arranque", "bootstrap_dns_desc": "Servidores DNS de inicialização são usados para resolver endereços IP dos resolvedores DoH/DoT que especifica como upstreams.", "check_dhcp_servers": "Verificar por servidores DHCP", "save_config": "Guardar definição", @@ -23,7 +23,7 @@ "dhcp_leases": "Concessões DHCP", "dhcp_static_leases": "Concessões de DHCP estático", "dhcp_leases_not_found": "Nenhuma concessão DHCP encontrada", - "dhcp_config_saved": "Configurações DHCP guardadas com sucesso", + "dhcp_config_saved": "Definições DHCP guardadas com sucesso", "dhcp_ipv4_settings": "Definições DHCP IPv4", "dhcp_ipv6_settings": "Definições DHCP IPv6", "form_error_required": "Campo obrigatório", @@ -75,7 +75,7 @@ "query_log": "Registo de consultas", "compact": "Compacto", "nothing_found": "Nada encontrado", - "faq": "Perguntas frequentes", + "faq": "FAQ", "version": "Versão", "address": "Endereço", "protocol": "Protocolo", @@ -91,7 +91,7 @@ "disabled_protection": "Desactivar protecção", "refresh_statics": "Repor estatísticas", "dns_query": "Consultas de DNS", - "blocked_by": "<0>Bloqueado por Filtros", + "blocked_by": "<0>Bloqueado por filtros", "stats_malware_phishing": "Malware/phishing bloqueados", "stats_adult": "Sites adultos bloqueados", "stats_query_domain": "Principais domínios consultados", @@ -125,18 +125,18 @@ "no_servers_specified": "Nenhum servidor especificado", "general_settings": "Definições gerais", "dns_settings": "Definições de DNS", - "dns_blocklists": "Listas negra de DNS", - "dns_allowlists": "Listas branca de DNS", - "dns_blocklists_desc": "O AdGuard Home bloqueará domínios que correspondam às listas negras.", - "dns_allowlists_desc": "Os domínios das listas branca de DNS serão permitidos mesmo que estejam em qualquer uma das listas negra.", + "dns_blocklists": "Lista de bloqueio de DNS", + "dns_allowlists": "Listas de permissões de DNS", + "dns_blocklists_desc": "O AdGuard Home bloqueará domínios que correspondam às listas de bloqueio.", + "dns_allowlists_desc": "Os domínios das listas de permissões de DNS serão permitidos mesmo que estejam em qualquer uma das listas de bloqueio.", "custom_filtering_rules": "Regras de filtragem personalizadas", - "encryption_settings": "Configurações de criptografia", - "dhcp_settings": "Configurações de DHCP", - "upstream_dns": "Servidores DNS upstream", + "encryption_settings": "Definições de criptografia", + "dhcp_settings": "Definições de DHCP", + "upstream_dns": "Servidores DNS primário", "upstream_dns_help": "Insira os endereços dos servidores, um por linha. Saber mais sobre a definição de servidores DNS primários.", "upstream_dns_configured_in_file": "Configurado em {{path}}", - "test_upstream_btn": "Testar upstreams", - "upstreams": "Upstreams", + "test_upstream_btn": "Testar DNS primário", + "upstreams": "DNS primário", "apply_btn": "Aplicar", "disabled_filtering_toast": "Filtragem desactivada", "enabled_filtering_toast": "Filtragem activada", @@ -157,22 +157,22 @@ "delete_table_action": "Apagar", "elapsed": "Tempo decorrido", "filters_and_hosts_hint": "O AdGuard Home entende regras básicas de bloqueio de anúncios e a sintaxe de arquivos de hosts.", - "no_blocklist_added": "Nenhuma lista negra foi adicionada", - "no_whitelist_added": "Nenhuma lista branca foi adicionada", - "add_blocklist": "Adicionar lista negra", - "add_allowlist": "Adicionar lista branca", + "no_blocklist_added": "Nenhuma lista de bloqueio foi adicionada", + "no_whitelist_added": "Nenhuma lista de permissões foi adicionada", + "add_blocklist": "Adicionar lista de bloqueio", + "add_allowlist": "Adicionar lista de permissões", "cancel_btn": "Cancelar", "enter_name_hint": "Insira o nome", "enter_url_or_path_hint": "Digite a URL ou o local da lista", "check_updates_btn": "Verificar actualizações", - "new_blocklist": "Nova lista negra", - "new_allowlist": "Nova lista branca", - "edit_blocklist": "Editar lista negra", - "edit_allowlist": "Editar lista branca", - "choose_blocklist": "Escolher as listas negras", - "choose_allowlist": "Escolher as listas brancas", - "enter_valid_blocklist": "Digite uma URL válida para a lista negra.", - "enter_valid_allowlist": "Digite uma URL válida para a lista branca.", + "new_blocklist": "Nova lista de bloqueio", + "new_allowlist": "Nova lista de permissões", + "edit_blocklist": "Editar lista de bloqueio", + "edit_allowlist": "Editar lista de permissões", + "choose_blocklist": "Escolher as listas de bloqueio", + "choose_allowlist": "Escolher as listas de permissões", + "enter_valid_blocklist": "Digite uma URL válida para a lista de bloqueio.", + "enter_valid_allowlist": "Digite uma URL válida para a lista de permissões.", "form_error_url_format": "Formato da URL inválida", "form_error_url_or_path_format": "URL ou local da lista inválida", "custom_filter_rules": "Regras de filtragem personalizadas", @@ -185,7 +185,7 @@ "example_comment_meaning": "apenas um comentário", "example_comment_hash": "# Também um comentário", "example_regex_meaning": "bloquear o acesso aos domínios que correspondam à expressão regular especificada", - "example_upstream_regular": "dNS regular (através do UDP)", + "example_upstream_regular": "DNS regular (através do UDP)", "example_upstream_dot": "<0>DNS-sobre-TLS criptografado", "example_upstream_doh": "<0>DNS-sobre-HTTPS criptografado", "example_upstream_doq": "<0>DNS-sobre-QUIC criptografado", @@ -338,7 +338,7 @@ "encryption_dot": "Porta DNS-sobre-TLS", "encryption_dot_desc": "Se essa porta estiver configurada, o AdGuard Home irá executar o servidor DNS-sobre- TSL nesta porta.", "encryption_doq": "Porta DNS-sobre-QUIC", - "encryption_doq_desc": "Se esta porta estiver configurada, o AdGuard Home executará um servidor DNS-sobre-QUIC nesta porta. É experimental e pode não ser confiável. Além disso, não há muitos clientes que ofereçam suporte no momento.", + "encryption_doq_desc": "Se esta porta estiver configurada, o AdGuard Home executará um servidor DNS-sobre-QUIC nesta porta. É experimental e pode não ser confiável. Além disso, não há demasiados clientes que ofereçam suporte no momento.", "encryption_certificates": "Certificados", "encryption_certificates_desc": "Para usar criptografia, precisa de fornecer uma cadeia de certificados SSL válida para o seu domínio. Pode obter um certificado gratuito em <0> {{link}} ou pode comprá-lo numa das autoridades de certificação confiáveis.", "encryption_certificates_input": "Copie/cole aqui o seu certificado codificado em PEM.", @@ -362,7 +362,7 @@ "form_error_port_unsafe": "Esta porta não é segura", "form_error_equal": "Não deve ser igual", "form_error_password": "As palavras-passe não coincidem", - "reset_settings": "Repor configurações", + "reset_settings": "Repor definições", "update_announcement": "AdGuard Home {{version}} está disponível!<0>Clique aqui para mais informações.", "setup_guide": "Guia de instalação", "dns_addresses": "Endereços DNS", @@ -393,7 +393,7 @@ "form_add_id": "Adicionar identificador", "form_client_name": "Insira o nome do cliente", "name": "Nome", - "client_global_settings": "Usar configurações globais", + "client_global_settings": "Usar definições globais", "client_deleted": "Cliente \"{{key}}\" excluído com sucesso", "client_added": "Cliente \"{{key}}\" adicionado com sucesso", "client_updated": "Cliente \"{{key}}\" actualizado com sucesso", @@ -402,7 +402,7 @@ "list_confirm_delete": "Você tem certeza de que deseja excluir essa lista?", "auto_clients_title": "Clientes (tempo de execução)", "auto_clients_desc": "Dados dos clientes que usam o AdGuard Home, que não são armazenados na definição", - "access_title": "Configurações de acesso", + "access_title": "Definições de acesso", "access_desc": "Aqui pode configurar as regras de acesso para o servidores de DNS do AdGuard Home.", "access_allowed_title": "Clientes permitidos", "access_allowed_desc": "Uma lista de endereços IP ou CIDR. Ao configurar, o AdGuard Home irá permitir solicitações apenas desses endereços de IP.", @@ -410,7 +410,7 @@ "access_disallowed_desc": "Uma lista de endereços IP ou CIDR. Ao configurar, o AdGuard Home irá descartar as solicitações desses endereços de IP.", "access_blocked_title": "Domínios bloqueados", "access_blocked_desc": "Não confunda isso com os filtros. O AdGuard Home irá descartar as consultas DNS com esses domínios.", - "access_settings_saved": "Configurações de acesso foram guardadas com sucesso", + "access_settings_saved": "Definições de acesso foram guardadas com sucesso", "updates_checked": "Actualizações verificadas com sucesso", "updates_version_equal": "O AdGuard Home está actualizado", "check_updates_now": "Verificar actualizações", @@ -487,7 +487,7 @@ "username_placeholder": "Insira o nome de utilizador", "password_label": "Palavra-passe", "password_placeholder": "Insira palavra-passe", - "sign_in": "Entrar", + "sign_in": "Iniciar sessão", "sign_out": "Sair", "forgot_password": "Não se lembra da palavra-passe?", "forgot_password_desc": "Siga <0>estes passos para criar uma nova palavra-passe para a sua conta de utilizador.", @@ -551,7 +551,7 @@ "validated_with_dnssec": "Validado com DNSSEC", "all_queries": "Todas as consultas", "show_blocked_responses": "Bloqueado", - "show_whitelisted_responses": "Lista Branca", + "show_whitelisted_responses": "Na lista branca", "show_processed_responses": "Processado", "blocked_safebrowsing": "Bloqueado pela navegação segura", "blocked_adult_websites": "Sítios adultos bloqueados", @@ -560,7 +560,7 @@ "filtered": "Filtrado", "rewritten": "Reescrito", "safe_search": "Pesquisa segura", - "blocklist": "Lista negra", + "blocklist": "Lista de bloqueio", "milliseconds_abbreviation": "ms", "cache_size": "Tamanho do cache", "cache_size_desc": "Tamanho do cache do DNS (em bytes)", @@ -575,11 +575,11 @@ "filter_category_general": "Geral", "filter_category_security": "Segurança", "filter_category_regional": "Regional", - "filter_category_other": "Outro", + "filter_category_other": "Noutro", "filter_category_general_desc": "Listas que bloqueiam o monitorização e a publicidade na maioria dos dispositivos", "filter_category_security_desc": "Listas especializadas em bloquear domínios de malware, phishing ou fraude", "filter_category_regional_desc": "Listas focadas em anúncios regionais e servidores de monitorização", - "filter_category_other_desc": "Outras listas negras", + "filter_category_other_desc": "Outras listas de bloqueio", "setup_config_to_enable_dhcp_server": "Defina a definição para habilitar o servidor DHCP", "original_response": "Resposta original", "click_to_view_queries": "Clique para ver as consultas", diff --git a/client/src/__locales/ro.json b/client/src/__locales/ro.json index 5295b2dc..21238763 100644 --- a/client/src/__locales/ro.json +++ b/client/src/__locales/ro.json @@ -1,6 +1,6 @@ { "client_settings": "Setări client", - "example_upstream_reserved": "Puteți preciza un DNS upstream <0>de domeniu(ii) specific(e)", + "example_upstream_reserved": "Puteți preciza un DNS în amonte <0>de domeniu(ii) specific(e)", "example_upstream_comment": "Puteți specifica comentariul", "upstream_parallel": "Folosiți interogări paralele pentru rezolvări rapide interogând simultan toate serverele în amonte", "parallel_requests": "Solicitări paralele", @@ -107,7 +107,7 @@ "number_of_dns_query_days": "Un număr de interogări DNS procesate în ultima {{count}} zi", "number_of_dns_query_days_plural": "Un număr de interogări DNS procesate în ultimele {{count}} zile", "number_of_dns_query_24_hours": "Un număr de interogări DNS procesate în ultimele 24 de ore", - "number_of_dns_query_blocked_24_hours": "Un număr de solicitări DNS blocate de filtrele de blocare și listele de blocaj de hosts", + "number_of_dns_query_blocked_24_hours": "Un număr de solicitări DNS blocate de filtrele de blocare și lista de blocaje din hosts", "number_of_dns_query_blocked_24_hours_by_sec": "Un număr de solicitări DNS blocate de modulul de securitate de navigare AdGuard", "number_of_dns_query_blocked_24_hours_adult": "Un număr de site-uri web pentru adulți blocate", "enforced_save_search": "Căutare protejată întărită", @@ -117,7 +117,7 @@ "block_domain_use_filters_and_hosts": "Blocați domenii folosind filtre și fișiere hosts", "filters_block_toggle_hint": "Puteți configura regulile de blocare în setările Filtre.", "use_adguard_browsing_sec": "Utilizați serviciul Navigarea în Securitate AdGuard", - "use_adguard_browsing_sec_hint": "AdGuard Home va verifica dacă domeniul este în lista negră a serviciul web de securitate de navigare. Pentru acesta va utiliza un lookup API discret: un prefix scurt al numelui de domeniu SHA256 hash este trimis serverului.", + "use_adguard_browsing_sec_hint": "AdGuard Home va verifica dacă domeniul este în lista de blocări a serviciul web de securitate de navigare. Pentru acesta va utiliza un lookup API discret: un prefix scurt al numelui de domeniu SHA256 hash este trimis serverului.", "use_adguard_parental": "Utilizați Controlul Parental AdGuard", "use_adguard_parental_hint": "AdGuard Home va verifica pentru conținut adult pe domeniu. Utilizează același API discret ca cel utilizat de serviciul de securitate de navigare.", "enforce_safe_search": "Căutare protejată întărită", @@ -125,14 +125,14 @@ "no_servers_specified": "Nu sunt specificate servere", "general_settings": "Setări Generale", "dns_settings": "Setări DNS", - "dns_blocklists": "DNS liste blocări", - "dns_allowlists": "DNS liste autorizări", + "dns_blocklists": "Liste de blocări DNS", + "dns_allowlists": "Listă de DNS-uri autorizate", "dns_blocklists_desc": "AdGuard Home blochează domenii incluse în liste de blocări.", "dns_allowlists_desc": "Domeniile DNS autorizate vor fi permise, chiar dacă se află pe orice listă de blocări.", "custom_filtering_rules": "Reguli filtrare personale", "encryption_settings": "Setări de criptare", "dhcp_settings": "Setări DHCP", - "upstream_dns": "Servere upstream DNS", + "upstream_dns": "Servere DNS în amonte", "upstream_dns_help": "Introduceți adresele serverelor una pe linie. Aflați mai multe despre configurarea serverelor DNS în amonte.", "upstream_dns_configured_in_file": "Configurat în {{path}}", "test_upstream_btn": "Testați upstreams", @@ -170,7 +170,7 @@ "edit_blocklist": "Editare blocare", "edit_allowlist": "Editare autorizare", "choose_blocklist": "Alegeți liste de blocări", - "choose_allowlist": "Alegeți liste de permisiuni", + "choose_allowlist": "Alegeți liste de autorizări", "enter_valid_blocklist": "Introduceți un URL valid pentru blocare.", "enter_valid_allowlist": "Introduceți un URL valid pentru autorizare.", "form_error_url_format": "Format URL invalid", @@ -192,7 +192,7 @@ "example_upstream_sdns": "puteți utiliza <0>DNS Stamps pentru rezolvere <1>DNSCrypt sau <2>DNS-over-HTTPS", "example_upstream_tcp": "DNS clasic (over TCP)", "all_lists_up_to_date_toast": "Toate listele sunt deja la zi", - "updated_upstream_dns_toast": "Serverele DNS upstream aduse la zi", + "updated_upstream_dns_toast": "Serverele DNS în amonte aduse la zi", "dns_test_ok_toast": "Serverele DNS specificate funcționează corect", "dns_test_not_ok_toast": "Serverul \"{{key}}\": nu a putut fi utilizat, verificați dacă l-ați scris corect", "unblock": "Deblocați", @@ -212,7 +212,7 @@ "empty_response_status": "Gol", "show_all_filter_type": "Arată tot", "show_filtered_type": "Arată cele filtrate", - "no_logs_found": "Nici un jurnal găsit", + "no_logs_found": "Niciun jurnal găsit", "refresh_btn": "Actualizare", "previous_btn": "Anterior", "next_btn": "Următor", @@ -560,7 +560,7 @@ "filtered": "Filtrate", "rewritten": "Rescrise", "safe_search": "Căutare sigură", - "blocklist": "Lista neagră", + "blocklist": "Lista de blocări", "milliseconds_abbreviation": "ms", "cache_size": "Mărime cache", "cache_size_desc": "Mărime cache DNS (în octeți)", diff --git a/client/src/__locales/ru.json b/client/src/__locales/ru.json index c0fb3705..9c9b02ff 100644 --- a/client/src/__locales/ru.json +++ b/client/src/__locales/ru.json @@ -511,7 +511,7 @@ "disable_ipv6": "Отключить IPv6", "disable_ipv6_desc": "Если эта опция включена, все DNS-запросы адресов IPv6 (тип AAAA) будут игнорироваться.", "fastest_addr": "Самый быстрый IP-адрес", - "fastest_addr_desc": "Опросить все DNS-серверы и вернуть самый быстрый IP-адрес из полученных ответов", + "fastest_addr_desc": "Опросить все DNS-серверы и вернуть самый быстрый IP-адрес из полученных ответов. Это замедлит DNS-запросы, так как нужно будет дождаться ответов со всех DNS-серверов, но улучшит соединение.", "autofix_warning_text": "При нажатии \"Исправить\" AdGuard Home настроит вашу систему на использование DNS-сервера AdGuard Home.", "autofix_warning_list": "Будут выполняться следующие задачи: <0>Деактивировать системный DNSStubListener <0>Установить адрес сервера DNS на 127.0.0.1 <0>Создать символическую ссылку /etc/resolv.conf на /run/systemd/resolve/resolv.conf <0>Остановить DNSStubListener (перезагрузить системную службу).", "autofix_warning_result": "В результате все DNS-запросы от вашей системы будут по умолчанию обрабатываться AdGuard Home.\n", diff --git a/client/src/__locales/si-lk.json b/client/src/__locales/si-lk.json index a3c09d5f..d39bda0b 100644 --- a/client/src/__locales/si-lk.json +++ b/client/src/__locales/si-lk.json @@ -1,5 +1,6 @@ { "client_settings": "අනුග්‍රාහක සැකසුම්", + "example_upstream_comment": "ඔබට අදහසක් සඳහන් කළ හැකිය", "parallel_requests": "සමාන්තර ඉල්ලීම්", "load_balancing": "ධාරිතාව තුලනය", "check_dhcp_servers": "ග.ධා.වි.කෙ. සේවාදායකයන් සඳහා පරීක්ෂා කරන්න", @@ -12,13 +13,13 @@ "dhcp_enable": "ග.ධා.වි.කෙ. සේවාදායකය සබල කරන්න", "dhcp_disable": "ග.ධා.වි.කෙ. සේවාදායකය අබල කරන්න", "dhcp_config_saved": "ග.ධා.වි.කෙ. වින්‍යාසය සාර්ථකව සුරකින ලදි", - "dhcp_ipv4_settings": "ග.ධා.වි.කෙ. IPv4 සැකසුම්", - "dhcp_ipv6_settings": "ග.ධා.වි.කෙ. IPv6 සැකසුම්", + "dhcp_ipv4_settings": "ග.ධා.වි.කෙ. අයිපීවී 4 සැකසුම්", + "dhcp_ipv6_settings": "ග.ධා.වි.කෙ. අයිපීවී 6 සැකසුම්", "form_error_required": "අවශ්‍ය ක්ෂේත්‍රයකි", "form_error_ip4_format": "වලංගු නොවන IPv4 ආකෘතියකි", "form_error_ip6_format": "වලංගු නොවන IPv6 ආකෘතියකි", "form_error_ip_format": "වලංගු නොවන අ.ජා. කෙ. (IP) ආකෘතියකි", - "form_error_mac_format": "වලංගු නොවන MAC ආකෘතියකි", + "form_error_mac_format": "වලංගු නොවන මා.ප්‍ර.පා. ආකෘතියකි", "form_error_client_id_format": "වලංගු නොවන අනුග්‍රාහක හැඳුනුම් ආකෘතියකි", "form_error_positive": "0 ට වඩා වැඩි විය යුතුය", "form_error_negative": "0 හෝ ඊට වැඩි විය යුතුය", @@ -92,19 +93,19 @@ "filters_block_toggle_hint": "ඔබට අවහිර කිරීමේ නීති පෙරහන් තුළ පිහිටුවිය හැකිය.", "use_adguard_browsing_sec": "AdGuard browsing security වෙබ් සේවාව භාවිතා කරන්න", "use_adguard_parental": "AdGuard parental control වෙබ් සේවාව භාවිතා කරන්න", - "use_adguard_parental_hint": "වසමේ වැඩිහිටියන්ට අදාල කරුණු අඩංගු දැයි ඇඩ්ගාර්ඩ් හෝම් විසින් පරීක්ෂා කරනු ඇත. එය browsing security වෙබ් සේවාව මෙන් රහස්‍යතා හිතකාමී යෙ.ක්‍ර. අ.මු. (API) භාවිතා කරයි.", + "use_adguard_parental_hint": "වසමේ වැඩිහිටියන්ට අදාල කරුණු අඩංගු දැයි ඇඩ්ගාර්ඩ් හෝම් විසින් පරීක්ෂා කරනු ඇත. එය බ්‍රව්සින් සෙකියුරිටි වෙබ් සේවාව මෙන් රහස්‍යතා හිතකාමී යෙ.ක්‍ර. අ.මු. (API) භාවිතා කරයි.", "enforce_safe_search": "ආරක්ෂිත සෙවීම බලාත්මක කරන්න", "enforce_save_search_hint": "ඇඩ්ගාර්ඩ් හෝම් හට පහත සෙවුම් යන්ත්‍ර තුළ ආරක්ෂිත සෙවීම බලාත්මක කළ හැකිය: ගූගල්, යූටියුබ්, බින්ග්, ඩක්ඩක්ගෝ, යාන්ඩෙක්ස් සහ පික්සාබේ.", "no_servers_specified": "සේවාදායක කිසිවක් නිශ්චිතව දක්වා නැත", "general_settings": "පොදු සැකසුම්", "dns_settings": "DNS සැකසුම්", - "dns_blocklists": "DNS අවහිර කිරී‌‌‌‌‌මේ ලැයිස්තු", - "dns_allowlists": "DNS අවසර දීමේ ලැයිස්තු", + "dns_blocklists": "ව.නා.ප. අවහිර කිරී‌‌‌‌‌මේ ලැයිස්තු", + "dns_allowlists": "ව.නා.ප. අවසර දීමේ ලැයිස්තු", "dns_blocklists_desc": "ඇඩ්ගාර්ඩ් හෝම් විසින් අවහිර කිරී‌‌‌‌‌මේ ලැයිස්තු වලට ගැලපෙන වසම් අවහිර කරනු ඇත.", "dns_allowlists_desc": "අවසර දීමේ ව.නා.ප. ලැයිස්තුවල වසම් කිසියම් අවහිර කිරී‌‌‌‌‌මේ ලැයිස්තුවක අඩංගු වුවද එය නොසලකා හැර අවසර දෙනු ලැබේ.", "custom_filtering_rules": "අභිරුචි පෙරීමේ නීති", "encryption_settings": "සංකේතාංකන සැකසුම්", - "dhcp_settings": "DHCP සැකසුම්", + "dhcp_settings": "ග.ධා.වි.කෙ. සැකසුම්", "upstream_dns": "Upstream ව.නා.ප. සේවාදායකයන්", "upstream_dns_help": "පේළියකට එක් සේවාදායක ලිපිනය බැගින් ඇතුළත් කරන්න. upstream ව.නා.ප. (DNS) \n සේවාදායකයන් වින්‍යාසගත කිරීම ගැන තව දැනගන්න.", "apply_btn": "යොදන්න", @@ -201,14 +202,16 @@ "anonymize_client_ip": "අනුග්‍රාහකයෙහි අ.ජා. කෙ. (IP) නිර්නාමික කරන්න", "anonymize_client_ip_desc": "ලොග සහ සංඛ්‍යාලේඛන තුළ අනුග්‍රාහකයේ සම්පූර්ණ අ.ජා. කෙ. ලිපිනය සුරකීමෙන් වලකින්න", "dns_config": "ව.නා.ප. සේවාදායක වින්‍යාසය", + "dns_cache_config": "ව.නා.ප. නිහිත වින්‍යාසය", + "dns_cache_config_desc": "මෙහිදී ඔබට ව.නා.ප. නිහිතය වින්‍යාසගත කළ හැකිය", "blocking_mode": "අවහිර කරන ආකාරය", "default": "සුපුරුදු", "nxdomain": "නොපවතින වසම", "refused": "REFUSED", "null_ip": "අභිශූන්‍යය අ.ජා. කෙ.", "custom_ip": "අභිරුචි අ.ජා. කෙ.", - "dns_over_https": "DNS-over-HTTPS", - "dns_over_tls": "DNS-over-TLS", + "blocking_ipv4": "අයි.පී.වී.4 අවහිර කිරීම\n", + "blocking_ipv6": "අයි.පී.වී.6 අවහිර කිරීම", "form_enter_rate_limit": "අනුපාත සීමාව ඇතුළත් කරන්න", "rate_limit": "අනුපාත සීමාව", "edns_enable": "EDNS අනුග්‍රාහක අනුජාලය සබල කරන්න", @@ -219,7 +222,7 @@ "blocking_mode_nxdomain": "නොපවතින වසම (NXDOMAIN): NXDOMAIN කේතය සමඟ ප්‍රතිචාර දක්වයි", "blocking_mode_null_ip": "අභිශූන්‍යය අ.ජා. කෙ. : ශුන්‍ය අ.ජා. කෙ. ලිපිනය සමඟ ප්‍රතිචාර දක්වයි (A සඳහා 0.0.0.0; AAAA සඳහා ::)", "blocking_mode_custom_ip": "අභිරුචි අන්තර්ජාල කෙටුම්පත: අතින් සැකසූ අ.ජා. කෙ. ලිපිනයක් සමඟ ප්‍රතිචාර දක්වයි", - "upstream_dns_client_desc": "ඔබ මෙම ක්ෂේත්‍රය හිස්ව තබා ගන්නේ නම්, ඇඩ්ගාර්ඩ් හෝම් විසින් <0>DNS සැකසුම් හි වින්‍යාසගත කර ඇති සේවාදායකයන් භාවිතා කරනු ඇත.", + "upstream_dns_client_desc": "ඔබ මෙම ක්ෂේත්‍රය හිස්ව තබා ගන්නේ නම්, ඇඩ්ගාර්ඩ් හෝම් විසින් <0>ව.නා.ප. සැකසුම් හි වින්‍යාසගත කර ඇති සේවාදායකයන් භාවිතා කරනු ඇත.", "tracker_source": "ලුහුබැඳීම් මූලාශ්‍රය", "source_label": "මූලාශ්‍රය", "found_in_known_domain_db": "දැනුවත් වසම් දත්ත ගබඩාවේ හමු විය.", @@ -233,9 +236,10 @@ "install_settings_title": "පරිපාලක වෙබ් අතුරු මුහුණත", "install_settings_listen": "සවන් දෙන අතුරු මුහුණත", "install_settings_port": "කවුළුව", + "install_settings_interface_link": "ඔබගේ ඇඩ්ගාර්ඩ් හෝම් පරිපාලක වෙබ් අතුරු මුහුණත පහත ලිපිනයන්ගෙන් ප්‍රවේශ විය හැකිය:", "form_error_port": "වලංගු කවුළුවක අගයක් ඇතුළත් කරන්න", "install_settings_dns": "ව.නා.ප. සේවාදායකය", - "install_settings_dns_desc": "පහත ලිපිනයන්හි ව.නා.ප. සේවාදායකය භාවිතා කිරීම සඳහා ඔබගේ උපාංග හෝ රවුටරය වින්‍යාසගත කිරීමට අවශ්‍ය වනු ඇත:", + "install_settings_dns_desc": "පහත ලිපිනයන්හි ව.නා.ප. සේවාදායකය භාවිතා කිරීම සඳහා ඔබගේ උපාංග හෝ මාර්ගකාරකය වින්‍යාසගත කිරීමට අවශ්‍ය වනු ඇත:", "install_settings_all_interfaces": "සියලුම අතුරුමුහුණත්", "install_auth_title": "සත්‍යාපනය", "install_auth_desc": "ඔබගේ ඇඩ්ගාර්ඩ් හෝම් පරිපාලක වෙබ් අතුරු මුහුණතට මුරපද සත්‍යාපනය වින්‍යාසගත කිරීම අතිශයින් නිර්දේශ කෙරේ. එය ඔබගේ ස්ථානීය ජාල‌යෙන් පමණක් ප්‍රවේශ විය හැකි වුවද, එය තව දුරටත් සීමා රහිත ප්‍රවේශයකින් ආරක්ෂා කර ගැනීම වැදගත් ය.", @@ -251,10 +255,11 @@ "install_submit_desc": "පිහිටුවීමේ ක්‍රියා පටිපාටිය අවසන් වී ඇති අතර ඔබ ඇඩ්ගාර්ඩ් හෝම් භාවිතය ආරම්භ කිරීමට සූදානම්ය.", "install_devices_router": "මාර්ගකාරකය", "install_devices_router_desc": "මෙම පිහිටුම ඔබගේ නිවසේ මාර්ගකාරකයට සම්බන්ධ සියලුම උපාංග ස්වයංක්‍රීයව ආවරණය කරන අතර ඔබට ඒවා අතින් වින්‍යාසගත කිරීමට අවශ්‍ය නොවනු ඇත.", - "install_devices_router_list_1": "ඔබේ මාර්ගකාරකය සඳහා වූ මනාපයන් විවෘත කරන්න. සාමාන්‍යයෙන්, එය ඔබගේ බ්‍රව්සරයෙන් URL එකක් හරහා (http://192.168.0.1/ හෝ http://192.168.1.1/ වැනි) පිවිසිය හැකිය. මුරපදය ඇතුළත් කිරීමට ඔබෙන් ඉල්ලා සිටිය හැකිය. ඔබට එය මතක නැතිනම්, බොහෝ විට මාර්ගකාරකයේ බොත්තමක් එබීමෙන් මුරපදය නැවත සැකසිය හැක. සමහර මාර්ගකාරක සඳහා විශේෂිත යෙදුමක් අවශ්‍ය වන අතර, එය දැනටමත් ඔබේ පරිගණකයේ/දුරකථනයේ ස්ථාපනය කර තිබිය යුතුය.", + "install_devices_address": "ඇඩ්ගාර්ඩ් හෝම් ව.නා.ප. සේවාදායකය පහත ලිපිනයන්ට සවන් දෙමින් පවතී", + "install_devices_router_list_1": "ඔබේ මාර්ගකාරකය සඳහා වූ මනාපයන් විවෘත කරන්න. සාමාන්‍යයෙන්, එය ඔබගේ අතිරික්සුවෙන් ඒ.ස.නි.(URL) ක් හරහා (http://192.168.0.1/ හෝ http://192.168.1.1/ වැනි) පිවිසිය හැකිය. මුර පදය ඇතුළත් කිරීමට ඔබෙන් ඉල්ලා සිටිය හැකිය. ඔබට එය මතක නැතිනම්, බොහෝ විට මාර්ගකාරකයේ බොත්තමක් එබීමෙන් මුරපදය නැවත සැකසිය හැක. සමහර මාර්ගකාරක සඳහා විශේෂිත යෙදුමක් අවශ්‍ය වන අතර, එය දැනටමත් ඔබේ පරිගණකයේ/දුරකථනයේ ස්ථාපනය කර තිබිය යුතුය.", "install_devices_router_list_2": "ග.ධා.වි.කෙ. (DHCP)/ ව.නා.ප. (DNS) සැකසුම් සොයා ගන්න. ඉලක්කම් කට්ටල දෙකකට හෝ තුනකට ඉඩ දෙන ක්ෂේත්‍රයක් අසල ඇති ව.නා.ප. අක්ෂර සොයන්න, සෑම එකක්ම ඉලක්කම් එකේ සිට තුන දක්වා කාණ්ඩ හතරකට බෙදා ඇත.", "install_devices_router_list_3": "ඔබගේ ඇඩ්ගාර්ඩ් හෝම් සේවාදායක ලිපින එහි ඇතුළත් කරන්න.", - "install_devices_router_list_4": "ඔබට සමහර වර්ගයේ රවුටර වල අභිරුචි ව.නා.ප. (DNS) සේවාදායකයක් සැකසිය නොහැක. මෙම අවස්ථාවේදී ඇඩ්ගාර්ඩ් හෝම් <0>ග.ධා.වි.කෙ. සේවාදායකයක් ලෙස පිහිටුවන්නේ නම් එය උපකාර වනු ඇත. එසේ නොමැතිනම්, ඔබගේ විශේෂිත මාර්ගකාරක මාදිළිය සඳහා වූ ව.නා.ප. සේවාදායකයන් රිසිකරණය කරන්නේ කෙසේද යන්න පිළිබඳ අත්පොත සෙවිය යුතුය.", + "install_devices_router_list_4": "ඔබට සමහර වර්ගයේ මාර්ගකාරකය වල අභිරුචි ව.නා.ප. සේවාදායකයක් සැකසිය නොහැක. මෙම අවස්ථාවේදී ඇඩ්ගාර්ඩ් හෝම් <0>ග.ධා.වි.කෙ. සේවාදායකයක් ලෙස පිහිටුවන්නේ නම් එය උපකාර වනු ඇත. එසේ නොමැතිනම්, ඔබගේ විශේෂිත මාර්ගකාරක මාදිළිය සඳහා වූ ව.නා.ප. සේවාදායකයන් රිසිකරණය කරන්නේ කෙසේද යන්න පිළිබඳ අත්පොත සෙවිය යුතුය.", "install_devices_windows_list_1": "ආරම්භක මෙනුව හෝ වින්ඩෝස් සෙවුම හරහා පාලක පැනලය විවෘත කරන්න.", "install_devices_windows_list_2": "ජාල සහ අන්තර්ජාල ප්‍රවර්ගයට ගොස් පසුව ජාල සහ බෙදාගැනීමේ මධ්‍යස්ථානය වෙත යන්න.", "install_devices_windows_list_3": "උපයුක්තක‌‌‌යෙහි සැකසුම් වෙනස් කිරීම තිරයේ වම් පසින් සොයාගෙන එය මත ක්ලික් කරන්න.", @@ -314,7 +319,7 @@ "reset_settings": "සැකසුම් යළි පිහිටුවන්න", "update_announcement": "ඇඩ්ගාර්ඩ් හෝම් {{version}} දැන් ලබා ගත හැකිය! වැඩි විස්තර සඳහා <0>මෙහි ක්ලික් කරන්න.", "setup_guide": "පිහිටුවීමේ මාර්ගෝපදේශය", - "dns_addresses": "DNS ලිපින", + "dns_addresses": "ව.නා.ප. ලිපින", "dns_start": "ව.නා.ප. සේවාදායකය ආරම්භ වෙමින් පවතී", "dns_status_error": "ව.නා.ප. සේවාදායකයේ තත්වය පරීක්ෂා කිරීමේදී දෝෂයකි", "down": "පහත", @@ -337,7 +342,7 @@ "ip_address": "අ.ජා. කෙ. (IP) ලිපිනය", "client_identifier_desc": "අනුග්‍රාහකයන් අ.ජා. කෙ. (IP) ලිපිනයක් හෝ මා.ප්‍ර.පා. (MAC) ලිපිනයක් මගින් හඳුනාගත හැකිය. මා.ප්‍ර.පා. හඳුන්වනයක් ලෙස භාවිතා කළ හැක්කේ ඇඩ්ගාර්ඩ් හෝම් ද <0>DHCP සේවාදායකයක් නම් පමණක් බව කරුණාවෙන් සලකන්න. ", "form_enter_ip": "අ.ජා. කෙ. (IP) ඇතුළත් කරන්න", - "form_enter_mac": "MAC ඇතුළත් කරන්න", + "form_enter_mac": "මා.ප්‍ර.පා. (MAC) ඇතුළත් කරන්න", "form_enter_id": "හඳුන්වනය ඇතුළත් කරන්න", "form_add_id": "හඳුන්වනයක් එක් කරන්න", "form_client_name": "අනුග්‍රාහකයේ නම ඇතුළත් කරන්න", @@ -360,22 +365,20 @@ "updates_checked": "යාවත්කාලීන කිරීම් සාර්ථකව පරික්ෂා කර ඇත", "updates_version_equal": "ඇඩ්ගාර්ඩ් හෝම් යාවත්කාලීනයි", "check_updates_now": "යාවත්කාල කිරීම සඳහා දැන් පරීක්ෂා කරන්න", - "dns_privacy": "DNS රහස්‍යතා", - "setup_dns_privacy_1": "<0>DNS-over-TLS: <1>{{address}} තන්තුව භාවිතා කරයි.", - "setup_dns_privacy_2": "<0>DNS-over-HTTPS: <1>{{address}} තන්තුව භාවිතා කරයි.", - "setup_dns_privacy_android_2": "<1>DNS-over-HTTPS සහ <1>DNS-over-TLS සඳහා <0>AdGuard for Android සහය දක්වයි.", - "setup_dns_privacy_ios_2": "<1>DNS-over-HTTPS සහ <1>DNS-over-TLS පිහිටුවීම් සඳහා <0>AdGuard for iOS සහය දක්වයි.", - "setup_dns_privacy_other_2": "<0>dnsproxy දන්නා සියලුම ආරක්ෂිත DNS කෙටුම්පත් සඳහා සහය දක්වයි.", + "dns_privacy": "ව.නා.ප. රහස්‍යතා", + "setup_dns_privacy_3": "<0>මෙහි ඔබට භාවිතා කළ හැකි මෘදුකාංග ලැයිස්තුවක් ඇත.", + "setup_dns_privacy_other_2": "<0>ඩීඑන්එස්ප්‍රොක්සි දන්නා සියලුම ආරක්ෂිත ව.නා.ප. කෙටුම්පත් සඳහා සහය දක්වයි.", "setup_dns_privacy_other_3": "<1>DNS-over-HTTPS සඳහා <0>dnscrypt-පෙරකලාසිය සහය දක්වයි.", "setup_dns_privacy_other_4": "<1>DNS-over-HTTPS සඳහා <0>මොසිල්ලා ෆයර්ෆොක්ස් සහය දක්වයි.", "setup_dns_notice": "ඔබට <1>DNS-over-HTTPS හෝ <1>DNS-over-TLS භාවිතා කිරීම සඳහා ඇඩ්ගාර්ඩ් හෝම් සැකසුම් තුළ <0>සංකේතාංකනය වින්‍යාසගත කිරීමට අවශ්‍ය වේ.", - "rewrite_add": "DNS නැවත ලිවීමක් එකතු කරන්න", - "rewrite_confirm_delete": "\"{{key}}\" සඳහා DNS නැවත ලිවීම ඉවත් කිරීමට අවශ්‍ය බව ඔබට විශ්වාසද?", + "rewrite_added": "\"{{key}}\" සඳහා ව.නා.ප. නැවත ලිවීම සාර්ථකව එකතු කරන ලදි", + "rewrite_add": "ව.නා.ප. නැවත ලිවීමක් එකතු කරන්න", + "rewrite_not_found": "ව.නා.ප. නැවත ලිවීම් හමු නොවීය", + "rewrite_confirm_delete": "\"{{key}}\" සඳහා ව.නා.ප. නැවත ලිවීම ඉවත් කිරීමට අවශ්‍ය බව ඔබට විශ්වාසද?", "rewrite_desc": "විශේෂිත වසම් නාමයක් සඳහා අභිරුචි ව.නා.ප. ප්‍රතිචාර පහසුවෙන් වින්‍යාසගත කිරීමට ඉඩ දෙයි.", "rewrite_applied": "නැවත ලිවීමේ නීතිය යොදා ඇත", "rewrite_hosts_applied": "ධාරක ගොනු නීතිය මගින් නැවත ලියා ඇත", - "dns_rewrites": "DNS නැවත ලිවීම්", - "form_domain": "වසම ඇතුළත් කරන්න", + "dns_rewrites": "ව.නා.ප. නැවත ලිවීම්", "form_answer": "අ.ජා. කෙ. (IP) ලිපිනය ‌හෝ වසම ඇතුළත් කරන්න ", "form_error_domain_format": "වලංගු නොවන වසම් ආකෘතියකි", "form_error_answer_format": "වලංගු නොවන පිළිතුරු ආකෘතියකි", @@ -403,6 +406,7 @@ "domain": "වසම", "answer": "පිළිතුර", "filter_added_successfully": "පෙරහන සාර්ථකව එකතු කරන ලදි", + "filter_removed_successfully": "ලැයිස්තුව සාර්ථකව ඉවත් කරන ලදි", "filter_updated": "ලැයිස්තුව සාර්ථකව යාවත්කාලීන කර ඇත", "statistics_configuration": "සංඛ්‍යාලේඛන වින්‍යාසය", "statistics_retention": "සංඛ්‍යාලේඛන රඳවා තබා ගැනීම", @@ -431,20 +435,24 @@ "network": "ජාලය", "descr": "විස්තරය", "whois": "Whois", + "filtering_rules_learn_more": "ඔබගේ ම ධාරක ලැයිස්තු සෑදීම පිළිබඳව <0>තව දැනගන්න.", "blocked_by_response": "ප්‍රතිචාරය අන්. නාමයක් (CNAME) හෝ අ.ජා. කෙ. මගින් අවහිර කර ඇත", "blocked_by_cname_or_ip": "අන්. නාමයක් (CNAME) හෝ අ.ජා. කෙ. මගින් අවහිර කර ඇත", "try_again": "නැවත උත්සහා කරන්න", "example_rewrite_domain": "මෙම වසම් නාමය සඳහා පමණක් ප්‍රතිචාර නැවත ලියන්න.", "example_rewrite_wildcard": "<0>example.org සහ එහි සියලුම උප වසම් සඳහා ප්‍රතිචාර නැවත ලියයි.", + "rewrite_ip_address": "අ.ජා. කෙ. ලිපිනය: මෙම අ.ජා. කෙටුම්පත A හෝ AAAA ප්‍රතිචාරයකට භාවිතා කරන්න", "rewrite_domain_name": "වසම් නාමය: අන්. නා. (CNAME) වාර්තාවක් එක් කරන්න", "disable_ipv6": "IPv6 අබල කරන්න", "disable_ipv6_desc": "මෙම අංගය සක්‍රීය කර ඇත්නම්, IPv6 ලිපින සඳහා වන සියලුම ව.නා.ප. විමසුම් (AAAA වර්ගය) අතහැර දමනු ලැබේ.", "fastest_addr": "වේගවත්ම අන්තර්ජාල කෙටුම්පත් (IP) ලිපිනය", + "fastest_addr_desc": "සියලුම ව.නා.ප. සේවාදායකයන් හරහා විමසා සියලු ප්‍රතිචාර අතරින් වේගවත්ම අ.ජා. කෙ. ලිපිනය ලබා දෙයි. සියලුම ව.නා.ප. සේවාදායකයන්ගේ ප්‍රතිචාර සඳහා අප බලා සිටිය යුතු බැවින් මෙය ව.නා.ප. විමසුම් මන්දගාමී කරන නමුත් සමස්ත සම්බන්ධතාවය වැඩි දියුණු කරයි.", "autofix_warning_text": "ඔබ \"නිරාකරණය කරන්න\" බොත්තම එබුවහොත්, ඔබගේ පද්ධතිය ඇඩ්ගාර්ඩ් හෝම් ව.නා.ප. සේවාදායකය භාවිතා කිරීමට වින්‍යාසගත කරනු ඇත.", "tags_title": "හැඳුනුම් සංකේත", "tags_desc": "අනුග්‍රාහකයට අනුරූප වන හැඳුනුම් සංකේත ඔබට තෝරා ගත හැකිය. පෙරහන් නීති වලට හැඳුනුම් සංකේත ඇතුළත් කළ හැකි අතර ඒවා වඩාත් නිවැරදිව යෙදීමට ඔබට ඉඩ සලසයි. <0>වැඩිදුර ඉගෙන ගන්න", "form_select_tags": "අනුග්‍රාහක හැඳුනුම් සංකේත", "check_title": "පෙරීම පරීක්ෂා කරන්න", + "check_desc": "ධාරක නාමය පෙරහන් කර ඇත්දැයි පරීක්ෂා කරන්න", "check": "පරීක්ෂා කරන්න", "form_enter_host": "ධාරක නාමයක් ඇතුළත් කරන්න", "filtered_custom_rules": "අභිරුචි පෙරීමේ නීති මගින් පෙරහන් කරන ලදි", @@ -456,12 +464,13 @@ "check_reason": "හේතුව: {{reason}}", "check_rule": "නීතිය: {{rule}}", "check_service": "සේවාවෙහි නම: {{service}}", + "service_name": "සේවාවේ නම", "check_not_found": "ඔබගේ පෙරහන් ලැයිස්තු තුළ සොයා ගත නොහැක", "client_confirm_block": "{{ip}} අනුග්‍රාහකය අවහිර කිරීමට අවශ්‍ය බව ඔබට විශ්වාසද?", "client_confirm_unblock": "{{ip}} අනුග්‍රාහකය අනවහිර කිරීමට අවශ්‍ය බව ඔබට විශ්වාසද?", "client_blocked": "අනුග්‍රාහකය \"{{ip}}\" සාර්ථකව අවහිර කරන ලදි", "client_unblocked": "අනුග්‍රාහකය \"{{ip}}\" සාර්ථකව අනවහිර කරන ලදි", - "static_ip": "ස්ථිතික අ.ජා. කෙ. (IP) ලිපිනය", + "static_ip": "ස්ථිතික අ.ජා. කෙ. ලිපිනය", "static_ip_desc": "ඇඩ්ගාර්ඩ් හෝම් යනු සේවාදායකයක් බැවින් එය නිසි ලෙස ක්‍රියා කිරීමට ස්ථිතික අන්තර්ජාල කෙටුම්පත් (IP) ලිපිනයක් අවශ්‍ය වේ. එසේ නොමැතිනම්, යම් අවස්ථාවක දී ඔබගේ මාර්ගකාරකය මෙම උපාංගයට වෙනත් අ.ජා. කෙ. ලිපිනයක් ලබා දිය හැකිය.", "set_static_ip": "ස්ථිතික අ.ජා. කෙ. (IP) ලිපිනයක් සකසන්න", "install_static_ok": "සුභ තොරතුරක්! ස්ථිතික අන්තර්ජාල කෙටුම්පත් (IP) ලිපිනය දැනටමත් වින්‍යාසගත කර ඇත", @@ -470,8 +479,6 @@ "confirm_static_ip": "ඇඩ්ගාර්ඩ් හෝම් ඔබේ ස්ථිතික අ.ජා. කෙ. (IP) ලිපිනය ලෙස {{ip}} වින්‍යාසගත කරනු ඇත. ඔබට ඉදිරියට යාමට අවශ්‍යද?", "list_updated": "{{count}} ලැයිස්තුව යාවත්කාලීන කරන ලදි", "list_updated_plural": "ලැයිස්තු {{count}} ක් යාවත්කාලීන කරන ලදි", - "dnssec_enable": "DNSSEC සබල කරන්න", - "validated_with_dnssec": "DNSSEC සමඟ තහවුරු කර ඇත", "all_queries": "සියලුම විමසුම්", "show_blocked_responses": "අවහිර කර ඇත", "show_whitelisted_responses": "සුදු ලැයිස්තුගත කර ඇත", @@ -484,8 +491,15 @@ "safe_search": "ආරක්ෂිත සෙවීම", "blocklist": "අවහිර කිරී‌‌‌‌‌මේ ලැයිස්තුව", "milliseconds_abbreviation": "මිලි තත්.", + "cache_size": "නිහිතයෙහි ප්‍රමාණය", + "cache_size_desc": "ව.නා.ප. නිහිතයෙහි ප්‍රමාණය (බයිට වලින්)", + "cache_ttl_min_override": "අවම පව. කා. අභිබවන්න", + "cache_ttl_max_override": "උපරිම පව. කා. අභිබවන්න", + "enter_cache_size": "ව.නා.ප. නිහිතයෙහි ප්‍රමාණය ඇතුළත් කරන්න (බයිට)", "enter_cache_ttl_min_override": "අවම පව. කා. (TTL) ඇතුළත් කරන්න", "enter_cache_ttl_max_override": "උපරිම පව. කා. (TTL) ඇතුළත් කරන්න", + "cache_ttl_max_override_desc": "ව.නා.ප. නිහිතයෙහි ඇති ඇතුළත් කිරීම් සඳහා ඉතා වැඩි පවත්නා කාලයක අගයක් (තත්පර) සකසන්න", + "ttl_cache_validation": "නිහිතයෙහි අවම පව. කා. (TTL) අගය උපරිම අගයට වඩා අඩු හෝ සමාන විය යුතුය", "filter_category_general": "පොදු", "filter_category_security": "ආරක්ෂණ", "filter_category_regional": "ප්‍රාදේශ්‍රීය", @@ -499,5 +513,6 @@ "click_to_view_queries": "විමසුම් බැලීමට ඔබන්න", "port_53_faq_link": "53 කවුළුව බොහෝ විට \"DNSStubListener\" හෝ \"systemd-resolved\" සේවාවන් භාවිතයට ගනු ලැබේ. කරුණාකර මෙය විසඳන්නේ කෙසේද යන්න පිළිබඳ <0>මෙම උපදෙස් කියවන්න.", "adg_will_drop_dns_queries": "ඇඩ්ගාර්ඩ් හෝම් විසින් මෙම අනුග්‍රාහකයේ සියලුම ව.නා.ප. විමසුම් අතහැර දමනු ඇත.", + "client_not_in_allowed_clients": "\"අවසර ලත් අනුග්‍රාහකයින්\" ලැයිස්තුවේ නොමැති නිසා අනුග්‍රාහකයට අවසර නැත.", "experimental": "පරීක්ෂණාත්මක" } \ No newline at end of file diff --git a/client/src/__locales/sk.json b/client/src/__locales/sk.json index 7c9fb056..82a6dd72 100644 --- a/client/src/__locales/sk.json +++ b/client/src/__locales/sk.json @@ -50,7 +50,7 @@ "dhcp_table_expires": "Vyprší", "dhcp_warning": "Ak chcete server DHCP napriek tomu povoliť, uistite sa, že v sieti nie je žiadny iný aktívny DHCP server. V opačnom prípade sa môže prerušiť internet pre už pripojené zariadenia!", "dhcp_error": "Nebolo možné určiť, či je v sieti iný DHCP server.", - "dhcp_static_ip_error": "Aby bolo možné používať DHCP server, musí byť nastavená statická IP adresa. Nepodarilo sa určiť, či je toto sieťové rozhranie nakonfigurované pomocou statickej adresy IP. Nastavte statickú adresu IP manuálne.", + "dhcp_static_ip_error": "Aby bolo možné používať DHCP server, musí byť nastavená statická IP adresa. Nepodarilo sa určiť, či je toto sieťové rozhranie nakonfigurované pomocou statickej adresy IP. Nastavte statickú IP adresu manuálne.", "dhcp_dynamic_ip_found": "Váš systém používa dynamickú konfiguráciu IP adresy pre rozhranie <0{{interfaceName}}. Aby bolo možné používať DHCP server, musí byť nastavená statická IP adresa. Vaša aktuálna adresa IP je <0>{{ipAddress}}. Automaticky nastavíme túto IP adresu ako statickú, ak stlačíte tlačidlo Povoliť DHCP.", "dhcp_lease_added": "Statický \"{{key}}\" prenájmu bol úspešne pridaný", "dhcp_lease_deleted": "Statický \"{{key}}\" prenájmu bol úspešne vymazaný", diff --git a/client/src/__locales/tr.json b/client/src/__locales/tr.json index e937207e..0f9b2047 100644 --- a/client/src/__locales/tr.json +++ b/client/src/__locales/tr.json @@ -2,18 +2,20 @@ "client_settings": "İstemci ayarları", "example_upstream_reserved": "<0>Belirli alan adları için DNS üst sunucusu tanımlayabilirsiniz.", "example_upstream_comment": "Bir yorum belirtebilirsiniz", - "upstream_parallel": "Tüm üst sunucuları eş zamanlı sorgulayarak çözümü hızlandırmak için paralel sorguları kullan", + "upstream_parallel": "Tüm üst sunucuları eş zamanlı sorgulayarak çözümü hızlandırmak için paralel istekleri kullan", "parallel_requests": "Paralel istekler", + "load_balancing": "Yük dengeleme", + "load_balancing_desc": "Her seferinde bir sunucuyu sorgulayın. AdGuard Home, sunucuyu seçmek için ağırlıklı rastgele algoritmayı kullanacak, böylece en hızlı sunucu daha sık kullanılacak.", "bootstrap_dns": "DNS Önyükleme sunucuları", "bootstrap_dns_desc": "DNS Önyükleme sunucuları, seçtiğiniz üst sunucuların DoH/DoT çözücülerine ait ip adreslerinin çözülmesi için kullanılır.", - "check_dhcp_servers": "DHCP sunucularını yokla", - "save_config": "Ayarları kaydet", + "check_dhcp_servers": "DHCP sunucularını denetle", + "save_config": "Yapılandırmayı kaydet", "enabled_dhcp": "DHCP sunucusu etkinleştirildi", "disabled_dhcp": "DHCP sunucusu devre dışı bırakıldı", "unavailable_dhcp": "DHCP kullanılamıyor", "unavailable_dhcp_desc": "AdGuard Home, işletim sisteminizde DHCP sunucusu çalıştıramıyor", "dhcp_title": "DHCP sunucusu (deneysel!)", - "dhcp_description": "Eğer router'ınız DHCP ayarlarını sunmuyorsa AdGuard'ın dahili DHCP sunucusunu kullanabilirsiniz.", + "dhcp_description": "Yönlendiriciniz DHCP ayarlarını sağlamıyorsa, AdGuard'ın kendi yerleşik DHCP sunucusunu kullanabilirsiniz.", "dhcp_enable": "DHCP sunucusunu etkinleştir", "dhcp_disable": "DHCP sunucusunu devre dışı bırak", "dhcp_not_found": "Yerleşik DHCP sunucusunu etkinleştirmek güvenlidir - Ağ üzerinde herhangi bir aktif DHCP sunucusu bulamadık. Ancak, otomatik testimiz şu anda %100 garanti vermediği için el ile tekrar kontrol etmenizi öneririz.", @@ -21,15 +23,15 @@ "dhcp_leases": "DHCP kiralamaları", "dhcp_static_leases": "Sabit DHCP kiralamaları", "dhcp_leases_not_found": "DHCP kiralaması bulunamadı", - "dhcp_config_saved": "DHCP sunucusu ayarı kaydedildi", + "dhcp_config_saved": "DHCP sunucusu yapılandırması kaydedildi", "dhcp_ipv4_settings": "DHCP IPv4 Ayarları", "dhcp_ipv6_settings": "DHCP IPv6 Ayarları", "form_error_required": "Gerekli alan", - "form_error_ip4_format": "Geçersiz IPv4 formatı", - "form_error_ip6_format": "Geçersiz IPv6 formatı", - "form_error_ip_format": "Geçersiz IPv4 formatı", + "form_error_ip4_format": "Geçersiz IPv4 biçimi", + "form_error_ip6_format": "Geçersiz IPv6 biçimi", + "form_error_ip_format": "Geçersiz IP biçimi", "form_error_mac_format": "Geçersiz MAC biçimi", - "form_error_client_id_format": "Geçersiz müşteri kimliği formatı", + "form_error_client_id_format": "Geçersiz istemci kimliği biçimi", "form_error_positive": "0'dan büyük olmalı", "form_error_negative": "0 veya daha büyük olmalıdır", "range_end_error": "Başlangıç aralığından daha büyük olmalı", @@ -43,10 +45,10 @@ "dhcp_interface_select": "DHCP arayüzünü seç", "dhcp_hardware_address": "Donanım adresi", "dhcp_ip_addresses": "IP adresleri", - "ip": "IP Adresi", - "dhcp_table_hostname": "Bilgisayar Adı", + "ip": "IP", + "dhcp_table_hostname": "Ana bilgisayar Adı", "dhcp_table_expires": "Geçerlilik Tarihi", - "dhcp_warning": "Dahili DHCP sunucusunu etkinleştirmek istiyorsanız başka aktif DHCP sunucusu olmadığından emin olun. Aksi takdirde cihazlar internete bağlanamayabilir.", + "dhcp_warning": "DHCP sunucusunu yine de etkinleştirmek istiyorsanız, ağınızda başka bir aktif DHCP sunucusu olmadığından emin olun. Aksi takdirde, bağlı cihazlar için interneti kırabilir!", "dhcp_error": "Ağda başka bir DHCP sunucusu olup olmadığını belirleyemedik.", "dhcp_static_ip_error": "DHCP sunucusunu kullanmak için statik bir IP adresi ayarlanmalıdır. Bu ağ arayüzünün statik IP adresi kullanılarak yapılandırılıp yapılandırılmadığını belirleyemedik. Lütfen statik bir IP adresini elle ayarlayın.", "dhcp_dynamic_ip_found": "Sisteminiz <0>{{interfaceName}} arayüzü için dinamik IP adresi yapılandırması kullanıyor. DHCP sunucusunu kullanmak için statik bir IP adresi ayarlanmalıdır. Geçerli IP adresiniz <0>{{ipAddress}}. DHCP'yi etkinleştir düğmesine basarsanız bu IP adresini statik IP adresiniz olarak ayarlayacağız.", @@ -59,8 +61,10 @@ "country": "Ülke", "city": "Şehir", "delete_confirm": "\"{{key}}\" silmek istediğinizden emin misiniz?", - "form_enter_hostname": "Cihaz ismi girin", + "form_enter_hostname": "Ana bilgisayar adı girin", "error_details": "Hata detayları", + "response_details": "Yanıt ayrıntıları", + "request_details": "İstek ayrıntıları", "client_details": "İstemci detayları", "details": "Detaylar", "back": "Geri", @@ -69,6 +73,7 @@ "filters": "Filtreler", "filter": "Filtre", "query_log": "Sorgu Günlüğü", + "compact": "Kompakt", "nothing_found": "Hiçbir şey bulunamadı", "faq": "SSS", "version": "Sürüm", @@ -76,7 +81,7 @@ "protocol": "Protokol", "on": "AÇIK", "off": "KAPALI", - "copyright": "Tüm hakları saklıdır", + "copyright": "Telif hakkı", "homepage": "Anasayfa", "report_an_issue": "Bir sorun bildir", "privacy_policy": "Gizlilik politikası", @@ -109,7 +114,7 @@ "number_of_dns_query_to_safe_search": "Güvenli Aramanın zorunlu kıldığı arama motorlarına gönderilen DNS isteklerinin sayısı", "average_processing_time": "Ortalama işlem süresi", "average_processing_time_hint": "Bir DNS isteğinin mili saniye cinsinden ortalama işlem süresi", - "block_domain_use_filters_and_hosts": "Filtreleri ve hosts listelerini kullanarak alan adlarını engelle", + "block_domain_use_filters_and_hosts": "Filtre ve ana bilgisayar listelerini kullanarak alan adlarını engelle", "filters_block_toggle_hint": "Filtreler sayfasından engelleme kurallarını ayarlayabilirsiniz.", "use_adguard_browsing_sec": "AdGuard gezinti koruması web hizmetini kullan", "use_adguard_browsing_sec_hint": "AdGuard Home, alan adının gezinti koruması web hizmetinde kara listede olup olmadığını kontrol edecek. Kontrol işlemi gizlilik dostu API kullanılarak yapılacak: yalnızca alan adının kısa bir ön eki SHA256 ile şifrelenip sunucuya gönderilecek.", @@ -121,9 +126,9 @@ "general_settings": "Genel ayarlar", "dns_settings": "DNS ayarları", "dns_blocklists": "DNS engelleme listeleri", - "dns_allowlists": "DNS izin listeleri", + "dns_allowlists": "DNS izin verilen listeleri", "dns_blocklists_desc": "AdGuard Home, engelleme listeleriyle eşleşen alanları engeller.", - "dns_allowlists_desc": "DNS izin listelerindeki alanlara, engelleme listelerinden birinde olsalar bile izin verilir.", + "dns_allowlists_desc": "DNS izin verilen listelerindeki alanlara, engelleme listelerinden birinde olsalar bile izin verilir.", "custom_filtering_rules": "Özel filtreleme kuralları", "encryption_settings": "Şifreleme ayarları", "dhcp_settings": "DHCP ayarları", @@ -131,7 +136,7 @@ "upstream_dns_help": "Her satıra bir sunucu adresi girin. Üst DNS sunucularını yapılandırma hakkında daha fazla bilgi edinin.", "upstream_dns_configured_in_file": "{{path}} içinde yapılandırıldı", "test_upstream_btn": "Üst sunucuyu test et", - "upstreams": "Upstreams", + "upstreams": "Üst kaynak", "apply_btn": "Uygula", "disabled_filtering_toast": "Filtreleme devre dışı", "enabled_filtering_toast": "Filtreleme çalışıyor", @@ -151,11 +156,11 @@ "edit_table_action": "Düzenle", "delete_table_action": "Sil", "elapsed": "Geçen zaman", - "filters_and_hosts_hint": "AdGuard Home temel reklam engelleme kurallarını ve hosts dosyalarının söz dizim kurallarını anlamaktadır.", - "no_blocklist_added": "Hiçbir engelleme listesi eklenmedi", + "filters_and_hosts_hint": "AdGuard Home temel reklam engelleme kuralları ve ana bilgisayar dosyalarının sözdizim kurallarını anlamaktadır.", + "no_blocklist_added": "Engelleme listesi eklenmedi", "no_whitelist_added": "İzin verilen listesi eklenmedi", "add_blocklist": "Engelleme listesi ekle", - "add_allowlist": "İzin listesi ekle", + "add_allowlist": "İzin verilen listesine ekle", "cancel_btn": "İptal", "enter_name_hint": "İsim girin", "enter_url_or_path_hint": "Bir URL ya da listenin tam yolunu girin", @@ -168,16 +173,16 @@ "choose_allowlist": "İzin verilen listelerini seç", "enter_valid_blocklist": "Engelleme listesine geçerli bir URL girin.", "enter_valid_allowlist": "İzin verilen listesine geçerli bir URL girin.", - "form_error_url_format": "Geçersiz url biçim", + "form_error_url_format": "Geçersiz URL biçimi", "form_error_url_or_path_format": "Geçersiz URL ya da listenin tam yolu", "custom_filter_rules": "Özel filtreleme kuralları", - "custom_filter_rules_hint": "Her satıra bir kural girin. Reklama engelleme kuralı veya hosts dosyası söz dizimi kullanabilirsiniz.", + "custom_filter_rules_hint": "Her satıra bir kural girin. Reklama engelleme kuralı veya ana bilgisayar dosyası sözdizimi kullanabilirsiniz.", "examples_title": "Örnekler", "example_meaning_filter_block": "example.org alan adına ve tüm alt alan adlarına olan erişimi engeller", "example_meaning_filter_whitelist": "example.org alan adına ve tüm alt alan adlarına olan erişim engelini kaldırır", "example_meaning_host_block": "AdGuard Home bu example.org adresi için 127.0.0.1 adresine yönlendirme yapacaktır (alt alan adları için geçerli değildir)", "example_comment": "! Buraya bir yorum ekledim", - "example_comment_meaning": "yorum eklemek", + "example_comment_meaning": "sadece bir yorum", "example_comment_hash": "# Bir yorum daha ekledim", "example_regex_meaning": "belirtilen düzenli ifadelerle eşleşen alan adlarına erişimi engelle", "example_upstream_regular": "normal DNS (UDP üzerinden)", @@ -202,6 +207,7 @@ "domain_or_client": "Alan adı veya istemci", "type_table_header": "Tür", "response_table_header": "Yanıt", + "response_code": "Yanıt kodu", "client_table_header": "İstemci", "empty_response_status": "Boş", "show_all_filter_type": "Tümünü göster", @@ -220,9 +226,10 @@ "query_log_filtered": "{{filter}} tarafından filtrelendi", "query_log_confirm_clear": "Tüm sorgu günlüğünü temizlemek istediğinizden emin misiniz?", "query_log_cleared": "Sorgu günlüğü başarıyla temizlendi", - "query_log_clear": "Sorgu kayıtlarını temizle", - "query_log_retention": "Sorgu kayıtlarının saklanması", - "query_log_enable": "Günlük kaydını etkinleştir", + "query_log_updated": "Sorgu günlüğü başarıyla güncellendi", + "query_log_clear": "Sorgu günlüklerini temizle", + "query_log_retention": "Sorgu günlüklerinin saklanması", + "query_log_enable": "Günlüğü etkinleştir", "query_log_configuration": "Günlük yapılandırması", "query_log_disabled": "Sorgu günlüğü devre dışı bırakıldı ve <0>ayarlarda yapılandırılabilir", "query_log_strict_search": "Katı arama için çift tırnak işareti kullanın", @@ -240,14 +247,16 @@ "custom_ip": "Özel IP", "blocking_ipv4": "IPv4 engelleme", "blocking_ipv6": "IPv6 engelleme", - "dns_over_https": "DNS üzerinden HTTPS", + "dns_over_https": "DNS-over-HTTPS", "dns_over_tls": "DNS-over-TLS", "download_mobileconfig_doh": "DNS-over-HTTPS için .mobileconfig dosyasını indir", "download_mobileconfig_dot": "DNS-over-TLS için .mobileconfig dosyasını indir", + "plain_dns": "Sade DNS", "form_enter_rate_limit": "Sıklık limitini girin", "rate_limit": "Sıklık limiti", "edns_enable": "EDNS İstemci Alt Ağını Etkinleştir", "edns_cs_desc": "Etkinleştirilirse, AdGuard Home, istemcilerin alt ağlarını DNS sunucularına gönderir.", + "rate_limit_desc": "Tek bir istemcinin yapmasına izin verilen saniye başına istek sayısı (0'a ayarlamak sınırsız anlamına gelir)", "blocking_ipv4_desc": "Engellenen bir A isteği için geri döndürülecek IP adresi", "blocking_ipv6_desc": "Engellenen bir AAAA isteği için geri döndürülecek IP adresi", "blocking_mode_default": "Varsayılan: Reklam engelleme stili kuralı tarafından engellendiğinde sıfır IP adresiyle (A için 0.0.0.0; AAAA için) yanıt verin; /etc/hosts-style kuralı tarafından engellendiğinde, kuralda belirtilen IP adresiyle yanıt verin", @@ -268,17 +277,17 @@ "install_welcome_desc": "AdGuard Home, ağ genelinde reklam ve izleyicileri engelleyen bir DNS sunucusudur. Tüm ağınızı ve tüm cihazlarınızı kontrol etmenize yarayan bir araçtır, istemci tarafında bir program kullanmanıza gerek duymaz.", "install_settings_title": "Yönetici Web Arayüzü", "install_settings_listen": "Dinleme arayüzü", - "install_settings_port": "Port", + "install_settings_port": "Bağlantı noktası", "install_settings_interface_link": "AdGuard Home yönetici web arayüzü sayfanız şu adresten erişilebilir olacaktır:", - "form_error_port": "Geçerli bir port değeri girin", + "form_error_port": "Geçerli bir bağlantı noktası değeri girin", "install_settings_dns": "DNS sunucusu", "install_settings_dns_desc": "Cihazlarınızı veya yönlendiricinizi şu adresteki DNS sunucusunu kullanması için ayarlamanız gerekecek:", "install_settings_all_interfaces": "Tüm arayüzler", "install_auth_title": "Kimlik Doğrulama", "install_auth_desc": "AdAdGuard Home yönetici web arayüzüne erişim için kullanıcı adı ve şifresi oluşturmanız şiddetle tavsiye edilir. Sadece yerel ağınız erişilebilir olsa bile izinsiz giriş yapılmasını engellemek için şifrenizin olması önemlidir.", "install_auth_username": "Kullanıcı adı", - "install_auth_password": "Şifre", - "install_auth_confirm": "Şifreyi onayla", + "install_auth_password": "Parola", + "install_auth_confirm": "Parolayı onayla", "install_auth_username_enter": "Kullanıcı adı girin", "install_auth_password_enter": "Şifre girin", "install_step": "Adım", @@ -289,7 +298,7 @@ "install_devices_router": "Yönlendirici", "install_devices_router_desc": "Bu kurulum evdeki yönlendiricinize bağlı tüm cihazlarınızı otomatik olarak kapsar ve her birini elle ayarlamanız gerekmez.", "install_devices_address": "AdGuard Home DNS sunucusu şu adresi dinleyecektir", - "install_devices_router_list_1": "Yönlendiricinizin ayarlarına girin. Genelde internet tarayıcınızdan bir URL vasıtasıyla erişebilirsiniz (http://192.168.0.1/ veya http://192.168.1.1/ gibi). Sizden şifre girmenizi isteyebilir. Hatırlamıyorsanız yönlendiricinizin arkasındaki 'reset' tuşuna basılı tutup fabrika ayarlarına sıfırlayabilirsiniz. Bazı yönlendiriciler belirli uygulamalarla çalışır, bu durumda bilgisayarınıza/telefonunuza kurulması gerekir.", + "install_devices_router_list_1": "Yönlendiricinizin ayarlarına girin. Genelde internet tarayıcınızdan bir URL vasıtasıyla erişebilirsiniz (http://192.168.0.1/ veya http://192.168.1.1/ gibi). Sizden parola girmenizi isteyebilir. Hatırlamıyorsanız yönlendiricinizin arkasındaki 'reset' tuşuna basılı tutup fabrika ayarlarına sıfırlayabilirsiniz. Bazı yönlendiriciler belirli uygulamalarla çalışır, bu durumda bilgisayarınıza/telefonunuza kurulması gerekir.", "install_devices_router_list_2": "DHCP/DNS ayarlarını bulun. DNS satırlarını arayın, genelde iki veya üç tanedir, üç rakam girilebilen dört ayrı grup içeren satırdır.", "install_devices_router_list_3": "AdGuard Home sunucusunun adresini o kısma yazın.", "install_devices_router_list_4": "Bazı yönlendirici tiplerinde özel bir DNS sunucusu ayarlayamazsınız. Bu durumda AdGuard Home'u bir DHCP sunucu olarak ayarlamanız yardımcı olabilir. Aksi halde, yönlendirici modeliniz için <0>DNS sunucularını elle nasıl özelleştirebileceğinizi aramalısınız.", @@ -313,7 +322,7 @@ "install_devices_ios_list_3": "Bağlı olduğunuz ağın ismine dokunun.", "install_devices_ios_list_4": "DNS alanına AdGuard Home sunucunuzun adreslerini girin.", "get_started": "Başlarken", - "next": "İleri", + "next": "Sonraki", "open_dashboard": "Panoyu Aç", "install_saved": "Başarıyla kaydedildi", "encryption_title": "Şifreleme", @@ -332,11 +341,11 @@ "encryption_doq_desc": "Bu bağlantı noktası yapılandırılırsa, AdGuard Home bu bağlantı noktasında DNS-over-QUIC sunucusu çalıştıracaktır. Deneysel ve güvenilir olmayabilir. Ayrıca, şu anda bunu destekleyen çok fazla istemci yok.", "encryption_certificates": "Sertifikalar", "encryption_certificates_desc": "Şifrelemeyi kullanmak için alan adınız için geçerli bir SSL sertifika zinciri temin etmeniz gerekir. <0>{{link}} adresinden ücretsiz temin edebilirsiniz veya güvenilir Sertifika Otoritelerinden satın alabilirsiniz.", - "encryption_certificates_input": "PEM formatındaki sertifikalarınızı buraya yapıştırın.", + "encryption_certificates_input": "PEM biçimindeki sertifikalarınızı buraya yapıştırın.", "encryption_status": "Durum", "encryption_expire": "Bitiş tarihi", "encryption_key": "Özel anahtar", - "encryption_key_input": "Sertifikanızın PEM formatı özel anahtarını buraya yapıştırın.", + "encryption_key_input": "Sertifikanızın PEM biçimi özel anahtarını buraya yapıştırın.", "encryption_enable": "Şifrelemeyi etkinleştir (HTTPS, DNS-over-HTTPS ve DNS-over-TLS)", "encryption_enable_desc": "Şifrelemeyi etkinleştirirseniz, AdGuard Home yönetici arayüzü HTTPS üzerinden çalışır ve DNS sunucusu DNS-over-HTTPS ve DNS-over-TLS üzerinden gelen istekleri dinler.", "encryption_chain_valid": "Sertifika zinciri geçerli", @@ -345,12 +354,12 @@ "encryption_key_invalid": "Bu geçersiz bir {{type}} özel anahtar", "encryption_subject": "Konu", "encryption_issuer": "Sertifikayı veren", - "encryption_hostnames": "Ana bilgisayar isimleri", + "encryption_hostnames": "Ana bilgisayar adları", "encryption_reset": "Şifreleme ayarlarını sıfırlamak istediğinize emin misiniz?", "topline_expiring_certificate": "SSL sertifikanızın süresi dolmak üzere. <0>Şifreleme ayarlarını güncelleyin.", "topline_expired_certificate": "SSL sertifikanızın süresi dolmuş. <0>Şifreleme ayarlarını güncelleyin.", - "form_error_port_range": "80-65535 aralığında geçerli bir port değeri girin.", - "form_error_port_unsafe": "Bu güvenli olmayan bir port", + "form_error_port_range": "80-65535 aralığında geçerli bir bağlantı noktası değeri girin", + "form_error_port_unsafe": "Bu güvenli olmayan bir bağlantı noktası", "form_error_equal": "Aynı olmamalı", "form_error_password": "Şifreler uyuşmuyor", "reset_settings": "Ayarları sıfırla", @@ -383,6 +392,7 @@ "form_enter_id": "Tanımlayıcı girin", "form_add_id": "Tanımlayıcı ekle", "form_client_name": "İstemci ismi girin", + "name": "İsim", "client_global_settings": "Genel ayarları kullan", "client_deleted": "\"{{key}}\" istemcisi başarıyla silindi", "client_added": "\"{{key}}\" istemcisi başarıyla eklendi", @@ -407,6 +417,8 @@ "dns_privacy": "DNS Gizliliği", "setup_dns_privacy_1": "<0>DNS-over-TLS: <1>{{address}} dizesini kullan.", "setup_dns_privacy_2": "<0>DNS-over-HTTPS: <1>{{address}} dizesini kullan.", + "setup_dns_privacy_3": "<0>İşte kullanabileceğiniz yazılımların bir listesi.", + "setup_dns_privacy_4": "Bir iOS 14 veya macOS Big Sur cihazında, DNS ayarlarına DNS-over-HTTPS veya DNS-over-TLS sunucuları ekleyen özel '.mobileconfig' dosyasını indirebilirsiniz.", "setup_dns_privacy_android_1": "Android 9 aslen DNS-over-TLS desteklemektedir. Yapılandırmak için, Ayarlar → Ağ ve internet → Gelişmiş → Özel DNS seçeneğine gidin ve alan adınızı buraya girin.", "setup_dns_privacy_android_2": "<0>AdGuard for Android, <1>DNS-over-HTTPS ve <1>DNS-over-TLS desteklemektedir.", "setup_dns_privacy_android_3": "<0>Intra Android'e <1>DNS-over-HTTPS desteğini ekler.", @@ -426,12 +438,12 @@ "rewrite_confirm_delete": "\"{{key}}\" için DNS yeniden yazımını silmek istediğinize emin misiniz?", "rewrite_desc": "Belirli bir alan adı için kolayca özel DNS yanıtı yapılandırmanıza olanak tanır.", "rewrite_applied": "Uygulanan Yeniden Yazım kuralı", - "rewrite_hosts_applied": "Host dosyası kuralı tarafından yeniden yazıldı", + "rewrite_hosts_applied": "Ana bilgisayar dosyası kuralı tarafından yeniden yazıldı", "dns_rewrites": "DNS yeniden yazımları", "form_domain": "Alan adı girin", "form_answer": "IP adresini veya alan adı girin", - "form_error_domain_format": "Geçersiz alan adı formatı", - "form_error_answer_format": "Geçersiz cevap formatı", + "form_error_domain_format": "Geçersiz alan adı biçimi", + "form_error_answer_format": "Geçersiz cevap biçimi", "configure": "Yapılandır", "main_settings": "Ana ayarlar", "block_services": "Belirli hizmetleri engelle", @@ -485,32 +497,35 @@ "network": "Ağ", "descr": "Açıklama", "whois": "Whois", - "filtering_rules_learn_more": "Ana makinelere dair kendi kara listelerinizi oluşturmakla alakalı <0>daha fazla bilgi edinin.", + "filtering_rules_learn_more": "Kendi ana bilgisayar listelerinizi oluşturma hakkında <0>daha fazla bilgi edinin.", "blocked_by_response": "Cevap olarak CNAME veya IP tarafından engellendi", "blocked_by_cname_or_ip": "CNAME veya IP tarafından engellendi", "try_again": "Tekrar deneyin", "domain_desc": "Yeniden yazılmasını istediğiniz alan adını veya joker karakteri girin.", "example_rewrite_domain": "cevapları yalnızca bu alan adı için yeniden yaz.", "example_rewrite_wildcard": "tüm <0>example.org alt alanları için cevapları yeniden yaz.", + "rewrite_ip_address": "IP adresi: bu IP'yi A veya AAAA yanıtında kullanın", "rewrite_domain_name": "Alan adı: bir CNAME kaydı ekleyin", + "rewrite_A": "<0>A: özel değer, üst sunucudan gelen <0>A kayıtlarını tut", + "rewrite_AAAA": "<0>AAA: özel değer, üst sunucudan gelen <0>AAA kayıtlarını tut", "disable_ipv6": "IPv6'yı Devre Dışı Bırak", "disable_ipv6_desc": "Bu özelliği etkinleştirirseniz, IPv6 adresleri (AAAA tipi) için gönderilen tüm DNS istekleri cevapsız bırakılacaktır.", "fastest_addr": "En hızlı IP adresi", "fastest_addr_desc": "Tüm DNS sunucularını sorgulayın ve tüm yanıtlar arasından en hızlı IP adresini döndürün. Bu, tüm DNS sunucularından yanıt beklememiz gerektiğinden DNS sorgularını yavaşlatacak ancak genel bağlantıyı iyileştirecektir.", "autofix_warning_text": "\"Düzelt\" i tıklatırsanız, AdGuardHome sisteminizi AdGuardHome DNS sunucusunu kullanacak şekilde yapılandırır.", - "autofix_warning_list": "Bu görevleri gerçekleştirecektir: <0> sistemi DNSStubListener'ı devre dışı bırakma <0> DNS sunucu adresini 127.0.0.1 olarak ayarlayın <0> /etc/resolv.conf / / run / systemd sembolik bağlantı hedefini değiştirin /resolve/resolv.conf <0> durdur DNSStubListener (sistemde yeniden çözülmüş hizmeti yeniden yükle) ", - "autofix_warning_result": "Sonuç olarak, sisteminizden gelen tüm DNS istekleri varsayılan olarak AdGuardHome tarafından işlenir.", + "autofix_warning_list": "Bu görevleri gerçekleştirecek: <0>Sistem DNSStubListener'ı devre dışı bırakın <0>DNS sunucusu adresini 127.0.0.1 olarak ayarlayın <0>/etc/resolv.conf'un sembolik bağlantı hedefini /run/systemd/resolve/resolv.conf ile değiştirin<0> <0>DNSStubListener'ı durdurun (systemd çözümlenmiş hizmeti yeniden yükleyin)", + "autofix_warning_result": "Sonuç olarak, sisteminizden gelen tüm DNS istekleri varsayılan olarak AdGuard Home tarafından işlenecektir.", "tags_title": "Etiketler", - "tags_desc": "İstemciye karşılık gelen etiketleri seçebilirsiniz. Etiketler, filtreleme kurallarına dahil edilebilir ve bunları daha doğru bir şekilde uygulamanıza olanak tanır. <0> Daha fazla bilgi edinin ", - "form_select_tags": "Müşteri etiketlerini seçin", - "check_title": "Filtrelemeyi kontrol edin", + "tags_desc": "Müşteriye karşılık gelen etiketleri seçebilirsiniz. Etiketler filtreleme kurallarına dahil edilebilir ve bunları daha doğru uygulamanıza olanak tanır. <0>Daha fazla bilgi edinin", + "form_select_tags": "İstemci etiketlerini seçin", + "check_title": "Filtrelemeyi denetleyin", "check_desc": "Ana bilgisayar adının filtrelenip filtrelenmediğini kontrol edin", - "check": "Kontrol", - "form_enter_host": "Bir ana bilgisayar adı girin", + "check": "Denetle", + "form_enter_host": "Ana bilgisayar adı girin", "filtered_custom_rules": "Özel filtreleme kurallarına göre filtrelendi", "choose_from_list": "Listeden seç", "add_custom_list": "Özel bir liste ekle", - "host_whitelisted": "Ana makine beyaz listeye alındı", + "host_whitelisted": "Ana bilgisayar beyaz listeye eklendi", "check_ip": "IP adresleri: {{ip}}", "check_cname": "CNAME: {{cname}}", "check_reason": "Sebep: {{reason}}", @@ -521,7 +536,7 @@ "client_confirm_block": "\"{{ip}}\" istemcisini engellemek istediğinizden emin misiniz?", "client_confirm_unblock": "\"{{ip}}\" istemcisinin engellemesini kaldırmak istediğinizden emin misiniz?", "client_blocked": "\"{{ip}}\" istemcisi başarıyla engellendi", - "client_unblocked": "\"{{ip}}\" müşterisinin engellemesi başarıyla kaldırıldı", + "client_unblocked": "\"{{ip}}\" istemcinin engellemesi başarıyla kaldırıldı", "static_ip": "Statik IP adres", "static_ip_desc": "AdGuard Home bir sunucudur, bu nedenle düzgün çalışması için statik bir IP adresine ihtiyaç duyar. Aksi takdirde, bir noktada yönlendiriciniz bu cihaza farklı bir IP adresi atayabilir.", "set_static_ip": "Statik IP adresi ayarlama", @@ -533,6 +548,7 @@ "list_updated_plural": "{{count}} liste güncellendi", "dnssec_enable": "DNSSEC'i etkinleştir", "dnssec_enable_desc": "DNSSEC'i giden DNS sorguları için etkinleştir ve sonucu kontrol et (DNSSEC-etkin sorgulama gerekli)", + "validated_with_dnssec": "DNSSEC ile doğrulandı", "all_queries": "Tüm sorgular", "show_blocked_responses": "Engellendi", "show_whitelisted_responses": "Beyaz listeye eklendi", @@ -542,6 +558,7 @@ "blocked_threats": "Engellenen Tehditler", "allowed": "İzin verildi", "filtered": "Filtrelenmiş", + "rewritten": "Yeniden yazılan", "safe_search": "Güvenli arama", "blocklist": "Engelleme listesi", "milliseconds_abbreviation": "ms", @@ -561,10 +578,13 @@ "filter_category_other": "Diğer", "filter_category_general_desc": "Çoğu cihazda izlemeyi ve reklamları engelleyen listeler", "filter_category_security_desc": "Kötü amaçlı yazılım, kimlik avı veya dolandırıcılık alanlarını engelleme konusunda özelleştirilmiş listeler", + "filter_category_regional_desc": "Bölgesel reklamlara ve izleme sunucularına odaklanan listeler", "filter_category_other_desc": "Diğer engelleme listeleri", "setup_config_to_enable_dhcp_server": "DHCP sunucusunu etkinleştirmek için kurulum yapılandırması", + "original_response": "Esas yanıt", "click_to_view_queries": "Sorguları görmek için tıklayın", "port_53_faq_link": "Port 53 genellikle \"DNSStubListener\" veya \"sistemd-resolved\" hizmetler tarafından kullanılır. Lütfen problemin nasıl çözüleceğine ilişkin <0>bu talimatı okuyun.", "adg_will_drop_dns_queries": "AdGuard Home, bu istemciden gelen tüm DNS sorgularını iptal eder.", + "client_not_in_allowed_clients": "İstemciye \"İzin verilen istemciler\" listesinde olmadığı için izin verilmiyor.", "experimental": "Deneysel" } \ No newline at end of file diff --git a/client/src/__locales/vi.json b/client/src/__locales/vi.json index d593c812..6e0a2faf 100644 --- a/client/src/__locales/vi.json +++ b/client/src/__locales/vi.json @@ -1,6 +1,7 @@ { "client_settings": "Cài đặt máy khách", "example_upstream_reserved": "bạn có thể chỉ định DNS ngược tuyến <0>cho một tên miền cụ thể(hoặc nhiều)", + "example_upstream_comment": "Bạn có thể thêm chú thích cụ thể", "upstream_parallel": "Sử dụng truy vấn song song để tăng tốc độ giải quyết bằng cách truy vấn đồng thời tất cả các máy chủ ngược tuyến", "parallel_requests": "Yêu cầu song song", "load_balancing": "Cân bằng tải", @@ -132,6 +133,7 @@ "encryption_settings": "Cài đặt mã hóa", "dhcp_settings": "Cài đặt DHCP", "upstream_dns": "Máy chủ DNS tìm kiếm", + "upstream_dns_configured_in_file": "Cấu hình tại {{path}}", "test_upstream_btn": "Kiểm tra", "upstreams": "Nguồn", "apply_btn": "Áp dụng", @@ -185,6 +187,7 @@ "example_upstream_regular": "DNS thông thường (dùng UDP)", "example_upstream_dot": "được mã hoá <0>DNS-over-TLS", "example_upstream_doh": "được mã hoá <0>DNS-over-HTTPS", + "example_upstream_doq": "được mã hoá <0>DNS-over-QUIC", "example_upstream_sdns": "bạn có thể sử dụng <0>DNS Stamps for <1>DNSCrypt hoặc <2>DNS-over-HTTPS ", "example_upstream_tcp": "DNS thông thường(dùng TCP)", "all_lists_up_to_date_toast": "Tất cả danh sách đã ở phiên bản mới nhất", @@ -193,6 +196,7 @@ "dns_test_not_ok_toast": "Máy chủ \"\"': không thể sử dụng, vui lòng kiểm tra lại", "unblock": "Bỏ chặn", "block": "Chặn", + "disallow_this_client": "Không cho phép client này", "time_table_header": "Thời gian", "date": "Ngày", "domain_name_table_header": "Tên miền", @@ -234,6 +238,7 @@ "blocking_mode": "Chế độ chặn", "default": "Mặc định", "nxdomain": "NXDOMAIN", + "refused": "REFUSED", "null_ip": "Địa chỉ IP rỗng", "custom_ip": "IP tuỳ chỉnh", "blocking_ipv4": "Chặn IPv4", @@ -245,8 +250,10 @@ "rate_limit": "Giới hạn yêu cầu", "edns_enable": "Bật mạng con EDNS Client", "edns_cs_desc": "Nếu được bật, AdGuard Home sẽ gửi các mạng con của khách hàng đến các máy chủ DNS.", + "rate_limit_desc": "Số lượng yêu cầu mỗi giây mà một khách hàng được phép thực hiện (0: không giới hạn)", "blocking_ipv4_desc": "Địa chỉ IP được trả lại cho một yêu cầu A bị chặn", "blocking_ipv6_desc": "Địa chỉ IP được trả lại cho một yêu cầu AAA bị chặn", + "blocking_mode_default": "Mặc định: Trả lời với NXDOMAIN khi bị chặn bởi quy tắc kiểu Adblock; phản hồi với địa chỉ IP được chỉ định trong quy tắc khi bị chặn bởi quy tắc / etc / hosts-style", "blocking_mode_nxdomain": "NXDOMAIN: Phản hổi với mã NXDOMAIN", "blocking_mode_null_ip": "Null IP: Trả lời bằng không địa chỉ IP (0.0.0.0 cho A; :: cho AAAA)", "blocking_mode_custom_ip": "IP tùy chỉnh: Phản hồi với địa chỉ IP đã được tiết lập", @@ -401,6 +408,7 @@ "dns_privacy": "DNS Riêng Tư", "setup_dns_privacy_1": "<0>DNS-over-TLS: Sử dụng chuỗi <1>{{address}}.", "setup_dns_privacy_2": "<0>DNS-over-HTTPS: Sử dụng chuỗi <1>{{address}}.", + "setup_dns_privacy_4": "Trên thiết bị chạy iOS 14 hoặc macOS Big Sur bạn có thể tải tệp '.mobileconfig' đặc biệt có chứa máy chủ DNS-over-HTTPS hoặc DNS-over-TLS trong thiết lập DNS.", "setup_dns_privacy_android_1": "Android 9 hỗ trợ DNS trên TLS nguyên bản. Để định cấu hình, hãy đi tới Cài đặt → Mạng & internet → Nâng cao → DNS Riêng Tư và nhập tên miền của bạn vào đó.", "setup_dns_privacy_android_2": "<0>AdGuard for Android hỗ trợ <1>DNS-over-HTTPS và <1>DNS-over-TLS.", "setup_dns_privacy_android_3": "<0>Intra thêm <1>DNS-over-HTTPS hỗ trợ cho Android.", @@ -545,6 +553,8 @@ "milliseconds_abbreviation": "ms", "cache_size": "Kích thước cache", "cache_size_desc": "Kích thước cache DNS (bytes)", + "cache_ttl_min_override": "Ghi đè TTL tối thiểu", + "cache_ttl_max_override": "Ghi đè TTL tối đa", "filter_category_general": "Chung", "filter_category_security": "Bảo mật", "filter_category_regional": "Khu vực", diff --git a/client/src/__locales/zh-cn.json b/client/src/__locales/zh-cn.json index 37095a3c..41199e56 100644 --- a/client/src/__locales/zh-cn.json +++ b/client/src/__locales/zh-cn.json @@ -1,6 +1,6 @@ { "client_settings": "客户端设置", - "example_upstream_reserved": "您可以将上游DNS 服务器<0>指定为特定域名", + "example_upstream_reserved": "您可以<0>为特定域名指定上游 DNS 服务器", "example_upstream_comment": "您可以指定注解", "upstream_parallel": "通过同时查询所有上游服务器,使用并行请求以加速解析", "parallel_requests": "并行请求", diff --git a/client/src/__locales/zh-hk.json b/client/src/__locales/zh-hk.json index 4247df2a..044980c1 100644 --- a/client/src/__locales/zh-hk.json +++ b/client/src/__locales/zh-hk.json @@ -2,12 +2,12 @@ "client_settings": "用戶端設定", "example_upstream_reserved": "您可以<0>指定網域使用特定 DNS 查詢", "example_upstream_comment": "您可以指定註解", - "upstream_parallel": "使用平行查詢,同時查詢所有上游伺服器來來加速解析結果", + "upstream_parallel": "使用平行查詢,同時查詢所有上游伺服器來加速解析結果", "parallel_requests": "平行處理", - "load_balancing": "負載平衝", + "load_balancing": "負載平衡", "load_balancing_desc": "一次只查詢一個伺服器。AdGuard Home 會使用加權隨機取樣來選擇使用的查詢結果,以確保速度最快的伺服器能被充分運用。", "bootstrap_dns": "引導(Boostrap) DNS 伺服器", - "bootstrap_dns_desc": "引導(Bootstrap)DNS 伺服器用來解析 DoH/DoT 的域名 IP。", + "bootstrap_dns_desc": "Bootstrap DNS 伺服器用於解析您所設定的上游 DoH/DoT 解析器的 IP 地址", "check_dhcp_servers": "檢查 DHCP 伺服器", "save_config": "儲存設定", "enabled_dhcp": "DHCP 伺服器已啟動", @@ -117,9 +117,9 @@ "block_domain_use_filters_and_hosts": "使用過濾器與 hosts 檔案阻擋網域查詢", "filters_block_toggle_hint": "您可在過濾器設定中設定封鎖規則。", "use_adguard_browsing_sec": "使用 AdGuard 瀏覽安全網路服務", - "use_adguard_browsing_sec_hint": "AdGuard Home 將檢查查詢網域是否在瀏覽安全服務黑名單內。它使用尊重個人隱私的 API 來進行檢查:會先透過 SHA256 將網域編碼後取簡短前置字串傳送到伺服器核對。", + "use_adguard_browsing_sec_hint": "AdGuard Home 將比對查詢網域是否在瀏覽安全服務黑名單內。AdGuard Home 選擇使用尊重個人隱私的 API 進行比對,先透過 SHA256 將網域編碼,取前置字串傳送到伺服器進行比對。", "use_adguard_parental": "使用 AdGuard 家長監護功能", - "use_adguard_parental_hint": "AdGuard Home 將檢查查詢網域是否含有成人內容。它使用與 AdGuard 瀏覽安全一樣的尊重個人隱私的 API 來進行檢查。", + "use_adguard_parental_hint": "AdGuard Home 將比對查詢網域是否含有成人內容。它使用與 AdGuard 瀏覽安全一樣的尊重個人隱私的 API 來進行檢查。", "enforce_safe_search": "強制使用安全搜尋", "enforce_save_search_hint": "AdGuard Home 可在下列搜尋引擎使用強制安全搜尋:Google、YouTube、Bing、DuckDuckGo 和 Yandex。", "no_servers_specified": "沒有指定的伺服器", @@ -133,8 +133,8 @@ "encryption_settings": "加密設定", "dhcp_settings": "DHCP 設定", "upstream_dns": "上游 DNS 伺服器", - "upstream_dns_help": "每行輸入一個伺服器位址。了解更多有關配置上遊 DNS 伺服器的內容", - "upstream_dns_configured_in_file": "被配置在 {{path}}", + "upstream_dns_help": "每行輸入一個伺服器位址。了解更多有關設定上游 DNS 伺服器的內容", + "upstream_dns_configured_in_file": "設定在 {{path}}", "test_upstream_btn": "測試上游 DNS", "upstreams": "上游", "apply_btn": "套用", @@ -188,19 +188,19 @@ "example_upstream_regular": "一般 DNS(透過 UDP)", "example_upstream_dot": "<0>DNS-over-TLS(流量加密)", "example_upstream_doh": "<0>DNS-over-HTTPS(流量加密)", - "example_upstream_doq": "加密的<0>DNS-over-QUIC", + "example_upstream_doq": "加密 <0>DNS-over-QUIC", "example_upstream_sdns": "您可以使透過 <0>DNS Stamps 來解析 <1>DNSCrypt 或 <2>DNS-over-HTTPS", "example_upstream_tcp": "一般 DNS(透過 TCP)", - "all_lists_up_to_date_toast": "所有清單已經是最新的", + "all_lists_up_to_date_toast": "所有清單已更新至最新", "updated_upstream_dns_toast": "已更新上游 DNS 伺服器", "dns_test_ok_toast": "設定中的 DNS 上游運作正常", - "dns_test_not_ok_toast": "設定中的 \"{{key}}\" DNS 出現錯誤,請檢察拼字", + "dns_test_not_ok_toast": "DNS 設定中的 \"{{key}}\" 出現錯誤,請確認是否正確輸入", "unblock": "解除封鎖", "block": "封鎖", "disallow_this_client": "不允許此用戶端", "allow_this_client": "允許此用戶端", - "block_for_this_client_only": "僅為此用戶端封鎖", - "unblock_for_this_client_only": "僅為此用戶端解除封鎖", + "block_for_this_client_only": "僅封鎖此用戶端", + "unblock_for_this_client_only": "僅解除封鎖此用戶端", "time_table_header": "時間", "date": "日期", "domain_name_table_header": "域名", @@ -254,7 +254,7 @@ "plain_dns": "一般未加密 DNS", "form_enter_rate_limit": "輸入速率限制", "rate_limit": "速率限制", - "edns_enable": "編輯 EDNS 用戶端子網路", + "edns_enable": "啟用 EDNS Client Subnet", "edns_cs_desc": "開啟後 AdGuard Home 將會傳送用戶端的子網路給 DNS 伺服器。", "rate_limit_desc": "限制單一裝置每秒發出的查詢次數(設定為 0 即表示無限制)", "blocking_ipv4_desc": "回覆指定 IPv4 位址給被封鎖的網域的 A 紀錄查詢", @@ -331,14 +331,14 @@ "encryption_server": "伺服器名稱", "encryption_server_enter": "輸入您的網域名稱", "encryption_server_desc": "要使用 HTTPS,您必須輸入與您 SSL 憑證相符的伺服器名稱。", - "encryption_redirect": "自重新導向到 HTTPS", + "encryption_redirect": "自動重新導向到 HTTPS", "encryption_redirect_desc": "如果啟用,AdGuard Home 將會自動導向 HTTP 到 HTTPS。", "encryption_https": "HTTPS 連接埠", "encryption_https_desc": "如果已設定 HTTPS,AdGuard Home 網頁管理介面將會使用 HTTPS 來存取,且「/dns-query」也提供 DNS-over-HTTPS 查詢。", "encryption_dot": "DNS-over-TLS 連接埠", "encryption_dot_desc": "如果已設定此連接埠,AdGuard Home 將啟動 DNS-over-TLS 伺服器來監聽請求。", - "encryption_doq": "DNS-over-QUIC端口", - "encryption_doq_desc": "如果此端口被配置了, AdGuard Home將會在此端口運行DNS-over-QUIC服務. 這目前還是實驗性的功能,可能不可靠. 另外,目前還沒有大量支持它的客戶端", + "encryption_doq": "DNS-over-QUIC 連接埠", + "encryption_doq_desc": "若設定此連接埠,AdGuard Home 將在此連接埠上運行 DNS-over-QUIC 服務。此功能還是實驗性功能,可能並不可靠。此外目前還沒有太多客戶端支援。", "encryption_certificates": "憑證", "encryption_certificates_desc": "要使用加密連線,必須擁有一個有效的 SSL 憑證對應您的網域。您可以從<0>{{link}}取得免費的 SSL 憑證或從受信任的 SSL 憑證簽發機構購買。", "encryption_certificates_input": "在這裡複製/貼上您的 PEM 憑證。", @@ -370,7 +370,7 @@ "dns_status_error": "檢查 DNS 伺服器狀態錯誤", "down": "離線", "fix": "修正", - "dns_providers": "下列是可以使用的<0>軟體清單。", + "dns_providers": "下列為常見的<0> DNS 伺服器。", "update_now": "立即更新", "update_failed": "自動更新發生錯誤。請嘗試依照以下步驟 來手動更新。", "processing_update": "請稍候,AdGuard Home 正在更新", @@ -393,7 +393,7 @@ "form_add_id": "新增識別碼", "form_client_name": "輸入用戶端名稱", "name": "名稱", - "client_global_settings": "Use global settings", + "client_global_settings": "使用全域設定", "client_deleted": "已刪除「{{key}}」", "client_added": "已新增「{{key}}」", "client_updated": "已更新「{{key}}」", @@ -418,13 +418,13 @@ "setup_dns_privacy_1": "<0>DNS-over-TLS:使用 <1>{{address}}。", "setup_dns_privacy_2": "<0>DNS-over-HTTPS:使用 <1>{{address}}。", "setup_dns_privacy_3": "<0>以下是您可以使用軟體的列表", - "setup_dns_privacy_4": "在 iOS 14 或 macOS Big Sur 裝置上,您可以下載特定的 '.mobileconfig' 檔案。此檔案將DNS-over-HTTPSDNS-over-TLS 伺服器添加至 DNS 設定。", - "setup_dns_privacy_android_1": "Android 9 原生支援 DNS-over-TLS。前網「設定」→「網路 & 網際網路」→「進階」→「私人 DNS」設定。", + "setup_dns_privacy_4": "在 iOS 14 或 macOS Big Sur 裝置上,您可以下載特定的 '.mobileconfig' 檔案。此檔案將DNS-over-HTTPSDNS-over-TLS 伺服器新增至 DNS 設定。", + "setup_dns_privacy_android_1": "Android 9 原生支援 DNS-over-TLS。前往「設定」→「網路 & 網際網路」→「進階」→「私人 DNS」設定。", "setup_dns_privacy_android_2": "<0>AdGuard for Android 支援 <1>DNS-over-HTTPS 與 <1>DNS-over-TLS。", "setup_dns_privacy_android_3": "<0>Intra 對 Android 新增支援 <1>DNS-over-HTTPS。", "setup_dns_privacy_ios_1": "<0>DNSCloak 支援 <1>DNS-over-HTTPS,若要使用您必須先產生 <2>DNS Stamp。", "setup_dns_privacy_ios_2": "<0>AdGuard for iOS 支援 <1>DNS-over-HTTPS 與 <1>DNS-over-TLS 設定。", - "setup_dns_privacy_other_title": "其他實作軟體", + "setup_dns_privacy_other_title": "其他實作方式", "setup_dns_privacy_other_1": "AdGuard Home 本身在任何平台都是安全的 DNS 用戶端。", "setup_dns_privacy_other_2": "<0>dnsproxy 支援所有加密 DNS 協定。", "setup_dns_privacy_other_3": "<0>dnscrypt-proxy 支援 <1>DNS-over-HTTPS。", @@ -440,7 +440,7 @@ "rewrite_applied": "已套用 DNS 覆寫規則", "rewrite_hosts_applied": "由「hosts 檔案」覆寫", "dns_rewrites": "DNS 覆寫", - "form_domain": "輸入網域名稱或使用萬用字元。", + "form_domain": "輸入網域名稱或使用 wildcard 字元。", "form_answer": "輸入 IP 或網域名稱", "form_error_domain_format": "網域格式無效", "form_error_answer_format": "回應格式無效", @@ -497,11 +497,11 @@ "network": "網路", "descr": "描述", "whois": "Whois", - "filtering_rules_learn_more": "<0>進一步了解關於創建自己的「hosts 檔案」", + "filtering_rules_learn_more": "<0>進一步了解如何創建自己的「hosts 檔案」", "blocked_by_response": "回應時被 CNAME 或 IP 封鎖", "blocked_by_cname_or_ip": "使用 CNAME 或 IP 封鎖", "try_again": "再試一次", - "domain_desc": "輸入您想要覆寫的網域或萬用字元。", + "domain_desc": "輸入您想要覆寫的網域或 wildcard 字元。", "example_rewrite_domain": "DNS 覆寫只套用在這個域名。", "example_rewrite_wildcard": "DNS 覆寫會套用在 <0>example.org 及所有子域名。", "rewrite_ip_address": "IP 位址:使用 A 或 AAAA 紀錄回應", @@ -509,20 +509,20 @@ "rewrite_A": "<0>A: 特殊值,將上游查詢結果覆寫 <0>A 紀錄", "rewrite_AAAA": "<0>AAAA: 特殊值,將上游查詢結果覆寫 <0>AAAA 紀錄", "disable_ipv6": "停用 IPv6", - "disable_ipv6_desc": "開啟此功能後所有,所有對於 IPv6 位址(AAAA)的查詢都會被捨棄。", + "disable_ipv6_desc": "開啟此功能後,將捨棄所有對 IPv6 位址(AAAA)的查詢。", "fastest_addr": "Fastest IP 位址", - "fastest_addr_desc": "從所有 DNS 伺服器查詢中回應最快的 IP 位址", + "fastest_addr_desc": "從所有 DNS 伺服器查詢中回應最快的 IP 位址。但這操作會等待所有 DNS 查詢結果後才能回應,導致速度有所降低,不過同時卻也改善了整體連線品質。", "autofix_warning_text": "如果您點擊「修復」,AdGuard Home 將更改您的系統 DNS 設定更改為 AdGuard Home DNS 伺服器", "autofix_warning_list": "它將執行這些任務:<0>停用系統 DNSStubListener <0>將 DNS 設定為 127.0.0.1 <0>更換軟連結將 /etc/resolv.conf 為 /run/systemd/resolve/resolv.conf <0>停止 DNSStubListener(重新載入 systemd-resolved)", "autofix_warning_result": "就結論來說 DNS 請求預設由本機的 AdGuard Home 處理。", "tags_title": "標籤", - "tags_desc": "您可以選擇與用戶端相對應的標籤。標籤可已包含在過濾規則內且使用上更精確。\n<0>進一步了解", + "tags_desc": "可在此指定用戶端的標籤。標籤可包含在過濾規則內,並且在指定上更為精確。\n<0>進一步了解", "form_select_tags": "選擇用戶端標籤", "check_title": "過濾檢查", "check_desc": "檢查網域是否被封鎖", "check": "檢查", "form_enter_host": "輸入網域", - "filtered_custom_rules": "被自訂過濾規則封鎖", + "filtered_custom_rules": "已套用自訂規則", "choose_from_list": "從清單中選取", "add_custom_list": "新增自訂清單", "host_whitelisted": "主機已列入白名單", @@ -569,7 +569,7 @@ "enter_cache_size": "輸入快取大小(bytes)", "enter_cache_ttl_min_override": "輸入最小 TTL 值(秒)", "enter_cache_ttl_max_override": "輸入最大 TTL 值(秒)", - "cache_ttl_min_override_desc": "快取 DNS 回應時,延長從上遊伺服器收到的 TTL 值 (秒)", + "cache_ttl_min_override_desc": "快取 DNS 回應時,延長從上游伺服器收到的 TTL 值 (秒)", "cache_ttl_max_override_desc": "設定 DNS 快取條目的最大 TTL 值(秒)", "ttl_cache_validation": "最小快取 TTL 值必須小於或等於最大值", "filter_category_general": "一般", @@ -584,7 +584,7 @@ "original_response": "原始回應", "click_to_view_queries": "按一下以檢視查詢結果", "port_53_faq_link": "連接埠 53 經常被「DNSStubListener」或「systemd-resolved」服務佔用。請閱讀下列有關解決<0>這個問題的說明", - "adg_will_drop_dns_queries": "AdGuard Home 將要終止所有來自此用戶端的 DNS 查詢。", + "adg_will_drop_dns_queries": "AdGuard Home 將停止回應此用戶端的所有 DNS 查詢。", "client_not_in_allowed_clients": "此用戶端不被允許,它不在\"允許的用戶端\"列表中。", "experimental": "實驗性" } \ No newline at end of file diff --git a/client/src/__locales/zh-tw.json b/client/src/__locales/zh-tw.json index 8d0f198f..fb3ed64a 100644 --- a/client/src/__locales/zh-tw.json +++ b/client/src/__locales/zh-tw.json @@ -1,6 +1,6 @@ { "client_settings": "用戶端設定", - "example_upstream_reserved": "您可明確指定<0>用於特定的網域之 DNS 上游", + "example_upstream_reserved": "您可以<0>為特定網域指定上游 DNS", "example_upstream_comment": "您可明確指定註解", "upstream_parallel": "透過同時地查詢所有上游的伺服器,使用並行的查詢以加速解析網域", "parallel_requests": "並行的請求", @@ -74,7 +74,7 @@ "filter": "過濾器", "query_log": "查詢記錄", "compact": "精簡的", - "nothing_found": "無什麼被找到", + "nothing_found": "沒找到什麼", "faq": "常見問答集", "version": "版本", "address": "位址", @@ -259,7 +259,7 @@ "rate_limit_desc": "單一的用戶端被允許傳送的每秒請求之數量(設定它為 0 表示無限制的)", "blocking_ipv4_desc": "要被返回給已封鎖的 A 請求之 IP 位址", "blocking_ipv6_desc": "要被返回給已封鎖的 AAAA 請求之 IP 位址", - "blocking_mode_default": "預設:當被廣告封鎖樣式的規則封鎖時,以零值 IP 位址(0.0.0.0 供 A;:: 供 AAAA)回覆;當被 /etc/hosts 樣式的規則封鎖時,以在該規則中之已明確指定的 IP 位址回覆", + "blocking_mode_default": "預設:當被 AdBlock 樣式的規則封鎖時,以零值 IP 位址(0.0.0.0 供 A;:: 供 AAAA)回覆;當被 /etc/hosts 樣式的規則封鎖時,以在該規則中之已明確指定的 IP 位址回覆", "blocking_mode_refused": "已拒絕(REFUSED):以 REFUSED 碼回覆", "blocking_mode_nxdomain": "不存在的網域(NXDOMAIN):以 NXDOMAIN 碼回覆", "blocking_mode_null_ip": "無效的 IP:以零值 IP 位址(0.0.0.0 供 A;:: 供 AAAA)回覆", @@ -284,7 +284,7 @@ "install_settings_dns_desc": "您將需要配置您的裝置或路由器以使用於下列的位址上之 DNS 伺服器:", "install_settings_all_interfaces": "所有的介面", "install_auth_title": "驗證", - "install_auth_desc": "配置屬於您的 AdGuard Home 管理員網路介面之密碼驗證是被非常建議的。即使它僅在您的區域網路中為可存取的,保護它免於不受限制的存取為仍然重要的。", + "install_auth_desc": "我們強烈建議您設定 AdGuard Home 管理員網路介面密碼。即使它僅能在您的區域網路中使用,保護它免於不受限制的存取仍然很重要。", "install_auth_username": "使用者名稱", "install_auth_password": "密碼", "install_auth_confirm": "確認密碼", @@ -585,6 +585,6 @@ "click_to_view_queries": "點擊以檢視查詢", "port_53_faq_link": "連接埠 53 常被 \"DNSStubListener\" 或 \"systemd-resolved\" 服務佔用。請閱讀有關如何解決這個的<0>用法說明。", "adg_will_drop_dns_queries": "AdGuard Home 將持續排除來自此用戶端之所有的 DNS 查詢。", - "client_not_in_allowed_clients": "因為該用戶端不在\"已允許的用戶端\"清單中,它未被允許。", + "client_not_in_allowed_clients": "該用戶端未被允許,因為它不在\"已允許的用戶端\"清單中。", "experimental": "實驗性的" } \ No newline at end of file diff --git a/scripts/translations/package-lock.json b/scripts/translations/package-lock.json index 5361d859..cd75b683 100644 --- a/scripts/translations/package-lock.json +++ b/scripts/translations/package-lock.json @@ -1,6 +1,6 @@ { "name": "translations", - "version": "0.1.0", + "version": "0.2.0", "lockfileVersion": 1, "requires": true, "dependencies": { From aff936141c43b262e71b250520b5237984b105f0 Mon Sep 17 00:00:00 2001 From: Andrey Meshkov Date: Sat, 6 Feb 2021 02:05:29 +0300 Subject: [PATCH 100/141] Updated Projects that use AdGuard Home section --- README.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 5402dc4c..31a81c1f 100644 --- a/README.md +++ b/README.md @@ -310,12 +310,13 @@ Here's what you can also do to contribute: ## Projects that use AdGuard Home -* Python library (https://github.com/frenck/python-adguardhome) -* Hass.io add-on (https://github.com/hassio-addons/addon-adguard-home) -* OpenWrt LUCI app (https://github.com/kongfl888/luci-app-adguardhome) -* Prometheus exporter for AdGuard Home (https://github.com/ebrianne/adguard-exporter) -* Cloudron app (https://git.cloudron.io/cloudron/adguard-home-app) -* AdGuard Home on GLInet routers (https://forum.gl-inet.com/t/adguardhome-on-gl-routers/10664) +* [AdGuard Home Remote](https://apps.apple.com/app/apple-store/id1543143740) - iOS app by [Joost](https://rocketscience-it.nl/) +* [Python library](https://github.com/frenck/python-adguardhome) by @frenck +* [Home Assistant add-on](https://github.com/hassio-addons/addon-adguard-home) by @frenck +* [OpenWrt LUCI app](https://github.com/kongfl888/luci-app-adguardhome) by @kongfl888 (originally by @rufengsuixing) +* [Prometheus exporter for AdGuard Home](https://github.com/ebrianne/adguard-exporter) by @ebrianne +* [AdGuard Home on GLInet routers](https://forum.gl-inet.com/t/adguardhome-on-gl-routers/10664) by [Gl-Inet](https://gl-inet.com/) +* [Cloudron app](https://git.cloudron.io/cloudron/adguard-home-app) by @gramakri ## Acknowledgments From 00700c13e475fde1e0f0d31ac48d9263c5187d3e Mon Sep 17 00:00:00 2001 From: Andrey Meshkov Date: Sat, 6 Feb 2021 02:07:33 +0300 Subject: [PATCH 101/141] Updated links to projects authors --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 31a81c1f..519119e9 100644 --- a/README.md +++ b/README.md @@ -311,12 +311,12 @@ Here's what you can also do to contribute: ## Projects that use AdGuard Home * [AdGuard Home Remote](https://apps.apple.com/app/apple-store/id1543143740) - iOS app by [Joost](https://rocketscience-it.nl/) -* [Python library](https://github.com/frenck/python-adguardhome) by @frenck -* [Home Assistant add-on](https://github.com/hassio-addons/addon-adguard-home) by @frenck -* [OpenWrt LUCI app](https://github.com/kongfl888/luci-app-adguardhome) by @kongfl888 (originally by @rufengsuixing) -* [Prometheus exporter for AdGuard Home](https://github.com/ebrianne/adguard-exporter) by @ebrianne +* [Python library](https://github.com/frenck/python-adguardhome) by [@frenck](https://github.com/frenck) +* [Home Assistant add-on](https://github.com/hassio-addons/addon-adguard-home) by [@frenck](https://github.com/frenck) +* [OpenWrt LUCI app](https://github.com/kongfl888/luci-app-adguardhome) by [@kongfl888](https://github.com/kongfl888) (originally by [@rufengsuixing](https://github.com/rufengsuixing)) +* [Prometheus exporter for AdGuard Home](https://github.com/ebrianne/adguard-exporter) by [@ebrianne](https://github.com/ebrianne) * [AdGuard Home on GLInet routers](https://forum.gl-inet.com/t/adguardhome-on-gl-routers/10664) by [Gl-Inet](https://gl-inet.com/) -* [Cloudron app](https://git.cloudron.io/cloudron/adguard-home-app) by @gramakri +* [Cloudron app](https://git.cloudron.io/cloudron/adguard-home-app) by [@gramakri](https://github.com/gramakri) ## Acknowledgments From 55bde69691ec3d63abac1f41828d58f90ccbf583 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Mon, 8 Feb 2021 12:58:35 +0300 Subject: [PATCH 102/141] Pull request: all: fix gh cache action version Merge in DNS/adguard-home from fix-cache-action to master Squashed commit of the following: commit d0bd209566e8385813a2a233b453f25495752398 Merge: 38761671 00700c13 Author: Ainar Garipov Date: Mon Feb 8 12:47:59 2021 +0300 Merge branch 'master' into fix-cache-action commit 3876167155d6100342b9bf020663ebb5fd90b7de Author: Ainar Garipov Date: Fri Feb 5 20:36:33 2021 +0300 all: don't build frontend twice commit f3a628dbafb5e8c0375e5c7b19e72a7c4d136f44 Author: Ainar Garipov Date: Fri Feb 5 20:23:14 2021 +0300 all: more fix commit 89ff42a271dc49d2c4de88db9ba39105558e1468 Author: Ainar Garipov Date: Fri Feb 5 20:17:55 2021 +0300 all: fix gh cache action version --- .github/workflows/build.yml | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0bf8807b..4df5b455 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -39,7 +39,9 @@ 'with': 'node-version': '${{ env.NODE_VERSION }}' - 'name': 'Set up Go modules cache' - 'uses': 'actions/cache@v2' + # TODO(a.garipov): Update when they fix the macOS issue. The issue is + # most probably https://github.com/actions/cache/issues/527. + 'uses': 'actions/cache@v2.1.3' 'with': 'path': '~/go/pkg/mod' 'key': "${{ runner.os }}-go-${{ hashFiles('go.sum') }}" @@ -48,7 +50,9 @@ 'id': 'npm-cache' 'run': 'echo "::set-output name=dir::$(npm config get cache)"' - 'name': 'Set up npm cache' - 'uses': 'actions/cache@v2' + # TODO(a.garipov): Update when they fix the macOS issue. The issue is + # most probably https://github.com/actions/cache/issues/527. + 'uses': 'actions/cache@v2.1.3' 'with': 'path': '${{ steps.npm-cache.outputs.dir }}' 'key': "${{ runner.os }}-node-${{ hashFiles('client/package-lock.json') }}" @@ -79,7 +83,9 @@ 'with': 'node-version': '${{ env.NODE_VERSION }}' - 'name': 'Set up Go modules cache' - 'uses': 'actions/cache@v2' + # TODO(a.garipov): Update when they fix the macOS issue. The issue is + # most probably https://github.com/actions/cache/issues/527. + 'uses': 'actions/cache@v2.1.3' 'with': 'path': '~/go/pkg/mod' 'key': "${{ runner.os }}-go-${{ hashFiles('go.sum') }}" @@ -87,8 +93,10 @@ - 'name': 'Get npm cache directory' 'id': 'npm-cache' 'run': 'echo "::set-output name=dir::$(npm config get cache)"' - - 'name': 'Set up node_modules cache' - 'uses': 'actions/cache@v2' + - 'name': 'Set up npm cache' + # TODO(a.garipov): Update when they fix the macOS issue. The issue is + # most probably https://github.com/actions/cache/issues/527. + 'uses': 'actions/cache@v2.1.3' 'with': 'path': '${{ steps.npm-cache.outputs.dir }}' 'key': "${{ runner.os }}-node-${{ hashFiles('client/package-lock.json') }}" @@ -100,7 +108,7 @@ - 'name': 'Set up Docker Buildx' 'uses': 'docker/setup-buildx-action@v1' - 'name': 'Run snapshot build' - 'run': 'make SIGN=0 VERBOSE=1 js-deps js-build build-release build-docker' + 'run': 'make SIGN=0 VERBOSE=1 build-release build-docker' 'notify': 'needs': From 63e4adc0e789b263937ead0406b80957555194e9 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Tue, 9 Feb 2021 13:17:19 +0300 Subject: [PATCH 103/141] Pull request: all: upd urlfilter Merge in DNS/adguard-home from upd-urlfilter to master Squashed commit of the following: commit 055cf6721f1412e8c63433bdcd7baf29f9afd699 Author: Ainar Garipov Date: Tue Feb 9 13:03:30 2021 +0300 all: upd urlfilter --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index aea6a6ee..b8d18f43 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.14 require ( github.com/AdguardTeam/dnsproxy v0.33.9 github.com/AdguardTeam/golibs v0.4.4 - github.com/AdguardTeam/urlfilter v0.14.2 + github.com/AdguardTeam/urlfilter v0.14.3 github.com/NYTimes/gziphandler v1.1.1 github.com/ameshkov/dnscrypt/v2 v2.0.1 github.com/digineo/go-ipset/v2 v2.2.1 diff --git a/go.sum b/go.sum index 995400c9..4afc5ca7 100644 --- a/go.sum +++ b/go.sum @@ -26,8 +26,8 @@ github.com/AdguardTeam/golibs v0.4.2/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKU github.com/AdguardTeam/golibs v0.4.4 h1:cM9UySQiYFW79zo5XRwnaIWVzfW4eNXmZktMrWbthpw= github.com/AdguardTeam/golibs v0.4.4/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4= github.com/AdguardTeam/gomitmproxy v0.2.0/go.mod h1:Qdv0Mktnzer5zpdpi5rAwixNJzW2FN91LjKJCkVbYGU= -github.com/AdguardTeam/urlfilter v0.14.2 h1:k26vEYz0mT/liDGZ0JGIBLYLMHaisIGX1UR0qaVnO4k= -github.com/AdguardTeam/urlfilter v0.14.2/go.mod h1:klx4JbOfc4EaNb5lWLqOwfg+pVcyRukmoJRvO55lL5U= +github.com/AdguardTeam/urlfilter v0.14.3 h1:MBaLEXS0LRQNbHtLkDCYhHINDPtkevPrYWGiOUuLJU4= +github.com/AdguardTeam/urlfilter v0.14.3/go.mod h1:klx4JbOfc4EaNb5lWLqOwfg+pVcyRukmoJRvO55lL5U= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= From 1fa4d55ae3210baaec81811ac7d4abe5f0ec8c9a Mon Sep 17 00:00:00 2001 From: Eugene Burkov Date: Tue, 9 Feb 2021 15:17:02 +0300 Subject: [PATCH 104/141] Pull request: 2639 use testify require vol.1 Merge in DNS/adguard-home from 2639-testify-require-1 to master Updates #2639. Squashed commit of the following: commit da7d283c6b20b4dbbc0af4689fa812d14f022b52 Merge: c4af71b0 63e4adc0 Author: Eugene Burkov Date: Tue Feb 9 14:27:41 2021 +0300 Merge branch 'master' into 2639-testify-require-1 commit c4af71b002dc68785106328f60946d7fa73fb933 Author: Eugene Burkov Date: Mon Feb 8 19:32:51 2021 +0300 querylog: fix tests for windows commit b616ea5de88a38550ffd42253d3054ea6f90cff9 Author: Eugene Burkov Date: Mon Feb 8 18:29:28 2021 +0300 querylog: imp tests again commit 091a698df5fbe6c3e572fde12da395f527c88b95 Author: Eugene Burkov Date: Mon Feb 8 15:49:38 2021 +0300 querylog: imp tests --- internal/querylog/qlog_test.go | 390 +++++++++++------- internal/querylog/qlogfile_test.go | 593 +++++++++++++-------------- internal/querylog/qlogreader_test.go | 236 +++-------- 3 files changed, 588 insertions(+), 631 deletions(-) diff --git a/internal/querylog/qlog_test.go b/internal/querylog/qlog_test.go index 07fac8ff..ffe12c4f 100644 --- a/internal/querylog/qlog_test.go +++ b/internal/querylog/qlog_test.go @@ -1,9 +1,12 @@ package querylog import ( + "fmt" + "io/ioutil" "math/rand" "net" "os" + "runtime" "sort" "testing" "time" @@ -14,226 +17,292 @@ import ( "github.com/AdguardTeam/AdGuardHome/internal/dnsfilter" "github.com/miekg/dns" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestMain(m *testing.M) { aghtest.DiscardLogOutput(m) } -func prepareTestDir() string { - const dir = "./agh-test" - _ = os.RemoveAll(dir) - _ = os.MkdirAll(dir, 0o755) +func prepareTestDir(t *testing.T) string { + t.Helper() + + wd, err := os.Getwd() + require.Nil(t, err) + + dir, err := ioutil.TempDir(wd, "agh-tests") + require.Nil(t, err) + require.NotEmpty(t, dir) + + t.Cleanup(func() { + // TODO(e.burkov): Replace with t.TempDir methods after updating + // go version to 1.15. + start := time.Now() + for { + err := os.RemoveAll(dir) + if err == nil { + break + } + + if runtime.GOOS != "windows" || time.Since(start) >= 500*time.Millisecond { + break + } + time.Sleep(5 * time.Millisecond) + } + + assert.Nil(t, err) + }) + return dir } -// Check adding and loading (with filtering) entries from disk and memory +// TestQueryLog tests adding and loading (with filtering) entries from disk and +// memory. func TestQueryLog(t *testing.T) { - conf := Config{ + l := newQueryLog(Config{ Enabled: true, FileEnabled: true, Interval: 1, MemSize: 100, - } - conf.BaseDir = prepareTestDir() - defer func() { _ = os.RemoveAll(conf.BaseDir) }() - l := newQueryLog(conf) + BaseDir: prepareTestDir(t), + }) - // add disk entries + // Add disk entries. addEntry(l, "example.org", net.IPv4(1, 1, 1, 1), net.IPv4(2, 2, 2, 1)) - // write to disk (first file) - _ = l.flushLogBuffer(true) - // start writing to the second file - _ = l.rotate() - // add disk entries + // Write to disk (first file). + require.Nil(t, l.flushLogBuffer(true)) + // Start writing to the second file. + require.Nil(t, l.rotate()) + // Add disk entries. addEntry(l, "example.org", net.IPv4(1, 1, 1, 2), net.IPv4(2, 2, 2, 2)) - // write to disk - _ = l.flushLogBuffer(true) - // add memory entries + // Write to disk. + require.Nil(t, l.flushLogBuffer(true)) + // Add memory entries. addEntry(l, "test.example.org", net.IPv4(1, 1, 1, 3), net.IPv4(2, 2, 2, 3)) addEntry(l, "example.com", net.IPv4(1, 1, 1, 4), net.IPv4(2, 2, 2, 4)) - // get all entries - params := newSearchParams() - entries, _ := l.search(params) - assert.Len(t, entries, 4) - assertLogEntry(t, entries[0], "example.com", net.IPv4(1, 1, 1, 4), net.IPv4(2, 2, 2, 4)) - assertLogEntry(t, entries[1], "test.example.org", net.IPv4(1, 1, 1, 3), net.IPv4(2, 2, 2, 3)) - assertLogEntry(t, entries[2], "example.org", net.IPv4(1, 1, 1, 2), net.IPv4(2, 2, 2, 2)) - assertLogEntry(t, entries[3], "example.org", net.IPv4(1, 1, 1, 1), net.IPv4(2, 2, 2, 1)) + type tcAssertion struct { + num int + host string + answer, client net.IP + } - // search by domain (strict) - params = newSearchParams() - params.searchCriteria = append(params.searchCriteria, searchCriteria{ - criteriaType: ctDomainOrClient, - strict: true, - value: "TEST.example.org", - }) - entries, _ = l.search(params) - assert.Len(t, entries, 1) - assertLogEntry(t, entries[0], "test.example.org", net.IPv4(1, 1, 1, 3), net.IPv4(2, 2, 2, 3)) + testCases := []struct { + name string + sCr []searchCriteria + want []tcAssertion + }{{ + name: "all", + sCr: []searchCriteria{}, + want: []tcAssertion{ + {num: 0, host: "example.com", answer: net.IPv4(1, 1, 1, 4), client: net.IPv4(2, 2, 2, 4)}, + {num: 1, host: "test.example.org", answer: net.IPv4(1, 1, 1, 3), client: net.IPv4(2, 2, 2, 3)}, + {num: 2, host: "example.org", answer: net.IPv4(1, 1, 1, 2), client: net.IPv4(2, 2, 2, 2)}, + {num: 3, host: "example.org", answer: net.IPv4(1, 1, 1, 1), client: net.IPv4(2, 2, 2, 1)}, + }, + }, { + name: "by_domain_strict", + sCr: []searchCriteria{{ + criteriaType: ctDomainOrClient, + strict: true, + value: "TEST.example.org", + }}, + want: []tcAssertion{{ + num: 0, host: "test.example.org", answer: net.IPv4(1, 1, 1, 3), client: net.IPv4(2, 2, 2, 3), + }}, + }, { + name: "by_domain_non-strict", + sCr: []searchCriteria{{ + criteriaType: ctDomainOrClient, + strict: false, + value: "example.ORG", + }}, + want: []tcAssertion{ + {num: 0, host: "test.example.org", answer: net.IPv4(1, 1, 1, 3), client: net.IPv4(2, 2, 2, 3)}, + {num: 1, host: "example.org", answer: net.IPv4(1, 1, 1, 2), client: net.IPv4(2, 2, 2, 2)}, + {num: 2, host: "example.org", answer: net.IPv4(1, 1, 1, 1), client: net.IPv4(2, 2, 2, 1)}, + }, + }, { + name: "by_client_ip_strict", + sCr: []searchCriteria{{ + criteriaType: ctDomainOrClient, + strict: true, + value: "2.2.2.2", + }}, + want: []tcAssertion{{ + num: 0, host: "example.org", answer: net.IPv4(1, 1, 1, 2), client: net.IPv4(2, 2, 2, 2), + }}, + }, { + name: "by_client_ip_non-strict", + sCr: []searchCriteria{{ + criteriaType: ctDomainOrClient, + strict: false, + value: "2.2.2", + }}, + want: []tcAssertion{ + {num: 0, host: "example.com", answer: net.IPv4(1, 1, 1, 4), client: net.IPv4(2, 2, 2, 4)}, + {num: 1, host: "test.example.org", answer: net.IPv4(1, 1, 1, 3), client: net.IPv4(2, 2, 2, 3)}, + {num: 2, host: "example.org", answer: net.IPv4(1, 1, 1, 2), client: net.IPv4(2, 2, 2, 2)}, + {num: 3, host: "example.org", answer: net.IPv4(1, 1, 1, 1), client: net.IPv4(2, 2, 2, 1)}, + }, + }} - // search by domain (not strict) - params = newSearchParams() - params.searchCriteria = append(params.searchCriteria, searchCriteria{ - criteriaType: ctDomainOrClient, - strict: false, - value: "example.ORG", - }) - entries, _ = l.search(params) - assert.Len(t, entries, 3) - assertLogEntry(t, entries[0], "test.example.org", net.IPv4(1, 1, 1, 3), net.IPv4(2, 2, 2, 3)) - assertLogEntry(t, entries[1], "example.org", net.IPv4(1, 1, 1, 2), net.IPv4(2, 2, 2, 2)) - assertLogEntry(t, entries[2], "example.org", net.IPv4(1, 1, 1, 1), net.IPv4(2, 2, 2, 1)) + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + params := newSearchParams() + params.searchCriteria = tc.sCr - // search by client IP (strict) - params = newSearchParams() - params.searchCriteria = append(params.searchCriteria, searchCriteria{ - criteriaType: ctDomainOrClient, - strict: true, - value: "2.2.2.2", - }) - entries, _ = l.search(params) - assert.Len(t, entries, 1) - assertLogEntry(t, entries[0], "example.org", net.IPv4(1, 1, 1, 2), net.IPv4(2, 2, 2, 2)) - - // search by client IP (part of) - params = newSearchParams() - params.searchCriteria = append(params.searchCriteria, searchCriteria{ - criteriaType: ctDomainOrClient, - strict: false, - value: "2.2.2", - }) - entries, _ = l.search(params) - assert.Len(t, entries, 4) - assertLogEntry(t, entries[0], "example.com", net.IPv4(1, 1, 1, 4), net.IPv4(2, 2, 2, 4)) - assertLogEntry(t, entries[1], "test.example.org", net.IPv4(1, 1, 1, 3), net.IPv4(2, 2, 2, 3)) - assertLogEntry(t, entries[2], "example.org", net.IPv4(1, 1, 1, 2), net.IPv4(2, 2, 2, 2)) - assertLogEntry(t, entries[3], "example.org", net.IPv4(1, 1, 1, 1), net.IPv4(2, 2, 2, 1)) + entries, _ := l.search(params) + require.Len(t, entries, len(tc.want)) + for _, want := range tc.want { + assertLogEntry(t, entries[want.num], want.host, want.answer, want.client) + } + }) + } } func TestQueryLogOffsetLimit(t *testing.T) { - conf := Config{ + l := newQueryLog(Config{ Enabled: true, Interval: 1, MemSize: 100, - } - conf.BaseDir = prepareTestDir() - defer func() { _ = os.RemoveAll(conf.BaseDir) }() - l := newQueryLog(conf) + BaseDir: prepareTestDir(t), + }) - // add 10 entries to the log - for i := 0; i < 10; i++ { - addEntry(l, "second.example.org", net.IPv4(1, 1, 1, 1), net.IPv4(2, 2, 2, 1)) + const ( + entNum = 10 + firstPageDomain = "first.example.org" + secondPageDomain = "second.example.org" + ) + // Add entries to the log. + for i := 0; i < entNum; i++ { + addEntry(l, secondPageDomain, net.IPv4(1, 1, 1, 1), net.IPv4(2, 2, 2, 1)) } - // write them to disk (first file) - _ = l.flushLogBuffer(true) - // add 10 more entries to the log (memory) - for i := 0; i < 10; i++ { - addEntry(l, "first.example.org", net.IPv4(1, 1, 1, 1), net.IPv4(2, 2, 2, 1)) + // Write them to the first file. + require.Nil(t, l.flushLogBuffer(true)) + // Add more to the in-memory part of log. + for i := 0; i < entNum; i++ { + addEntry(l, firstPageDomain, net.IPv4(1, 1, 1, 1), net.IPv4(2, 2, 2, 1)) } - // First page params := newSearchParams() - params.offset = 0 - params.limit = 10 - entries, _ := l.search(params) - assert.Len(t, entries, 10) - assert.Equal(t, entries[0].QHost, "first.example.org") - assert.Equal(t, entries[9].QHost, "first.example.org") - // Second page - params.offset = 10 - params.limit = 10 - entries, _ = l.search(params) - assert.Len(t, entries, 10) - assert.Equal(t, entries[0].QHost, "second.example.org") - assert.Equal(t, entries[9].QHost, "second.example.org") + testCases := []struct { + name string + offset int + limit int + wantLen int + want string + }{{ + name: "page_1", + offset: 0, + limit: 10, + wantLen: 10, + want: firstPageDomain, + }, { + name: "page_2", + offset: 10, + limit: 10, + wantLen: 10, + want: secondPageDomain, + }, { + name: "page_2.5", + offset: 15, + limit: 10, + wantLen: 5, + want: secondPageDomain, + }, { + name: "page_3", + offset: 20, + limit: 10, + wantLen: 0, + }} - // Second and a half page - params.offset = 15 - params.limit = 10 - entries, _ = l.search(params) - assert.Len(t, entries, 5) - assert.Equal(t, entries[0].QHost, "second.example.org") - assert.Equal(t, entries[4].QHost, "second.example.org") + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + params.offset = tc.offset + params.limit = tc.limit + entries, _ := l.search(params) - // Third page - params.offset = 20 - params.limit = 10 - entries, _ = l.search(params) - assert.Empty(t, entries) + require.Len(t, entries, tc.wantLen) + + if tc.wantLen > 0 { + assert.Equal(t, entries[0].QHost, tc.want) + assert.Equal(t, entries[tc.wantLen-1].QHost, tc.want) + } + }) + } } func TestQueryLogMaxFileScanEntries(t *testing.T) { - conf := Config{ + l := newQueryLog(Config{ Enabled: true, FileEnabled: true, Interval: 1, MemSize: 100, - } - conf.BaseDir = prepareTestDir() - defer func() { _ = os.RemoveAll(conf.BaseDir) }() - l := newQueryLog(conf) + BaseDir: prepareTestDir(t), + }) - // add 10 entries to the log - for i := 0; i < 10; i++ { + const entNum = 10 + // Add entries to the log. + for i := 0; i < entNum; i++ { addEntry(l, "example.org", net.IPv4(1, 1, 1, 1), net.IPv4(2, 2, 2, 1)) } - // write them to disk (first file) - _ = l.flushLogBuffer(true) + // Write them to disk. + require.Nil(t, l.flushLogBuffer(true)) params := newSearchParams() - params.maxFileScanEntries = 5 // do not scan more than 5 records - entries, _ := l.search(params) - assert.Len(t, entries, 5) - params.maxFileScanEntries = 0 // disable the limit - entries, _ = l.search(params) - assert.Len(t, entries, 10) + for _, maxFileScanEntries := range []int{5, 0} { + t.Run(fmt.Sprintf("limit_%d", maxFileScanEntries), func(t *testing.T) { + params.maxFileScanEntries = maxFileScanEntries + entries, _ := l.search(params) + assert.Len(t, entries, entNum-maxFileScanEntries) + }) + } } func TestQueryLogFileDisabled(t *testing.T) { - conf := Config{ + l := newQueryLog(Config{ Enabled: true, FileEnabled: false, Interval: 1, MemSize: 2, - } - conf.BaseDir = prepareTestDir() - defer func() { _ = os.RemoveAll(conf.BaseDir) }() - l := newQueryLog(conf) + BaseDir: prepareTestDir(t), + }) addEntry(l, "example1.org", net.IPv4(1, 1, 1, 1), net.IPv4(2, 2, 2, 1)) addEntry(l, "example2.org", net.IPv4(1, 1, 1, 1), net.IPv4(2, 2, 2, 1)) + // The oldest entry is going to be removed from memory buffer. addEntry(l, "example3.org", net.IPv4(1, 1, 1, 1), net.IPv4(2, 2, 2, 1)) - // the oldest entry is now removed from mem buffer params := newSearchParams() ll, _ := l.search(params) - assert.Len(t, ll, 2) + require.Len(t, ll, 2) assert.Equal(t, "example3.org", ll[0].QHost) assert.Equal(t, "example2.org", ll[1].QHost) } func addEntry(l *queryLog, host string, answerStr, client net.IP) { - q := dns.Msg{} - q.Question = append(q.Question, dns.Question{ - Name: host + ".", - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }) - - a := dns.Msg{} - a.Question = append(a.Question, q.Question[0]) - answer := new(dns.A) - answer.Hdr = dns.RR_Header{ - Name: q.Question[0].Name, - Rrtype: dns.TypeA, - Class: dns.ClassINET, + q := dns.Msg{ + Question: []dns.Question{{ + Name: host + ".", + Qtype: dns.TypeA, + Qclass: dns.ClassINET, + }}, + } + + a := dns.Msg{ + Question: q.Question, + Answer: []dns.RR{&dns.A{ + Hdr: dns.RR_Header{ + Name: q.Question[0].Name, + Rrtype: dns.TypeA, + Class: dns.ClassINET, + }, + A: answerStr, + }}, } - answer.A = answerStr - a.Answer = append(a.Answer, answer) res := dnsfilter.Result{ IsFiltered: true, Reason: dnsfilter.Rewritten, @@ -254,19 +323,22 @@ func addEntry(l *queryLog, host string, answerStr, client net.IP) { l.Add(params) } -func assertLogEntry(t *testing.T, entry *logEntry, host string, answer, client net.IP) bool { +func assertLogEntry(t *testing.T, entry *logEntry, host string, answer, client net.IP) { + t.Helper() + + require.NotNil(t, entry) + assert.Equal(t, host, entry.QHost) assert.Equal(t, client, entry.IP) assert.Equal(t, "A", entry.QType) assert.Equal(t, "IN", entry.QClass) - msg := new(dns.Msg) - assert.Nil(t, msg.Unpack(entry.Answer)) - assert.Len(t, msg.Answer, 1) + msg := &dns.Msg{} + require.Nil(t, msg.Unpack(entry.Answer)) + require.Len(t, msg.Answer, 1) + ip := proxyutil.GetIPFromDNSRecord(msg.Answer[0]).To16() - assert.NotNil(t, ip) assert.Equal(t, answer, ip) - return true } func testEntries() (entries []*logEntry) { @@ -332,8 +404,8 @@ func TestLogEntriesByTime_sort(t *testing.T) { entries := testEntries() sort.Sort(logEntriesByTimeDesc(entries)) - for i := 1; i < len(entries); i++ { - assert.False(t, entries[i].Time.After(entries[i-1].Time), - "%s %s", entries[i].Time, entries[i-1].Time) + for i := range entries[1:] { + assert.False(t, entries[i+1].Time.After(entries[i].Time), + "%s %s", entries[i+1].Time, entries[i].Time) } } diff --git a/internal/querylog/qlogfile_test.go b/internal/querylog/qlogfile_test.go index 7d603ba5..b583d8da 100644 --- a/internal/querylog/qlogfile_test.go +++ b/internal/querylog/qlogfile_test.go @@ -2,347 +2,340 @@ package querylog import ( "encoding/binary" + "errors" + "fmt" "io" "io/ioutil" "math" "net" - "os" "strings" "testing" "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) -func TestQLogFileEmpty(t *testing.T) { - testDir := prepareTestDir() - defer func() { _ = os.RemoveAll(testDir) }() - testFile := prepareTestFile(testDir, 0) +// prepareTestFiles prepares several test query log files, each with the +// specified lines count. +func prepareTestFiles(t *testing.T, dir string, filesNum, linesNum int) []string { + t.Helper() - // create the new QLogFile instance - q, err := NewQLogFile(testFile) - assert.Nil(t, err) - assert.NotNil(t, q) - defer q.Close() - - // seek to the start - pos, err := q.SeekStart() - assert.Nil(t, err) - assert.EqualValues(t, 0, pos) - - // try reading anyway - line, err := q.ReadNext() - assert.Equal(t, io.EOF, err) - assert.Empty(t, line) -} - -func TestQLogFileLarge(t *testing.T) { - // should be large enough - count := 50000 - - testDir := prepareTestDir() - defer func() { _ = os.RemoveAll(testDir) }() - testFile := prepareTestFile(testDir, count) - - // create the new QLogFile instance - q, err := NewQLogFile(testFile) - assert.Nil(t, err) - assert.NotNil(t, q) - defer q.Close() - - // seek to the start - pos, err := q.SeekStart() - assert.Nil(t, err) - assert.NotEqualValues(t, 0, pos) - - read := 0 - var line string - for err == nil { - line, err = q.ReadNext() - if err == nil { - assert.NotZero(t, len(line)) - read++ - } - } - - assert.Equal(t, count, read) - assert.Equal(t, io.EOF, err) -} - -func TestQLogFileSeekLargeFile(t *testing.T) { - // more or less big file - count := 10000 - - testDir := prepareTestDir() - defer func() { _ = os.RemoveAll(testDir) }() - testFile := prepareTestFile(testDir, count) - - // create the new QLogFile instance - q, err := NewQLogFile(testFile) - assert.Nil(t, err) - assert.NotNil(t, q) - defer q.Close() - - // CASE 1: NOT TOO OLD LINE - testSeekLineQLogFile(t, q, 300) - - // CASE 2: OLD LINE - testSeekLineQLogFile(t, q, count-300) - - // CASE 3: FIRST LINE - testSeekLineQLogFile(t, q, 0) - - // CASE 4: LAST LINE - testSeekLineQLogFile(t, q, count) - - // CASE 5: Seek non-existent (too low) - _, _, err = q.SeekTS(123) - assert.NotNil(t, err) - - // CASE 6: Seek non-existent (too high) - ts, _ := time.Parse(time.RFC3339, "2100-01-02T15:04:05Z07:00") - _, _, err = q.SeekTS(ts.UnixNano()) - assert.NotNil(t, err) - - // CASE 7: "Almost" found - line, err := getQLogFileLine(q, count/2) - assert.Nil(t, err) - // ALMOST the record we need - timestamp := readQLogTimestamp(line) - 1 - assert.NotEqualValues(t, 0, timestamp) - _, depth, err := q.SeekTS(timestamp) - assert.NotNil(t, err) - assert.LessOrEqual(t, depth, int(math.Log2(float64(count))+3)) -} - -func TestQLogFileSeekSmallFile(t *testing.T) { - // more or less big file - count := 10 - - testDir := prepareTestDir() - defer func() { _ = os.RemoveAll(testDir) }() - testFile := prepareTestFile(testDir, count) - - // create the new QLogFile instance - q, err := NewQLogFile(testFile) - assert.Nil(t, err) - assert.NotNil(t, q) - defer q.Close() - - // CASE 1: NOT TOO OLD LINE - testSeekLineQLogFile(t, q, 2) - - // CASE 2: OLD LINE - testSeekLineQLogFile(t, q, count-2) - - // CASE 3: FIRST LINE - testSeekLineQLogFile(t, q, 0) - - // CASE 4: LAST LINE - testSeekLineQLogFile(t, q, count) - - // CASE 5: Seek non-existent (too low) - _, _, err = q.SeekTS(123) - assert.NotNil(t, err) - - // CASE 6: Seek non-existent (too high) - ts, _ := time.Parse(time.RFC3339, "2100-01-02T15:04:05Z07:00") - _, _, err = q.SeekTS(ts.UnixNano()) - assert.NotNil(t, err) - - // CASE 7: "Almost" found - line, err := getQLogFileLine(q, count/2) - assert.Nil(t, err) - // ALMOST the record we need - timestamp := readQLogTimestamp(line) - 1 - assert.NotEqualValues(t, 0, timestamp) - _, depth, err := q.SeekTS(timestamp) - assert.NotNil(t, err) - assert.LessOrEqual(t, depth, int(math.Log2(float64(count))+3)) -} - -func testSeekLineQLogFile(t *testing.T, q *QLogFile, lineNumber int) { - line, err := getQLogFileLine(q, lineNumber) - assert.Nil(t, err) - ts := readQLogTimestamp(line) - assert.NotEqualValues(t, 0, ts) - - // try seeking to that line now - pos, _, err := q.SeekTS(ts) - assert.Nil(t, err) - assert.NotEqualValues(t, 0, pos) - - testLine, err := q.ReadNext() - assert.Nil(t, err) - assert.Equal(t, line, testLine) -} - -func getQLogFileLine(q *QLogFile, lineNumber int) (string, error) { - _, err := q.SeekStart() - if err != nil { - return "", err - } - - for i := 1; i < lineNumber; i++ { - _, err := q.ReadNext() - if err != nil { - return "", err - } - } - return q.ReadNext() -} - -// Check adding and loading (with filtering) entries from disk and memory -func TestQLogFile(t *testing.T) { - testDir := prepareTestDir() - defer func() { _ = os.RemoveAll(testDir) }() - testFile := prepareTestFile(testDir, 2) - - // create the new QLogFile instance - q, err := NewQLogFile(testFile) - assert.Nil(t, err) - assert.NotNil(t, q) - defer q.Close() - - // seek to the start - pos, err := q.SeekStart() - assert.Nil(t, err) - assert.Greater(t, pos, int64(0)) - - // read first line - line, err := q.ReadNext() - assert.Nil(t, err) - assert.Contains(t, line, "0.0.0.2") - assert.True(t, strings.HasPrefix(line, "{"), line) - assert.True(t, strings.HasSuffix(line, "}"), line) - - // read second line - line, err = q.ReadNext() - assert.Nil(t, err) - assert.EqualValues(t, 0, q.position) - assert.Contains(t, line, "0.0.0.1") - assert.True(t, strings.HasPrefix(line, "{"), line) - assert.True(t, strings.HasSuffix(line, "}"), line) - - // try reading again (there's nothing to read anymore) - line, err = q.ReadNext() - assert.Equal(t, io.EOF, err) - assert.Empty(t, line) -} - -// prepareTestFile - prepares a test query log file with the specified number of lines -func prepareTestFile(dir string, linesCount int) string { - return prepareTestFiles(dir, 1, linesCount)[0] -} - -// prepareTestFiles - prepares several test query log files -// each of them -- with the specified linesCount -func prepareTestFiles(dir string, filesCount, linesCount int) []string { - format := `{"IP":"${IP}","T":"${TIMESTAMP}","QH":"example.org","QT":"A","QC":"IN","Answer":"AAAAAAABAAEAAAAAB2V4YW1wbGUDb3JnAAABAAEHZXhhbXBsZQNvcmcAAAEAAQAAAAAABAECAwQ=","Result":{},"Elapsed":0,"Upstream":"upstream"}` + const strV = "\"%s\"" + const nl = "\n" + const format = `{"IP":` + strV + `,"T":` + strV + `,` + + `"QH":"example.org","QT":"A","QC":"IN",` + + `"Answer":"AAAAAAABAAEAAAAAB2V4YW1wbGUDb3JnAAABAAEHZXhhbXBsZQNvcmcAAAEAAQAAAAAABAECAwQ=",` + + `"Result":{},"Elapsed":0,"Upstream":"upstream"}` + nl lineTime, _ := time.Parse(time.RFC3339Nano, "2020-02-18T22:36:35.920973+03:00") lineIP := uint32(0) - files := make([]string, filesCount) - for j := 0; j < filesCount; j++ { - f, _ := ioutil.TempFile(dir, "*.txt") - files[filesCount-j-1] = f.Name() + files := make([]string, filesNum) + for j := range files { + f, err := ioutil.TempFile(dir, "*.txt") + require.Nil(t, err) + files[filesNum-j-1] = f.Name() - for i := 0; i < linesCount; i++ { + for i := 0; i < linesNum; i++ { lineIP++ lineTime = lineTime.Add(time.Second) ip := make(net.IP, 4) binary.BigEndian.PutUint32(ip, lineIP) - line := format - line = strings.ReplaceAll(line, "${IP}", ip.String()) - line = strings.ReplaceAll(line, "${TIMESTAMP}", lineTime.Format(time.RFC3339Nano)) + line := fmt.Sprintf(format, ip, lineTime.Format(time.RFC3339Nano)) - _, _ = f.WriteString(line) - _, _ = f.WriteString("\n") + _, err = f.WriteString(line) + require.Nil(t, err) } } return files } -func TestQLogSeek(t *testing.T) { - testDir := prepareTestDir() - defer func() { _ = os.RemoveAll(testDir) }() +// prepareTestFile prepares a test query log file with the specified number of +// lines. +func prepareTestFile(t *testing.T, dir string, linesCount int) string { + t.Helper() - d := `{"T":"2020-08-31T18:44:23.911246629+03:00","QH":"wfqvjymurpwegyv","QT":"A","QC":"IN","CP":"","Answer":"","Result":{},"Elapsed":66286385,"Upstream":"tls://dns-unfiltered.adguard.com:853"} -{"T":"2020-08-31T18:44:25.376690873+03:00"} -{"T":"2020-08-31T18:44:25.382540454+03:00"}` - f, _ := ioutil.TempFile(testDir, "*.txt") - _, _ = f.WriteString(d) - defer f.Close() - - q, err := NewQLogFile(f.Name()) - assert.Nil(t, err) - defer q.Close() - - target, _ := time.Parse(time.RFC3339, "2020-08-31T18:44:25.376690873+03:00") - - _, depth, err := q.SeekTS(target.UnixNano()) - assert.Nil(t, err) - assert.Equal(t, 1, depth) + return prepareTestFiles(t, dir, 1, linesCount)[0] } -func TestQLogSeek_ErrTSTooLate(t *testing.T) { - testDir := prepareTestDir() +// newTestQLogFile creates new *QLogFile for tests and registers the required +// cleanup functions. +func newTestQLogFile(t *testing.T, linesNum int) (file *QLogFile) { + t.Helper() + + testFile := prepareTestFile(t, prepareTestDir(t), linesNum) + + // Create the new QLogFile instance. + file, err := NewQLogFile(testFile) + require.Nil(t, err) + assert.NotNil(t, file) t.Cleanup(func() { - _ = os.RemoveAll(testDir) + assert.Nil(t, file.Close()) }) - d := `{"T":"2020-08-31T18:44:23.911246629+03:00","QH":"wfqvjymurpwegyv","QT":"A","QC":"IN","CP":"","Answer":"","Result":{},"Elapsed":66286385,"Upstream":"tls://dns-unfiltered.adguard.com:853"} -{"T":"2020-08-31T18:44:25.376690873+03:00"} -{"T":"2020-08-31T18:44:25.382540454+03:00"} -` - f, err := ioutil.TempFile(testDir, "*.txt") - assert.Nil(t, err) - defer f.Close() - - _, err = f.WriteString(d) - assert.Nil(t, err) - - q, err := NewQLogFile(f.Name()) - assert.Nil(t, err) - defer q.Close() - - target, err := time.Parse(time.RFC3339, "2020-08-31T18:44:25.382540454+03:00") - assert.Nil(t, err) - - _, depth, err := q.SeekTS(target.UnixNano() + int64(time.Second)) - assert.Equal(t, ErrTSTooLate, err) - assert.Equal(t, 2, depth) + return file } -func TestQLogSeek_ErrTSTooEarly(t *testing.T) { - testDir := prepareTestDir() +func TestQLogFile_ReadNext(t *testing.T) { + testCases := []struct { + name string + linesNum int + }{{ + name: "empty", + linesNum: 0, + }, { + name: "large", + linesNum: 50000, + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + q := newTestQLogFile(t, tc.linesNum) + + // Calculate the expected position. + fileInfo, err := q.file.Stat() + require.Nil(t, err) + var expPos int64 + if expPos = fileInfo.Size(); expPos > 0 { + expPos-- + } + + // Seek to the start. + pos, err := q.SeekStart() + require.Nil(t, err) + require.EqualValues(t, expPos, pos) + + var read int + var line string + for err == nil { + line, err = q.ReadNext() + if err == nil { + assert.NotEmpty(t, line) + read++ + } + } + + require.Equal(t, io.EOF, err) + assert.Equal(t, tc.linesNum, read) + }) + } +} + +func TestQLogFile_SeekTS_good(t *testing.T) { + linesCases := []struct { + name string + num int + }{{ + name: "large", + num: 10000, + }, { + name: "small", + num: 10, + }} + + for _, l := range linesCases { + testCases := []struct { + name string + linesNum int + line int + }{{ + name: "not_too_old", + line: 2, + }, { + name: "old", + line: l.num - 2, + }, { + name: "first", + line: 0, + }, { + name: "last", + line: l.num, + }} + + q := newTestQLogFile(t, l.num) + + for _, tc := range testCases { + t.Run(l.name+"_"+tc.name, func(t *testing.T) { + line, err := getQLogFileLine(q, tc.line) + require.Nil(t, err) + ts := readQLogTimestamp(line) + assert.NotEqualValues(t, 0, ts) + + // Try seeking to that line now. + pos, _, err := q.SeekTS(ts) + require.Nil(t, err) + assert.NotEqualValues(t, 0, pos) + + testLine, err := q.ReadNext() + require.Nil(t, err) + assert.Equal(t, line, testLine) + }) + } + } +} + +func TestQLogFile_SeekTS_bad(t *testing.T) { + linesCases := []struct { + name string + num int + }{{ + name: "large", + num: 10000, + }, { + name: "small", + num: 10, + }} + + for _, l := range linesCases { + testCases := []struct { + name string + ts int64 + leq bool + }{{ + name: "non-existent_long_ago", + }, { + name: "non-existent_far_ahead", + }, { + name: "almost", + leq: true, + }} + + q := newTestQLogFile(t, l.num) + testCases[0].ts = 123 + + lateTS, _ := time.Parse(time.RFC3339, "2100-01-02T15:04:05Z07:00") + testCases[1].ts = lateTS.UnixNano() + + line, err := getQLogFileLine(q, l.num/2) + require.Nil(t, err) + testCases[2].ts = readQLogTimestamp(line) - 1 + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + assert.NotEqualValues(t, 0, tc.ts) + + _, depth, err := q.SeekTS(tc.ts) + assert.NotEmpty(t, l.num) + require.NotNil(t, err) + if tc.leq { + assert.LessOrEqual(t, depth, int(math.Log2(float64(l.num))+3)) + } + }) + } + } +} + +func getQLogFileLine(q *QLogFile, lineNumber int) (line string, err error) { + if _, err = q.SeekStart(); err != nil { + return line, err + } + + for i := 1; i < lineNumber; i++ { + if _, err = q.ReadNext(); err != nil { + return line, err + } + } + + return q.ReadNext() +} + +// Check adding and loading (with filtering) entries from disk and memory. +func TestQLogFile(t *testing.T) { + // Create the new QLogFile instance. + q := newTestQLogFile(t, 2) + + // Seek to the start. + pos, err := q.SeekStart() + require.Nil(t, err) + assert.Greater(t, pos, int64(0)) + + // Read first line. + line, err := q.ReadNext() + require.Nil(t, err) + assert.Contains(t, line, "0.0.0.2") + assert.True(t, strings.HasPrefix(line, "{"), line) + assert.True(t, strings.HasSuffix(line, "}"), line) + + // Read second line. + line, err = q.ReadNext() + require.Nil(t, err) + assert.EqualValues(t, 0, q.position) + assert.Contains(t, line, "0.0.0.1") + assert.True(t, strings.HasPrefix(line, "{"), line) + assert.True(t, strings.HasSuffix(line, "}"), line) + + // Try reading again (there's nothing to read anymore). + line, err = q.ReadNext() + require.Equal(t, io.EOF, err) + assert.Empty(t, line) +} + +func NewTestQLogFileData(t *testing.T, data string) (file *QLogFile) { + f, err := ioutil.TempFile(prepareTestDir(t), "*.txt") + require.Nil(t, err) t.Cleanup(func() { - _ = os.RemoveAll(testDir) + assert.Nil(t, f.Close()) }) - d := `{"T":"2020-08-31T18:44:23.911246629+03:00","QH":"wfqvjymurpwegyv","QT":"A","QC":"IN","CP":"","Answer":"","Result":{},"Elapsed":66286385,"Upstream":"tls://dns-unfiltered.adguard.com:853"} -{"T":"2020-08-31T18:44:25.376690873+03:00"} -{"T":"2020-08-31T18:44:25.382540454+03:00"} -` - f, err := ioutil.TempFile(testDir, "*.txt") - assert.Nil(t, err) - defer f.Close() + _, err = f.WriteString(data) + require.Nil(t, err) - _, err = f.WriteString(d) - assert.Nil(t, err) + file, err = NewQLogFile(f.Name()) + require.Nil(t, err) + t.Cleanup(func() { + assert.Nil(t, file.Close()) + }) - q, err := NewQLogFile(f.Name()) - assert.Nil(t, err) - defer q.Close() - - target, err := time.Parse(time.RFC3339, "2020-08-31T18:44:23.911246629+03:00") - assert.Nil(t, err) - - _, depth, err := q.SeekTS(target.UnixNano() - int64(time.Second)) - assert.Equal(t, ErrTSTooEarly, err) - assert.Equal(t, 1, depth) + return file +} + +func TestQLog_Seek(t *testing.T) { + const nl = "\n" + const strV = "%s" + const recs = `{"T":"` + strV + `","QH":"wfqvjymurpwegyv","QT":"A","QC":"IN","CP":"","Answer":"","Result":{},"Elapsed":66286385,"Upstream":"tls://dns-unfiltered.adguard.com:853"}` + nl + + `{"T":"` + strV + `"}` + nl + + `{"T":"` + strV + `"}` + nl + timestamp, _ := time.Parse(time.RFC3339Nano, "2020-08-31T18:44:25.376690873+03:00") + + testCases := []struct { + name string + delta int + wantErr error + wantDepth int + }{{ + name: "ok", + delta: 0, + wantErr: nil, + wantDepth: 2, + }, { + name: "too_late", + delta: 2, + wantErr: ErrTSTooLate, + wantDepth: 2, + }, { + name: "too_early", + delta: -2, + wantErr: ErrTSTooEarly, + wantDepth: 1, + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + data := fmt.Sprintf(recs, + timestamp.Add(-time.Second).Format(time.RFC3339Nano), + timestamp.Format(time.RFC3339Nano), + timestamp.Add(time.Second).Format(time.RFC3339Nano), + ) + + q := NewTestQLogFileData(t, data) + + _, depth, err := q.SeekTS(timestamp.Add(time.Second * time.Duration(tc.delta)).UnixNano()) + require.Truef(t, errors.Is(err, tc.wantErr), "%v", err) + assert.Equal(t, tc.wantDepth, depth) + }) + } } diff --git a/internal/querylog/qlogreader_test.go b/internal/querylog/qlogreader_test.go index 967e8396..0333be36 100644 --- a/internal/querylog/qlogreader_test.go +++ b/internal/querylog/qlogreader_test.go @@ -3,110 +3,77 @@ package querylog import ( "errors" "io" - "os" "testing" "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) -func TestQLogReaderEmpty(t *testing.T) { - r, err := NewQLogReader([]string{}) - assert.Nil(t, err) - assert.NotNil(t, r) - defer r.Close() +// newTestQLogReader creates new *QLogReader for tests and registers the +// required cleanup functions. +func newTestQLogReader(t *testing.T, filesNum, linesNum int) (reader *QLogReader) { + t.Helper() - // seek to the start - err = r.SeekStart() - assert.Nil(t, err) + testFiles := prepareTestFiles(t, prepareTestDir(t), filesNum, linesNum) - line, err := r.ReadNext() - assert.Empty(t, line) - assert.Equal(t, io.EOF, err) + // Create the new QLogReader instance. + reader, err := NewQLogReader(testFiles) + require.Nil(t, err) + assert.NotNil(t, reader) + t.Cleanup(func() { + assert.Nil(t, reader.Close()) + }) + + return reader } -func TestQLogReaderOneFile(t *testing.T) { - // let's do one small file - count := 10 - filesCount := 1 +func TestQLogReader(t *testing.T) { + testCases := []struct { + name string + filesNum int + linesNum int + }{{ + name: "empty", + filesNum: 0, + linesNum: 0, + }, { + name: "one_file", + filesNum: 1, + linesNum: 10, + }, { + name: "multiple_files", + filesNum: 5, + linesNum: 10000, + }} - testDir := prepareTestDir() - defer func() { _ = os.RemoveAll(testDir) }() - testFiles := prepareTestFiles(testDir, filesCount, count) + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + r := newTestQLogReader(t, tc.filesNum, tc.linesNum) - r, err := NewQLogReader(testFiles) - assert.Nil(t, err) - assert.NotNil(t, r) - defer r.Close() + // Seek to the start. + err := r.SeekStart() + require.Nil(t, err) - // seek to the start - err = r.SeekStart() - assert.Nil(t, err) + // Read everything. + var read int + var line string + for err == nil { + line, err = r.ReadNext() + if err == nil { + assert.NotEmpty(t, line) + read++ + } + } - // read everything - read := 0 - var line string - for err == nil { - line, err = r.ReadNext() - if err == nil { - assert.True(t, len(line) > 0) - read++ - } + require.Equal(t, io.EOF, err) + assert.Equal(t, tc.filesNum*tc.linesNum, read) + }) } - - assert.Equal(t, count*filesCount, read) - assert.Equal(t, io.EOF, err) -} - -func TestQLogReaderMultipleFiles(t *testing.T) { - // should be large enough - count := 10000 - filesCount := 5 - - testDir := prepareTestDir() - defer func() { _ = os.RemoveAll(testDir) }() - testFiles := prepareTestFiles(testDir, filesCount, count) - - r, err := NewQLogReader(testFiles) - assert.Nil(t, err) - assert.NotNil(t, r) - defer r.Close() - - // seek to the start - err = r.SeekStart() - assert.Nil(t, err) - - // read everything - read := 0 - var line string - for err == nil { - line, err = r.ReadNext() - if err == nil { - assert.True(t, len(line) > 0) - read++ - } - } - - assert.Equal(t, count*filesCount, read) - assert.Equal(t, io.EOF, err) } func TestQLogReader_Seek(t *testing.T) { - count := 10000 - filesCount := 2 - - testDir := prepareTestDir() - t.Cleanup(func() { - _ = os.RemoveAll(testDir) - }) - testFiles := prepareTestFiles(testDir, filesCount, count) - - r, err := NewQLogReader(testFiles) - assert.Nil(t, err) - assert.NotNil(t, r) - t.Cleanup(func() { - _ = r.Close() - }) + r := newTestQLogReader(t, 2, 10000) testCases := []struct { name string @@ -114,7 +81,7 @@ func TestQLogReader_Seek(t *testing.T) { want error }{{ name: "not_too_old", - time: "2020-02-19T04:04:56.920973+03:00", + time: "2020-02-18T22:39:35.920973+03:00", want: nil, }, { name: "old", @@ -122,7 +89,7 @@ func TestQLogReader_Seek(t *testing.T) { want: nil, }, { name: "first", - time: "2020-02-19T04:09:55.920973+03:00", + time: "2020-02-18T22:36:36.920973+03:00", want: nil, }, { name: "last", @@ -147,28 +114,20 @@ func TestQLogReader_Seek(t *testing.T) { timestamp, err := time.Parse(time.RFC3339Nano, tc.time) assert.Nil(t, err) + if tc.name == "first" { + assert.True(t, true) + } + err = r.SeekTS(timestamp.UnixNano()) - assert.True(t, errors.Is(err, tc.want), err) + assert.True(t, errors.Is(err, tc.want)) }) } } func TestQLogReader_ReadNext(t *testing.T) { - count := 10 - filesCount := 1 - - testDir := prepareTestDir() - t.Cleanup(func() { - _ = os.RemoveAll(testDir) - }) - testFiles := prepareTestFiles(testDir, filesCount, count) - - r, err := NewQLogReader(testFiles) - assert.Nil(t, err) - assert.NotNil(t, r) - t.Cleanup(func() { - _ = r.Close() - }) + const linesNum = 10 + const filesNum = 1 + r := newTestQLogReader(t, filesNum, linesNum) testCases := []struct { name string @@ -180,7 +139,7 @@ func TestQLogReader_ReadNext(t *testing.T) { want: nil, }, { name: "too_big", - start: count + 1, + start: linesNum + 1, want: io.EOF, }} @@ -199,70 +158,3 @@ func TestQLogReader_ReadNext(t *testing.T) { }) } } - -// TODO(e.burkov): Remove the tests below. Make tests above more compelling. -func TestQLogReaderSeek(t *testing.T) { - // more or less big file - count := 10000 - filesCount := 2 - - testDir := prepareTestDir() - defer func() { _ = os.RemoveAll(testDir) }() - testFiles := prepareTestFiles(testDir, filesCount, count) - - r, err := NewQLogReader(testFiles) - assert.Nil(t, err) - assert.NotNil(t, r) - defer r.Close() - - // CASE 1: NOT TOO OLD LINE - testSeekLineQLogReader(t, r, 300) - - // CASE 2: OLD LINE - testSeekLineQLogReader(t, r, count-300) - - // CASE 3: FIRST LINE - testSeekLineQLogReader(t, r, 0) - - // CASE 4: LAST LINE - testSeekLineQLogReader(t, r, count) - - // CASE 5: Seek non-existent (too low) - err = r.SeekTS(123) - assert.NotNil(t, err) - - // CASE 6: Seek non-existent (too high) - ts, _ := time.Parse(time.RFC3339, "2100-01-02T15:04:05Z07:00") - err = r.SeekTS(ts.UnixNano()) - assert.NotNil(t, err) -} - -func testSeekLineQLogReader(t *testing.T, r *QLogReader, lineNumber int) { - line, err := getQLogReaderLine(r, lineNumber) - assert.Nil(t, err) - ts := readQLogTimestamp(line) - assert.NotEqualValues(t, 0, ts) - - // try seeking to that line now - err = r.SeekTS(ts) - assert.Nil(t, err) - - testLine, err := r.ReadNext() - assert.Nil(t, err) - assert.Equal(t, line, testLine) -} - -func getQLogReaderLine(r *QLogReader, lineNumber int) (string, error) { - err := r.SeekStart() - if err != nil { - return "", err - } - - for i := 1; i < lineNumber; i++ { - _, err := r.ReadNext() - if err != nil { - return "", err - } - } - return r.ReadNext() -} From 6471504555ba87e6e93cc015ba4bb4e4893fd20f Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Tue, 9 Feb 2021 16:51:44 +0300 Subject: [PATCH 105/141] Pull request: home: imp upgrade test Merge in DNS/adguard-home from imp-upgrade-test to master Updates #2639. Updates #2646. Squashed commit of the following: commit f7bd8e020fa8fc285cdd6ecdf36711574f2e6e38 Author: Ainar Garipov Date: Tue Feb 9 16:38:40 2021 +0300 home: imp test more commit 5b64131e04d568871cf401fa3fc2c7980d69bee0 Author: Ainar Garipov Date: Tue Feb 9 16:09:22 2021 +0300 home: imp upgrade test --- internal/home/upgrade_test.go | 278 +++++++++++++--------------------- 1 file changed, 107 insertions(+), 171 deletions(-) diff --git a/internal/home/upgrade_test.go b/internal/home/upgrade_test.go index f884de04..4210305a 100644 --- a/internal/home/upgrade_test.go +++ b/internal/home/upgrade_test.go @@ -3,183 +3,108 @@ package home import ( "fmt" "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) -func TestUpgrade1to2(t *testing.T) { - // let's create test config for 1 schema version - diskConfig := createTestDiskConfig(1) +// any is a convenient alias for interface{}. +type any = interface{} - // update config - err := upgradeSchema1to2(&diskConfig) - if err != nil { - t.Fatalf("Can't upgrade schema version from 1 to 2") - } +// object is a convenient alias for map[string]interface{}. +type object = map[string]any - // ensure that schema version was bumped - compareSchemaVersion(t, diskConfig["schema_version"], 2) +func TestUpgradeSchema1to2(t *testing.T) { + diskConf := testDiskConf(1) - // old coredns entry should be removed - _, ok := diskConfig["coredns"] - if ok { - t.Fatalf("Core DNS config was not removed after upgrade schema version from 1 to 2") - } + err := upgradeSchema1to2(&diskConf) + require.Nil(t, err) - // pull out new dns config - dnsMap, ok := diskConfig["dns"] - if !ok { - t.Fatalf("No DNS config after upgrade schema version from 1 to 2") - } + require.Equal(t, diskConf["schema_version"], 2) - // cast dns configurations to maps and compare them - oldDNSConfig := castInterfaceToMap(t, createTestDNSConfig(1)) - newDNSConfig := castInterfaceToMap(t, dnsMap) - compareConfigs(t, &oldDNSConfig, &newDNSConfig) + _, ok := diskConf["coredns"] + require.False(t, ok) + + dnsMap, ok := diskConf["dns"] + require.True(t, ok) + + oldDNSConf := convertToObject(t, testDNSConf(1)) + newDNSConf := convertToObject(t, dnsMap) + assert.Equal(t, oldDNSConf, newDNSConf) - // exclude dns config and schema version from disk config comparison oldExcludedEntries := []string{"coredns", "schema_version"} newExcludedEntries := []string{"dns", "schema_version"} - oldDiskConfig := createTestDiskConfig(1) - compareConfigsWithoutEntries(t, &oldDiskConfig, &diskConfig, oldExcludedEntries, newExcludedEntries) + oldDiskConf := testDiskConf(1) + assertEqualExcept(t, oldDiskConf, diskConf, oldExcludedEntries, newExcludedEntries) } -func TestUpgrade2to3(t *testing.T) { - // let's create test config - diskConfig := createTestDiskConfig(2) +func TestUpgradeSchema2to3(t *testing.T) { + diskConf := testDiskConf(2) - // upgrade schema from 2 to 3 - err := upgradeSchema2to3(&diskConfig) - if err != nil { - t.Fatalf("Can't update schema version from 2 to 3: %s", err) - } + err := upgradeSchema2to3(&diskConf) + require.Nil(t, err) - // check new schema version - compareSchemaVersion(t, diskConfig["schema_version"], 3) + require.Equal(t, diskConf["schema_version"], 3) - // pull out new dns configuration - dnsMap, ok := diskConfig["dns"] - if !ok { - t.Fatalf("No dns config in new configuration") - } + dnsMap, ok := diskConf["dns"] + require.True(t, ok) - // cast dns configuration to map - newDNSConfig := castInterfaceToMap(t, dnsMap) - - // check if bootstrap DNS becomes an array - bootstrapDNS := newDNSConfig["bootstrap_dns"] + newDNSConf := convertToObject(t, dnsMap) + bootstrapDNS := newDNSConf["bootstrap_dns"] switch v := bootstrapDNS.(type) { case []string: - if len(v) != 1 { - t.Fatalf("Wrong count of bootsrap DNS servers: %d", len(v)) - } - - if v[0] != "8.8.8.8:53" { - t.Fatalf("Bootsrap DNS server is not 8.8.8.8:53 : %s", v[0]) - } + require.Len(t, v, 1) + require.Equal(t, "8.8.8.8:53", v[0]) default: - t.Fatalf("Wrong type for bootsrap DNS: %T", v) + t.Fatalf("wrong type for bootsrap dns: %T", v) } - // exclude bootstrap DNS from DNS configs comparison excludedEntries := []string{"bootstrap_dns"} - oldDNSConfig := castInterfaceToMap(t, createTestDNSConfig(2)) - compareConfigsWithoutEntries(t, &oldDNSConfig, &newDNSConfig, excludedEntries, excludedEntries) + oldDNSConf := convertToObject(t, testDNSConf(2)) + assertEqualExcept(t, oldDNSConf, newDNSConf, excludedEntries, excludedEntries) - // excluded dns config and schema version from disk config comparison excludedEntries = []string{"dns", "schema_version"} - oldDiskConfig := createTestDiskConfig(2) - compareConfigsWithoutEntries(t, &oldDiskConfig, &diskConfig, excludedEntries, excludedEntries) + oldDiskConf := testDiskConf(2) + assertEqualExcept(t, oldDiskConf, diskConf, excludedEntries, excludedEntries) } -func castInterfaceToMap(t *testing.T, oldConfig interface{}) (newConfig map[string]interface{}) { - newConfig = make(map[string]interface{}) - switch v := oldConfig.(type) { - case map[interface{}]interface{}: +func convertToObject(t *testing.T, oldConf any) (newConf object) { + t.Helper() + + switch v := oldConf.(type) { + case map[any]any: + newConf = make(object, len(v)) for key, value := range v { - newConfig[fmt.Sprint(key)] = value + newConf[fmt.Sprint(key)] = value } - case map[string]interface{}: + case object: + newConf = make(object, len(v)) for key, value := range v { - newConfig[key] = value + newConf[key] = value } default: - t.Fatalf("DNS configuration is not a map") + t.Fatalf("dns configuration is not a map, got %T", oldConf) } - return + + return newConf } -// compareConfigsWithoutEntry removes entries from configs and returns result of compareConfigs -func compareConfigsWithoutEntries(t *testing.T, oldConfig, newConfig *map[string]interface{}, oldKey, newKey []string) { - for _, k := range oldKey { - delete(*oldConfig, k) +// assertEqualExcept removes entries from configs and compares them. +func assertEqualExcept(t *testing.T, oldConf, newConf object, oldKeys, newKeys []string) { + t.Helper() + + for _, k := range oldKeys { + delete(oldConf, k) } - for _, k := range newKey { - delete(*newConfig, k) + for _, k := range newKeys { + delete(newConf, k) } - compareConfigs(t, oldConfig, newConfig) + + assert.Equal(t, oldConf, newConf) } -// compares configs before and after schema upgrade -func compareConfigs(t *testing.T, oldConfig, newConfig *map[string]interface{}) { - if len(*oldConfig) != len(*newConfig) { - t.Fatalf("wrong config entries count! Before upgrade: %d; After upgrade: %d", len(*oldConfig), len(*oldConfig)) - } - - // Check old and new entries - for k, v := range *newConfig { - switch value := v.(type) { - case string: - if value != (*oldConfig)[k] { - t.Fatalf("wrong value for string %s. Before update: %s; After update: %s", k, (*oldConfig)[k], value) - } - case int: - if value != (*oldConfig)[k] { - t.Fatalf("wrong value for int %s. Before update: %d; After update: %d", k, (*oldConfig)[k], value) - } - case []string: - for i, line := range value { - if len((*oldConfig)[k].([]string)) != len(value) { - t.Fatalf("wrong array length for %s. Before update: %d; After update: %d", k, len((*oldConfig)[k].([]string)), len(value)) - } - if (*oldConfig)[k].([]string)[i] != line { - t.Fatalf("wrong data for string array %s. Before update: %s; After update: %s", k, (*oldConfig)[k].([]string)[i], line) - } - } - case bool: - if v != (*oldConfig)[k].(bool) { - t.Fatalf("wrong boolean value for %s", k) - } - case []filter: - if len((*oldConfig)[k].([]filter)) != len(value) { - t.Fatalf("wrong filters count. Before update: %d; After update: %d", len((*oldConfig)[k].([]filter)), len(value)) - } - for i, newFilter := range value { - oldFilter := (*oldConfig)[k].([]filter)[i] - if oldFilter.Enabled != newFilter.Enabled || oldFilter.Name != newFilter.Name || oldFilter.RulesCount != newFilter.RulesCount { - t.Fatalf("old filter %s not equals new filter %s", oldFilter.Name, newFilter.Name) - } - } - default: - t.Fatalf("uknown data type for %s: %T", k, value) - } - } -} - -// compareSchemaVersion check if newSchemaVersion equals schemaVersion -func compareSchemaVersion(t *testing.T, newSchemaVersion interface{}, schemaVersion int) { - switch v := newSchemaVersion.(type) { - case int: - if v != schemaVersion { - t.Fatalf("Wrong schema version in new config file") - } - default: - t.Fatalf("Schema version is not an integer after update") - } -} - -func createTestDiskConfig(schemaVersion int) (diskConfig map[string]interface{}) { - diskConfig = make(map[string]interface{}) - diskConfig["language"] = "en" - diskConfig["filters"] = []filter{ +func testDiskConf(schemaVersion int) (diskConf object) { + filters := []filter{ { URL: "https://filters.adtidy.org/android/filters/111_optimized.txt", Name: "Latvian filter", @@ -191,40 +116,51 @@ func createTestDiskConfig(schemaVersion int) (diskConfig map[string]interface{}) RulesCount: 200, }, } - diskConfig["user_rules"] = []string{} - diskConfig["schema_version"] = schemaVersion - diskConfig["bind_host"] = "0.0.0.0" - diskConfig["bind_port"] = 80 - diskConfig["auth_name"] = "name" - diskConfig["auth_pass"] = "pass" - dnsConfig := createTestDNSConfig(schemaVersion) - if schemaVersion > 1 { - diskConfig["dns"] = dnsConfig - } else { - diskConfig["coredns"] = dnsConfig + diskConf = object{ + "language": "en", + "filters": filters, + "user_rules": []string{}, + "schema_version": schemaVersion, + "bind_host": "0.0.0.0", + "bind_port": 80, + "auth_name": "name", + "auth_pass": "pass", } - return diskConfig + + dnsConf := testDNSConf(schemaVersion) + if schemaVersion > 1 { + diskConf["dns"] = dnsConf + } else { + diskConf["coredns"] = dnsConf + } + + return diskConf } -func createTestDNSConfig(schemaVersion int) map[interface{}]interface{} { - dnsConfig := make(map[interface{}]interface{}) - dnsConfig["port"] = 53 - dnsConfig["blocked_response_ttl"] = 10 - dnsConfig["querylog_enabled"] = true - dnsConfig["ratelimit"] = 20 - dnsConfig["bootstrap_dns"] = "8.8.8.8:53" - if schemaVersion > 2 { - dnsConfig["bootstrap_dns"] = []string{"8.8.8.8:53"} +// testDNSConf creates a DNS config for test the way gopkg.in/yaml.v2 would +// unmarshal it. In YAML, keys aren't guaranteed to always only be strings. +func testDNSConf(schemaVersion int) (dnsConf map[any]any) { + dnsConf = map[any]any{ + "port": 53, + "blocked_response_ttl": 10, + "querylog_enabled": true, + "ratelimit": 20, + "bootstrap_dns": "8.8.8.8:53", + "parental_sensitivity": 13, + "ratelimit_whitelist": []string{}, + "upstream_dns": []string{"tls://1.1.1.1", "tls://1.0.0.1", "8.8.8.8"}, + "filtering_enabled": true, + "refuse_any": true, + "parental_enabled": true, + "bind_host": "0.0.0.0", + "protection_enabled": true, + "safesearch_enabled": true, + "safebrowsing_enabled": true, } - dnsConfig["parental_sensitivity"] = 13 - dnsConfig["ratelimit_whitelist"] = []string{} - dnsConfig["upstream_dns"] = []string{"tls://1.1.1.1", "tls://1.0.0.1", "8.8.8.8"} - dnsConfig["filtering_enabled"] = true - dnsConfig["refuse_any"] = true - dnsConfig["parental_enabled"] = true - dnsConfig["bind_host"] = "0.0.0.0" - dnsConfig["protection_enabled"] = true - dnsConfig["safesearch_enabled"] = true - dnsConfig["safebrowsing_enabled"] = true - return dnsConfig + + if schemaVersion > 2 { + dnsConf["bootstrap_dns"] = []string{"8.8.8.8:53"} + } + + return dnsConf } From a3dddd72c1cd864749355c49ef5313a447709638 Mon Sep 17 00:00:00 2001 From: Eugene Burkov Date: Tue, 9 Feb 2021 19:38:31 +0300 Subject: [PATCH 106/141] Pull request: 2639 use testify require vol.2 Merge in DNS/adguard-home from 2639-testify-require-2 to master Updates #2639. Squashed commit of the following: commit 31cc29a166e2e48a73956853cbc6d6dd681ab6da Author: Eugene Burkov Date: Tue Feb 9 18:48:31 2021 +0300 all: deal with t.Run commit 484f477fbfedd03aca4d322bc1cc9e131f30e1ce Author: Eugene Burkov Date: Tue Feb 9 17:44:02 2021 +0300 all: fix readability, imp tests commit 1231a825b353c16e43eae1b660dbb4c87805f564 Author: Eugene Burkov Date: Tue Feb 9 16:06:29 2021 +0300 all: imp tests --- internal/aghtest/os.go | 45 ++++++ internal/querylog/qlog_test.go | 43 +----- internal/querylog/qlogfile_test.go | 17 ++- internal/querylog/qlogreader_test.go | 2 +- internal/stats/stats_test.go | 130 +++++++++-------- internal/stats/unit.go | 113 ++++++++------- internal/sysutil/net_linux_test.go | 43 +++--- internal/util/autohosts_test.go | 200 +++++++++++++++++---------- internal/util/helpers_test.go | 4 +- internal/util/network_test.go | 17 +-- 10 files changed, 345 insertions(+), 269 deletions(-) create mode 100644 internal/aghtest/os.go diff --git a/internal/aghtest/os.go b/internal/aghtest/os.go new file mode 100644 index 00000000..a9885c03 --- /dev/null +++ b/internal/aghtest/os.go @@ -0,0 +1,45 @@ +package aghtest + +import ( + "io/ioutil" + "os" + "runtime" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// PrepareTestDir returns the full path to temporary created directory and +// registers the appropriate cleanup for *t. +func PrepareTestDir(t *testing.T) (dir string) { + t.Helper() + + wd, err := os.Getwd() + require.Nil(t, err) + + dir, err = ioutil.TempDir(wd, "agh-test") + require.Nil(t, err) + require.NotEmpty(t, dir) + + t.Cleanup(func() { + // TODO(e.burkov): Replace with t.TempDir methods after updating + // go version to 1.15. + start := time.Now() + for { + err := os.RemoveAll(dir) + if err == nil { + break + } + + if runtime.GOOS != "windows" || time.Since(start) >= 500*time.Millisecond { + break + } + time.Sleep(5 * time.Millisecond) + } + assert.Nil(t, err) + }) + + return dir +} diff --git a/internal/querylog/qlog_test.go b/internal/querylog/qlog_test.go index ffe12c4f..c8b34eb5 100644 --- a/internal/querylog/qlog_test.go +++ b/internal/querylog/qlog_test.go @@ -2,11 +2,8 @@ package querylog import ( "fmt" - "io/ioutil" "math/rand" "net" - "os" - "runtime" "sort" "testing" "time" @@ -24,38 +21,6 @@ func TestMain(m *testing.M) { aghtest.DiscardLogOutput(m) } -func prepareTestDir(t *testing.T) string { - t.Helper() - - wd, err := os.Getwd() - require.Nil(t, err) - - dir, err := ioutil.TempDir(wd, "agh-tests") - require.Nil(t, err) - require.NotEmpty(t, dir) - - t.Cleanup(func() { - // TODO(e.burkov): Replace with t.TempDir methods after updating - // go version to 1.15. - start := time.Now() - for { - err := os.RemoveAll(dir) - if err == nil { - break - } - - if runtime.GOOS != "windows" || time.Since(start) >= 500*time.Millisecond { - break - } - time.Sleep(5 * time.Millisecond) - } - - assert.Nil(t, err) - }) - - return dir -} - // TestQueryLog tests adding and loading (with filtering) entries from disk and // memory. func TestQueryLog(t *testing.T) { @@ -64,7 +29,7 @@ func TestQueryLog(t *testing.T) { FileEnabled: true, Interval: 1, MemSize: 100, - BaseDir: prepareTestDir(t), + BaseDir: aghtest.PrepareTestDir(t), }) // Add disk entries. @@ -166,7 +131,7 @@ func TestQueryLogOffsetLimit(t *testing.T) { Enabled: true, Interval: 1, MemSize: 100, - BaseDir: prepareTestDir(t), + BaseDir: aghtest.PrepareTestDir(t), }) const ( @@ -240,7 +205,7 @@ func TestQueryLogMaxFileScanEntries(t *testing.T) { FileEnabled: true, Interval: 1, MemSize: 100, - BaseDir: prepareTestDir(t), + BaseDir: aghtest.PrepareTestDir(t), }) const entNum = 10 @@ -268,7 +233,7 @@ func TestQueryLogFileDisabled(t *testing.T) { FileEnabled: false, Interval: 1, MemSize: 2, - BaseDir: prepareTestDir(t), + BaseDir: aghtest.PrepareTestDir(t), }) addEntry(l, "example1.org", net.IPv4(1, 1, 1, 1), net.IPv4(2, 2, 2, 1)) diff --git a/internal/querylog/qlogfile_test.go b/internal/querylog/qlogfile_test.go index b583d8da..b74111fc 100644 --- a/internal/querylog/qlogfile_test.go +++ b/internal/querylog/qlogfile_test.go @@ -12,15 +12,20 @@ import ( "testing" "time" + "github.com/AdguardTeam/AdGuardHome/internal/aghtest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // prepareTestFiles prepares several test query log files, each with the // specified lines count. -func prepareTestFiles(t *testing.T, dir string, filesNum, linesNum int) []string { +func prepareTestFiles(t *testing.T, filesNum, linesNum int) []string { t.Helper() + if filesNum == 0 { + return []string{} + } + const strV = "\"%s\"" const nl = "\n" const format = `{"IP":` + strV + `,"T":` + strV + `,` + @@ -31,6 +36,8 @@ func prepareTestFiles(t *testing.T, dir string, filesNum, linesNum int) []string lineTime, _ := time.Parse(time.RFC3339Nano, "2020-02-18T22:36:35.920973+03:00") lineIP := uint32(0) + dir := aghtest.PrepareTestDir(t) + files := make([]string, filesNum) for j := range files { f, err := ioutil.TempFile(dir, "*.txt") @@ -56,10 +63,10 @@ func prepareTestFiles(t *testing.T, dir string, filesNum, linesNum int) []string // prepareTestFile prepares a test query log file with the specified number of // lines. -func prepareTestFile(t *testing.T, dir string, linesCount int) string { +func prepareTestFile(t *testing.T, linesCount int) string { t.Helper() - return prepareTestFiles(t, dir, 1, linesCount)[0] + return prepareTestFiles(t, 1, linesCount)[0] } // newTestQLogFile creates new *QLogFile for tests and registers the required @@ -67,7 +74,7 @@ func prepareTestFile(t *testing.T, dir string, linesCount int) string { func newTestQLogFile(t *testing.T, linesNum int) (file *QLogFile) { t.Helper() - testFile := prepareTestFile(t, prepareTestDir(t), linesNum) + testFile := prepareTestFile(t, linesNum) // Create the new QLogFile instance. file, err := NewQLogFile(testFile) @@ -275,7 +282,7 @@ func TestQLogFile(t *testing.T) { } func NewTestQLogFileData(t *testing.T, data string) (file *QLogFile) { - f, err := ioutil.TempFile(prepareTestDir(t), "*.txt") + f, err := ioutil.TempFile(aghtest.PrepareTestDir(t), "*.txt") require.Nil(t, err) t.Cleanup(func() { assert.Nil(t, f.Close()) diff --git a/internal/querylog/qlogreader_test.go b/internal/querylog/qlogreader_test.go index 0333be36..07fd6fd3 100644 --- a/internal/querylog/qlogreader_test.go +++ b/internal/querylog/qlogreader_test.go @@ -15,7 +15,7 @@ import ( func newTestQLogReader(t *testing.T, filesNum, linesNum int) (reader *QLogReader) { t.Helper() - testFiles := prepareTestFiles(t, prepareTestDir(t), filesNum, linesNum) + testFiles := prepareTestFiles(t, filesNum, linesNum) // Create the new QLogReader instance. reader, err := NewQLogReader(testFiles) diff --git a/internal/stats/stats_test.go b/internal/stats/stats_test.go index 7643b31a..e58f198c 100644 --- a/internal/stats/stats_test.go +++ b/internal/stats/stats_test.go @@ -9,6 +9,7 @@ import ( "github.com/AdguardTeam/AdGuardHome/internal/aghtest" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestMain(m *testing.M) { @@ -34,28 +35,30 @@ func TestStats(t *testing.T) { Filename: "./stats.db", LimitDays: 1, } + + s, err := createObject(conf) + require.Nil(t, err) t.Cleanup(func() { + s.clear() + s.Close() assert.Nil(t, os.Remove(conf.Filename)) }) - s, _ := createObject(conf) - - e := Entry{} - - e.Domain = "domain" - e.Client = "127.0.0.1" - e.Result = RFiltered - e.Time = 123456 - s.Update(e) - - e.Domain = "domain" - e.Client = "127.0.0.1" - e.Result = RNotFiltered - e.Time = 123456 - s.Update(e) + s.Update(Entry{ + Domain: "domain", + Client: "127.0.0.1", + Result: RFiltered, + Time: 123456, + }) + s.Update(Entry{ + Domain: "domain", + Client: "127.0.0.1", + Result: RNotFiltered, + Time: 123456, + }) d, ok := s.getData() - assert.True(t, ok) + require.True(t, ok) a := []uint64{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2} assert.True(t, UIntArrayEquals(d.DNSQueries, a)) @@ -70,12 +73,15 @@ func TestStats(t *testing.T) { assert.True(t, UIntArrayEquals(d.ReplacedParental, a)) m := d.TopQueried + require.NotEmpty(t, m) assert.EqualValues(t, 1, m[0]["domain"]) m = d.TopBlocked + require.NotEmpty(t, m) assert.EqualValues(t, 1, m[0]["domain"]) m = d.TopClients + require.NotEmpty(t, m) assert.EqualValues(t, 2, m[0]["127.0.0.1"]) assert.EqualValues(t, 2, d.NumDNSQueries) @@ -86,81 +92,69 @@ func TestStats(t *testing.T) { assert.EqualValues(t, 0.123456, d.AvgProcessingTime) topClients := s.GetTopClientsIP(2) + require.NotEmpty(t, topClients) assert.True(t, net.IP{127, 0, 0, 1}.Equal(topClients[0])) - - s.clear() - s.Close() } func TestLargeNumbers(t *testing.T) { - var hour int32 = 1 + var hour int32 = 0 newID := func() uint32 { - // use "atomic" to make Go race detector happy + // Use "atomic" to make go race detector happy. return uint32(atomic.LoadInt32(&hour)) } - // log.SetLevel(log.DEBUG) conf := Config{ Filename: "./stats.db", LimitDays: 1, UnitID: newID, } + s, err := createObject(conf) + require.Nil(t, err) t.Cleanup(func() { + s.Close() assert.Nil(t, os.Remove(conf.Filename)) }) - s, _ := createObject(conf) - e := Entry{} + // Number of distinct clients and domains every hour. + const n = 1000 - n := 1000 // number of distinct clients and domains every hour - for h := 0; h != 12; h++ { - if h != 0 { - atomic.AddInt32(&hour, 1) - } - for i := 0; i != n; i++ { - e.Domain = fmt.Sprintf("domain%d", i) - ip := net.IP{127, 0, 0, 1} - ip[2] = byte((i & 0xff00) >> 8) - ip[3] = byte(i & 0xff) - e.Client = ip.String() - e.Result = RNotFiltered - e.Time = 123456 - s.Update(e) + for h := 0; h < 12; h++ { + atomic.AddInt32(&hour, 1) + for i := 0; i < n; i++ { + s.Update(Entry{ + Domain: fmt.Sprintf("domain%d", i), + Client: net.IP{ + 127, + 0, + byte((i & 0xff00) >> 8), + byte(i & 0xff), + }.String(), + Result: RNotFiltered, + Time: 123456, + }) } } d, ok := s.getData() - assert.True(t, ok) - assert.EqualValues(t, int(hour)*n, d.NumDNSQueries) - - s.Close() + require.True(t, ok) + assert.EqualValues(t, hour*n, d.NumDNSQueries) } -// this code is a chunk copied from getData() that generates aggregate data per day -func aggregateDataPerDay(firstID uint32) int { - firstDayID := (firstID + 24 - 1) / 24 * 24 // align_ceil(24) - a := []uint64{} - var sum uint64 - id := firstDayID - nextDayID := firstDayID + 24 - for i := firstDayID - firstID; int(i) != 720; i++ { - sum++ - if id == nextDayID { - a = append(a, sum) - sum = 0 - nextDayID += 24 +func TestStatsCollector(t *testing.T) { + ng := func(_ *unitDB) uint64 { + return 0 + } + units := make([]*unitDB, 720) + + t.Run("hours", func(t *testing.T) { + statsData := statsCollector(units, 0, Hours, ng) + assert.Len(t, statsData, 720) + }) + + t.Run("days", func(t *testing.T) { + for i := 0; i != 25; i++ { + statsData := statsCollector(units, uint32(i), Days, ng) + require.Lenf(t, statsData, 30, "i=%d", i) } - id++ - } - if id <= nextDayID { - a = append(a, sum) - } - return len(a) -} - -func TestAggregateDataPerTimeUnit(t *testing.T) { - for i := 0; i != 25; i++ { - alen := aggregateDataPerDay(uint32(i)) - assert.Equalf(t, 30, alen, "i=%d", i) - } + }) } diff --git a/internal/stats/unit.go b/internal/stats/unit.go index 6f31cd5e..d955d04f 100644 --- a/internal/stats/unit.go +++ b/internal/stats/unit.go @@ -528,6 +528,57 @@ func (s *statsCtx) loadUnits(limit uint32) ([]*unitDB, uint32) { return units, firstID } +// numsGetter is a signature for statsCollector argument. +type numsGetter func(u *unitDB) (num uint64) + +// statsCollector collects statisctics for the given *unitDB slice by specified +// timeUnit using ng to retrieve data. +func statsCollector(units []*unitDB, firstID uint32, timeUnit TimeUnit, ng numsGetter) (nums []uint64) { + if timeUnit == Hours { + for _, u := range units { + nums = append(nums, ng(u)) + } + } else { + // Per time unit counters: 720 hours may span 31 days, so we + // skip data for the first day in this case. + // align_ceil(24) + firstDayID := (firstID + 24 - 1) / 24 * 24 + + var sum uint64 + id := firstDayID + nextDayID := firstDayID + 24 + for i := int(firstDayID - firstID); i != len(units); i++ { + sum += ng(units[i]) + if id == nextDayID { + nums = append(nums, sum) + sum = 0 + nextDayID += 24 + } + id++ + } + if id <= nextDayID { + nums = append(nums, sum) + } + } + return nums +} + +// pairsGetter is a signature for topsCollector argument. +type pairsGetter func(u *unitDB) (pairs []countPair) + +// topsCollector collects statistics about highest values fro the given *unitDB +// slice using pg to retrieve data. +func topsCollector(units []*unitDB, max int, pg pairsGetter) []map[string]uint64 { + m := map[string]uint64{} + for _, u := range units { + for _, it := range pg(u) { + m[it.Name] += it.Count + } + } + a2 := convertMapToSlice(m, max) + return convertTopSlice(a2) +} + /* Algorithm: . Prepare array of N units, where N is the value of "limit" configuration setting . Load data for the most recent units from file @@ -568,65 +619,25 @@ func (s *statsCtx) getData() (statsResponse, bool) { return statsResponse{}, false } - // per time unit counters: - // 720 hours may span 31 days, so we skip data for the first day in this case - firstDayID := (firstID + 24 - 1) / 24 * 24 // align_ceil(24) - - statsCollector := func(numsGetter func(u *unitDB) (num uint64)) (nums []uint64) { - if timeUnit == Hours { - for _, u := range units { - nums = append(nums, numsGetter(u)) - } - } else { - var sum uint64 - id := firstDayID - nextDayID := firstDayID + 24 - for i := int(firstDayID - firstID); i != len(units); i++ { - sum += numsGetter(units[i]) - if id == nextDayID { - nums = append(nums, sum) - sum = 0 - nextDayID += 24 - } - id++ - } - if id <= nextDayID { - nums = append(nums, sum) - } - } - return nums - } - - topsCollector := func(max int, pairsGetter func(u *unitDB) (pairs []countPair)) []map[string]uint64 { - m := map[string]uint64{} - for _, u := range units { - for _, it := range pairsGetter(u) { - m[it.Name] += it.Count - } - } - a2 := convertMapToSlice(m, max) - return convertTopSlice(a2) - } - - dnsQueries := statsCollector(func(u *unitDB) (num uint64) { return u.NTotal }) + dnsQueries := statsCollector(units, firstID, timeUnit, func(u *unitDB) (num uint64) { return u.NTotal }) if timeUnit != Hours && len(dnsQueries) != int(limit/24) { log.Fatalf("len(dnsQueries) != limit: %d %d", len(dnsQueries), limit) } data := statsResponse{ DNSQueries: dnsQueries, - BlockedFiltering: statsCollector(func(u *unitDB) (num uint64) { return u.NResult[RFiltered] }), - ReplacedSafebrowsing: statsCollector(func(u *unitDB) (num uint64) { return u.NResult[RSafeBrowsing] }), - ReplacedParental: statsCollector(func(u *unitDB) (num uint64) { return u.NResult[RParental] }), - TopQueried: topsCollector(maxDomains, func(u *unitDB) (pairs []countPair) { return u.Domains }), - TopBlocked: topsCollector(maxDomains, func(u *unitDB) (pairs []countPair) { return u.BlockedDomains }), - TopClients: topsCollector(maxClients, func(u *unitDB) (pairs []countPair) { return u.Clients }), + BlockedFiltering: statsCollector(units, firstID, timeUnit, func(u *unitDB) (num uint64) { return u.NResult[RFiltered] }), + ReplacedSafebrowsing: statsCollector(units, firstID, timeUnit, func(u *unitDB) (num uint64) { return u.NResult[RSafeBrowsing] }), + ReplacedParental: statsCollector(units, firstID, timeUnit, func(u *unitDB) (num uint64) { return u.NResult[RParental] }), + TopQueried: topsCollector(units, maxDomains, func(u *unitDB) (pairs []countPair) { return u.Domains }), + TopBlocked: topsCollector(units, maxDomains, func(u *unitDB) (pairs []countPair) { return u.BlockedDomains }), + TopClients: topsCollector(units, maxClients, func(u *unitDB) (pairs []countPair) { return u.Clients }), } - // total counters: - - sum := unitDB{} - sum.NResult = make([]uint64, rLast) + // Total counters: + sum := unitDB{ + NResult: make([]uint64, rLast), + } timeN := 0 for _, u := range units { sum.NTotal += u.NTotal diff --git a/internal/sysutil/net_linux_test.go b/internal/sysutil/net_linux_test.go index a9851cb2..3fbfc547 100644 --- a/internal/sysutil/net_linux_test.go +++ b/internal/sysutil/net_linux_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) const nl = "\n" @@ -48,7 +49,7 @@ func TestDHCPCDStaticConfig(t *testing.T) { t.Run(tc.name, func(t *testing.T) { r := bytes.NewReader(tc.data) has, err := dhcpcdStaticConfig(r, "wlan0") - assert.Nil(t, err) + require.Nil(t, err) assert.Equal(t, tc.want, has) }) } @@ -85,26 +86,36 @@ func TestIfacesStaticConfig(t *testing.T) { t.Run(tc.name, func(t *testing.T) { r := bytes.NewReader(tc.data) has, err := ifacesStaticConfig(r, "enp0s3") - assert.Nil(t, err) + require.Nil(t, err) assert.Equal(t, tc.want, has) }) } } func TestSetStaticIPdhcpcdConf(t *testing.T) { - dhcpcdConf := nl + `interface wlan0` + nl + - `static ip_address=192.168.0.2/24` + nl + - `static routers=192.168.0.1` + nl + - `static domain_name_servers=192.168.0.2` + nl + nl + testCases := []struct { + name string + dhcpcdConf string + routers net.IP + }{{ + name: "with_gateway", + dhcpcdConf: nl + `interface wlan0` + nl + + `static ip_address=192.168.0.2/24` + nl + + `static routers=192.168.0.1` + nl + + `static domain_name_servers=192.168.0.2` + nl + nl, + routers: net.IP{192, 168, 0, 1}, + }, { + name: "without_gateway", + dhcpcdConf: nl + `interface wlan0` + nl + + `static ip_address=192.168.0.2/24` + nl + + `static domain_name_servers=192.168.0.2` + nl + nl, + routers: nil, + }} - s := updateStaticIPdhcpcdConf("wlan0", "192.168.0.2/24", net.IP{192, 168, 0, 1}, net.IP{192, 168, 0, 2}) - assert.Equal(t, dhcpcdConf, s) - - // without gateway - dhcpcdConf = nl + `interface wlan0` + nl + - `static ip_address=192.168.0.2/24` + nl + - `static domain_name_servers=192.168.0.2` + nl + nl - - s = updateStaticIPdhcpcdConf("wlan0", "192.168.0.2/24", nil, net.IP{192, 168, 0, 2}) - assert.Equal(t, dhcpcdConf, s) + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + s := updateStaticIPdhcpcdConf("wlan0", "192.168.0.2/24", tc.routers, net.IP{192, 168, 0, 2}) + assert.Equal(t, tc.dhcpcdConf, s) + }) + } } diff --git a/internal/util/autohosts_test.go b/internal/util/autohosts_test.go index c5f26934..82e16da9 100644 --- a/internal/util/autohosts_test.go +++ b/internal/util/autohosts_test.go @@ -11,114 +11,162 @@ import ( "github.com/AdguardTeam/AdGuardHome/internal/aghtest" "github.com/miekg/dns" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestMain(m *testing.M) { aghtest.DiscardLogOutput(m) } -func prepareTestDir() string { - const dir = "./agh-test" - _ = os.RemoveAll(dir) - _ = os.MkdirAll(dir, 0o755) - return dir +func prepareTestFile(t *testing.T) (f *os.File) { + t.Helper() + + dir := aghtest.PrepareTestDir(t) + + f, err := ioutil.TempFile(dir, "") + require.Nil(t, err) + require.NotNil(t, f) + t.Cleanup(func() { + assert.Nil(t, f.Close()) + }) + + return f +} + +func assertWriting(t *testing.T, f *os.File, strs ...string) { + t.Helper() + + for _, str := range strs { + n, err := f.WriteString(str) + require.Nil(t, err) + assert.Equal(t, n, len(str)) + } } func TestAutoHostsResolution(t *testing.T) { - ah := AutoHosts{} + ah := &AutoHosts{} - dir := prepareTestDir() - defer func() { _ = os.RemoveAll(dir) }() - - f, _ := ioutil.TempFile(dir, "") - defer func() { _ = os.Remove(f.Name()) }() - defer f.Close() - - _, _ = f.WriteString(" 127.0.0.1 host localhost # comment \n") - _, _ = f.WriteString(" ::1 localhost#comment \n") + f := prepareTestFile(t) + assertWriting(t, f, + " 127.0.0.1 host localhost # comment \n", + " ::1 localhost#comment \n", + ) ah.Init(f.Name()) - // Existing host - ips := ah.Process("localhost", dns.TypeA) - assert.NotNil(t, ips) - assert.Len(t, ips, 1) - assert.Equal(t, net.ParseIP("127.0.0.1"), ips[0]) + t.Run("existing_host", func(t *testing.T) { + ips := ah.Process("localhost", dns.TypeA) + require.Len(t, ips, 1) + assert.Equal(t, net.IPv4(127, 0, 0, 1), ips[0]) + }) - // Unknown host - ips = ah.Process("newhost", dns.TypeA) - assert.Nil(t, ips) + t.Run("unknown_host", func(t *testing.T) { + ips := ah.Process("newhost", dns.TypeA) + assert.Nil(t, ips) - // Unknown host (comment) - ips = ah.Process("comment", dns.TypeA) - assert.Nil(t, ips) + // Comment. + ips = ah.Process("comment", dns.TypeA) + assert.Nil(t, ips) + }) - // Test hosts file - table := ah.List() - names, ok := table["127.0.0.1"] - assert.True(t, ok) - assert.Equal(t, []string{"host", "localhost"}, names) + t.Run("hosts_file", func(t *testing.T) { + names, ok := ah.List()["127.0.0.1"] + require.True(t, ok) + assert.Equal(t, []string{"host", "localhost"}, names) + }) - // Test PTR - a, _ := dns.ReverseAddr("127.0.0.1") - a = strings.TrimSuffix(a, ".") - hosts := ah.ProcessReverse(a, dns.TypePTR) - if assert.Len(t, hosts, 2) { - assert.Equal(t, hosts[0], "host") - } + t.Run("ptr", func(t *testing.T) { + testCases := []struct { + wantIP string + wantLen int + wantHost string + }{ + {wantIP: "127.0.0.1", wantLen: 2, wantHost: "host"}, + {wantIP: "::1", wantLen: 1, wantHost: "localhost"}, + } - a, _ = dns.ReverseAddr("::1") - a = strings.TrimSuffix(a, ".") - hosts = ah.ProcessReverse(a, dns.TypePTR) - if assert.Len(t, hosts, 1) { - assert.Equal(t, hosts[0], "localhost") - } + for _, tc := range testCases { + a, err := dns.ReverseAddr(tc.wantIP) + require.Nil(t, err) + + a = strings.TrimSuffix(a, ".") + hosts := ah.ProcessReverse(a, dns.TypePTR) + require.Len(t, hosts, tc.wantLen) + assert.Equal(t, tc.wantHost, hosts[0]) + } + }) } func TestAutoHostsFSNotify(t *testing.T) { - ah := AutoHosts{} + ah := &AutoHosts{} - dir := prepareTestDir() - defer func() { _ = os.RemoveAll(dir) }() + f := prepareTestFile(t) - f, _ := ioutil.TempFile(dir, "") - defer func() { _ = os.Remove(f.Name()) }() - defer f.Close() - - // Init - _, _ = f.WriteString(" 127.0.0.1 host localhost \n") + assertWriting(t, f, " 127.0.0.1 host localhost \n") ah.Init(f.Name()) - // Unknown host - ips := ah.Process("newhost", dns.TypeA) - assert.Nil(t, ips) + t.Run("unknown_host", func(t *testing.T) { + ips := ah.Process("newhost", dns.TypeA) + assert.Nil(t, ips) + }) - // Stat monitoring for changes + // Start monitoring for changes. ah.Start() - defer ah.Close() + t.Cleanup(ah.Close) - // Update file - _, _ = f.WriteString("127.0.0.2 newhost\n") - _ = f.Sync() + assertWriting(t, f, "127.0.0.2 newhost\n") + require.Nil(t, f.Sync()) - // wait until fsnotify has triggerred and processed the file-modification event + // Wait until fsnotify has triggerred and processed the + // file-modification event. time.Sleep(50 * time.Millisecond) - // Check if we are notified about changes - ips = ah.Process("newhost", dns.TypeA) - assert.NotNil(t, ips) - assert.Len(t, ips, 1) - assert.True(t, net.IP{127, 0, 0, 2}.Equal(ips[0])) + t.Run("notified", func(t *testing.T) { + ips := ah.Process("newhost", dns.TypeA) + assert.NotNil(t, ips) + require.Len(t, ips, 1) + assert.True(t, net.IP{127, 0, 0, 2}.Equal(ips[0])) + }) } -func TestIP(t *testing.T) { - assert.True(t, net.IP{127, 0, 0, 1}.Equal(DNSUnreverseAddr("1.0.0.127.in-addr.arpa"))) - assert.Equal(t, "::abcd:1234", DNSUnreverseAddr("4.3.2.1.d.c.b.a.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa").String()) - assert.Equal(t, "::abcd:1234", DNSUnreverseAddr("4.3.2.1.d.c.B.A.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa").String()) +func TestDNSReverseAddr(t *testing.T) { + testCases := []struct { + name string + have string + want net.IP + }{{ + name: "good_ipv4", + have: "1.0.0.127.in-addr.arpa", + want: net.IP{127, 0, 0, 1}, + }, { + name: "good_ipv6", + have: "4.3.2.1.d.c.b.a.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa", + want: net.ParseIP("::abcd:1234"), + }, { + name: "good_ipv6_case", + have: "4.3.2.1.d.c.B.A.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa", + want: net.ParseIP("::abcd:1234"), + }, { + name: "bad_ipv4_dot", + have: "1.0.0.127.in-addr.arpa.", + }, { + name: "wrong_ipv4", + have: ".0.0.127.in-addr.arpa", + }, { + name: "wrong_ipv6", + have: ".3.2.1.d.c.b.a.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa", + }, { + name: "bad_ipv6_dot", + have: "4.3.2.1.d.c.b.a.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0..ip6.arpa", + }, { + name: "bad_ipv6_space", + have: "4.3.2.1.d.c.b. .0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa", + }} - assert.Nil(t, DNSUnreverseAddr("1.0.0.127.in-addr.arpa.")) - assert.Nil(t, DNSUnreverseAddr(".0.0.127.in-addr.arpa")) - assert.Nil(t, DNSUnreverseAddr(".3.2.1.d.c.b.a.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa")) - assert.Nil(t, DNSUnreverseAddr("4.3.2.1.d.c.b.a.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0..ip6.arpa")) - assert.Nil(t, DNSUnreverseAddr("4.3.2.1.d.c.b. .0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa")) + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + ip := DNSUnreverseAddr(tc.have) + assert.True(t, tc.want.Equal(ip)) + }) + } } diff --git a/internal/util/helpers_test.go b/internal/util/helpers_test.go index 68ebbabd..a09d97e6 100644 --- a/internal/util/helpers_test.go +++ b/internal/util/helpers_test.go @@ -4,12 +4,14 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestSplitNext(t *testing.T) { s := " a,b , c " + assert.Equal(t, "a", SplitNext(&s, ',')) assert.Equal(t, "b", SplitNext(&s, ',')) assert.Equal(t, "c", SplitNext(&s, ',')) - assert.Empty(t, s) + require.Empty(t, s) } diff --git a/internal/util/network_test.go b/internal/util/network_test.go index 9b2a9554..bcc1da4c 100644 --- a/internal/util/network_test.go +++ b/internal/util/network_test.go @@ -2,22 +2,15 @@ package util import ( "testing" + + "github.com/stretchr/testify/require" ) func TestGetValidNetInterfacesForWeb(t *testing.T) { ifaces, err := GetValidNetInterfacesForWeb() - if err != nil { - t.Fatalf("Cannot get net interfaces: %s", err) - } - if len(ifaces) == 0 { - t.Fatalf("No net interfaces found") - } - + require.Nilf(t, err, "Cannot get net interfaces: %s", err) + require.NotEmpty(t, ifaces, "No net interfaces found") for _, iface := range ifaces { - if len(iface.Addresses) == 0 { - t.Fatalf("No addresses found for %s", iface.Name) - } - - t.Logf("%v", iface) + require.NotEmptyf(t, iface.Addresses, "No addresses found for %s", iface.Name) } } From 9df093578110e672804ddd81d7ddb60bed903330 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Tue, 9 Feb 2021 20:44:41 +0300 Subject: [PATCH 107/141] Pull request: all: imp changelog Merge in DNS/adguard-home from imp-chlog to master Squashed commit of the following: commit 50805731db515d529ea583a591165b889a257a24 Author: Ainar Garipov Date: Tue Feb 9 20:30:26 2021 +0300 all: imp changelog --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eac27727..dc3ebd4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,7 +47,7 @@ and this project adheres to - Improved HTTP requests handling and timeouts ([#2343]). - Our snap package now uses the `core20` image as its base ([#2306]). - New build system and various internal improvements ([#2271], [#2276], [#2297], - [#2509], [#2552]). + [#2509], [#2552], [#2639], [#2646]). ### Deprecated @@ -107,6 +107,8 @@ and this project adheres to [#2552]: https://github.com/AdguardTeam/AdGuardHome/issues/2552 [#2589]: https://github.com/AdguardTeam/AdGuardHome/issues/2589 [#2630]: https://github.com/AdguardTeam/AdGuardHome/issues/2630 +[#2639]: https://github.com/AdguardTeam/AdGuardHome/issues/2639 +[#2646]: https://github.com/AdguardTeam/AdGuardHome/issues/2646 ## [v0.104.3] - 2020-11-19 From 771a32cc9d8eaab12c09af406f2a1a82d54c68bf Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Wed, 10 Feb 2021 15:00:39 +0300 Subject: [PATCH 108/141] Pull request: client: revert 5a1e04c2 Merge in DNS/adguard-home from revert-locales to master Updates #2643. Squashed commit of the following: commit 7fb729edbeee681bdcdd0a295e530bc6f776cbac Author: Ainar Garipov Date: Wed Feb 10 14:41:52 2021 +0300 all: reupd locales commit 28416bee63f5b318ad8c8fcfb86f531893b077d1 Author: Ainar Garipov Date: Wed Feb 10 14:33:41 2021 +0300 scripts: imp translations downloading commit d8c2bfe08d7f50597dedcb5b73059d16f329b71a Merge: 00b19782 9df09357 Author: Ainar Garipov Date: Wed Feb 10 14:33:10 2021 +0300 Merge branch 'master' into revert-locales commit 00b19782e2d3311ac4cc1c4b79a886fdf0d386b9 Merge: 5e3d2b74 64715045 Author: Ainar Garipov Date: Tue Feb 9 16:52:56 2021 +0300 Merge branch 'master' into revert-locales commit 5e3d2b74ca57637d6625a21fde1ffbdb28cc8b18 Merge: f34e2fe9 63e4adc0 Author: Ainar Garipov Date: Tue Feb 9 13:17:30 2021 +0300 Merge branch 'master' into revert-locales commit f34e2fe9248feb9f1087be1b17ce187371896603 Author: Ainar Garipov Date: Tue Feb 9 12:47:47 2021 +0300 client: revert 5a1e04c2 --- .gitattributes | 5 +--- client/src/__locales/be.json | 4 ---- client/src/__locales/bg.json | 2 -- client/src/__locales/cs.json | 14 +++++++---- client/src/__locales/da.json | 14 +++++++---- client/src/__locales/de.json | 10 ++++++-- client/src/__locales/en.json | 14 +++++++---- client/src/__locales/es.json | 14 +++++++---- client/src/__locales/fa.json | 4 ---- client/src/__locales/fr.json | 14 +++++++---- client/src/__locales/hr.json | 4 ---- client/src/__locales/hu.json | 4 ---- client/src/__locales/id.json | 4 ---- client/src/__locales/it.json | 14 +++++++---- client/src/__locales/ja.json | 12 +++++++--- client/src/__locales/ko.json | 10 ++++++-- client/src/__locales/nl.json | 14 +++++++---- client/src/__locales/no.json | 4 ---- client/src/__locales/pl.json | 14 +++++++---- client/src/__locales/pt-br.json | 14 +++++++---- client/src/__locales/pt-pt.json | 14 +++++++---- client/src/__locales/ro.json | 14 +++++++---- client/src/__locales/ru.json | 14 +++++++---- client/src/__locales/si-lk.json | 31 +++++++++++------------- client/src/__locales/sk.json | 11 +++++---- client/src/__locales/sl.json | 12 +++++++--- client/src/__locales/sr-cs.json | 4 ---- client/src/__locales/sv.json | 3 --- client/src/__locales/th.json | 3 --- client/src/__locales/tr.json | 12 +++++++--- client/src/__locales/vi.json | 33 ++++++++++++++++++++++---- client/src/__locales/zh-cn.json | 12 +++++++--- client/src/__locales/zh-hk.json | 4 ---- client/src/__locales/zh-tw.json | 4 +--- scripts/translations/download.js | 22 +++++++++++++---- scripts/translations/package-lock.json | 2 +- 36 files changed, 242 insertions(+), 142 deletions(-) diff --git a/.gitattributes b/.gitattributes index 0e3eb755..b2b017bd 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,4 +1 @@ -client/* linguist-vendored -client/src/__locales/*.json binary -# Make an exception for the english locale, as it is the base one. -client/src/__locales/en.json -binary +client/* linguist-vendored diff --git a/client/src/__locales/be.json b/client/src/__locales/be.json index f34bd8d9..e2d35a79 100644 --- a/client/src/__locales/be.json +++ b/client/src/__locales/be.json @@ -269,7 +269,6 @@ "source_label": "Крыніца", "found_in_known_domain_db": "Знойдзены ў базе вядомых даменаў.", "category_label": "Катэгорыя", - "rule_label": "Правіла", "list_label": "Спіс", "unknown_filter": "Невядомы фільтр {{filterId}}", "known_tracker": "Вядомы трэкер", @@ -330,7 +329,6 @@ "encryption_config_saved": "Налады шыфравання захаваны", "encryption_server": "Імя сервера", "encryption_server_enter": "Увядзіце ваша даменавае імя", - "encryption_server_desc": "Для выкарыстання HTTPS вам трэба ўвесці імя сервера, якое падыходзіць вашаму SSL-сертыфікату.", "encryption_redirect": "Аўтаматычна перанакіроўваць на HTTPS", "encryption_redirect_desc": "Калі ўлучана, AdGuard Home будзе аўтаматычна перанакіроўваць вас з HTTP на HTTPS адрас.", "encryption_https": "Порт HTTPS", @@ -386,7 +384,6 @@ "client_edit": "Рэдагаваць кліента", "client_identifier": "Ідэнтыфікатар", "ip_address": "IP-адрас", - "client_identifier_desc": "Кліенты могуць быць ідэнтыфікаваны па IP-адрасе, CIDR ці MAC-адрасу. Звярніце ўвагу, што выкарыстанне MAC як ідэнтыфікатара магчыма, толькі калі AdGuard Home таксама з'яўляецца і <0>DHCP-серверам", "form_enter_ip": "Увядзіце IP", "form_enter_mac": "Увядзіце MAC", "form_enter_id": "Увядзіце ідэнтыфікатар", @@ -529,7 +526,6 @@ "check_ip": "IP-адрасы: {{ip}}", "check_cname": "CNAME: {{cname}}", "check_reason": "Прычына: {{reason}}", - "check_rule": "Правіла: {{rule}}", "check_service": "Назва сэрвісу: {{service}}", "service_name": "Назва сэрвіса", "check_not_found": "Не знойдзена ў вашым спісе фільтраў", diff --git a/client/src/__locales/bg.json b/client/src/__locales/bg.json index 7cf95826..2ae2c568 100644 --- a/client/src/__locales/bg.json +++ b/client/src/__locales/bg.json @@ -144,7 +144,6 @@ "source_label": "Източник", "found_in_known_domain_db": "Намерен в списъците с домейни.", "category_label": "Категория", - "rule_label": "Правило", "unknown_filter": "Непознат филтър {{filterId}}", "install_welcome_title": "Добре дошли в AdGuard Home!", "install_welcome_desc": "AdGuard Home e мрежово решение за блокиране на реклами и тракери на DNS ниво. Създадено е за да ви даде пълен контрол над мрежата и всичките ви устройства, без да е необходимо допълнително инсталиране на друг софтуер.", @@ -202,7 +201,6 @@ "encryption_config_saved": "Конфигурацията е успешно записана", "encryption_server": "Име на сървъра", "encryption_server_enter": "Въведете име на домейна", - "encryption_server_desc": "За да използвате HTTPS, трябва името на сървъра да съвпада с това на SSL сертификата.", "encryption_redirect": "Автоматично пренасочване към HTTPS", "encryption_redirect_desc": "Служи за автоматично пренасочване от HTTP към HTTPS на страницата за Администрация в AdGuard Home.", "encryption_https": "HTTPS порт", diff --git a/client/src/__locales/cs.json b/client/src/__locales/cs.json index e813a3ea..ca5d8a5c 100644 --- a/client/src/__locales/cs.json +++ b/client/src/__locales/cs.json @@ -32,6 +32,7 @@ "form_error_ip_format": "Neplatný formát IP", "form_error_mac_format": "Neplatný formát MAC", "form_error_client_id_format": "Neplatný formát ID klienta", + "form_error_server_name": "Neplatný název serveru", "form_error_positive": "Musí být větší než 0", "form_error_negative": "Musí být rovno nebo větší než 0", "range_end_error": "Musí být větší než začátek rozsahu", @@ -249,8 +250,13 @@ "blocking_ipv6": "Blokování IPv6", "dns_over_https": "DNS přes HTTPS", "dns_over_tls": "DNS přes TLS", + "dns_over_quic": "DNS skrze QUIC", + "client_id": "ID klienta", + "client_id_placeholder": "Zadejte ID klienta", + "client_id_desc": "Různé klienty lze identifikovat pomocí speciálního ID klienta. Zde se můžete dozvědět více o tom, jak klienty identifikovat.", "download_mobileconfig_doh": "Stáhnout .mobileconfig pro DNS skrze HTTPS", "download_mobileconfig_dot": "Stáhnout .mobileconfig pro DNS skrze TLS", + "download_mobileconfig": "Stáhnout konfigurační soubor", "plain_dns": "Čisté DNS", "form_enter_rate_limit": "Zadejte rychlostní limit", "rate_limit": "Rychlostní limit", @@ -269,7 +275,7 @@ "source_label": "Zdroj", "found_in_known_domain_db": "Nalezeno v databázi známých domén", "category_label": "Kategorie", - "rule_label": "Pravidlo", + "rule_label": "Pravidla", "list_label": "Seznam", "unknown_filter": "Neznámý filtr {{filterId}}", "known_tracker": "Známý slídič", @@ -330,7 +336,7 @@ "encryption_config_saved": "Konfigurace šifrování byla uložena", "encryption_server": "Název serveru", "encryption_server_enter": "Zadejte název domény", - "encryption_server_desc": "Abyste mohli používat protokol HTTPS, musíte zadat název serveru, který odpovídá vašemu certifikátu SSL.", + "encryption_server_desc": "Abyste mohli používat HTTPS, musíte zadat název serveru, který odpovídá vašemu certifikátu SSL nebo zástupnému certifikátu. Pokud není pole nastaveno, bude přijímat připojení TLS pro libovolnou doménu.", "encryption_redirect": "Automaticky přesměrovat na HTTPS", "encryption_redirect_desc": "Pokud je zaškrtnuto, AdGuard Home vás automaticky přesměruje z adres HTTP na HTTPS.", "encryption_https": "HTTPS port", @@ -386,7 +392,7 @@ "client_edit": "Upravit klienta", "client_identifier": "Identifikátor", "ip_address": "IP adresa", - "client_identifier_desc": "Klienti můžou být identifikováni podle IP adresy, CIDR nebo MAC adresy. Upozorňujeme, že použití MAC jako identifikátoru je možné pouze v případě, že je AdGuard Home také <0>DHCP server", + "client_identifier_desc": "Klienti můžou být identifikováni podle IP adresy, CIDR, MAC adresy nebo speciálního ID klienta (může být použito pro DoT/DoH/DoQ). <0>Zde se můžete dozvědět více o tom, jak klienty identifikovat.", "form_enter_ip": "Zadejte IP", "form_enter_mac": "Zadejte MAC", "form_enter_id": "Zadejte identifikátor", @@ -430,6 +436,7 @@ "setup_dns_privacy_other_3": "<0>dnscrypt-proxy podporuje <1>DNS-přes-HTTPS.", "setup_dns_privacy_other_4": "<0>Mozilla Firefox podporuje <1>DNS-přes-HTTPS.", "setup_dns_privacy_other_5": "Další implementace naleznete <0>zde a <1>zde.", + "setup_dns_privacy_ioc_mac": "Konfigurace pro iOS a macOS", "setup_dns_notice": "Pro použití <1>DNS-přes-HTTPS nebo <1>DNS-přes-TLS potřebujete v nastaveních AdGuard Home <0>nakonfigurovat šifrování.", "rewrite_added": "Přesměrování DNS pro „{{key}}“ úspěšně přidáno", "rewrite_deleted": "Přesměrování DNS pro „{{key}}“ úspěšně smazáno", @@ -529,7 +536,6 @@ "check_ip": "IP adresy: {{ip}}", "check_cname": "CNAME: {{cname}}", "check_reason": "Důvod: {{reason}}", - "check_rule": "Pravidlo: {{rule}}", "check_service": "Název služby: {{service}}", "service_name": "Název služby", "check_not_found": "Nenalezeno ve Vašich seznamech filtrů", diff --git a/client/src/__locales/da.json b/client/src/__locales/da.json index 1e80a324..97105ad3 100644 --- a/client/src/__locales/da.json +++ b/client/src/__locales/da.json @@ -32,6 +32,7 @@ "form_error_ip_format": "Ugyldigt IP-format", "form_error_mac_format": "Ugyldigt MAC-format", "form_error_client_id_format": "Ugyldigt klient-ID-format", + "form_error_server_name": "Ugyldigt servernavn", "form_error_positive": "Skal være større end 0", "form_error_negative": "Skal være lig med 0 eller større", "range_end_error": "Skal være større end starten af ​​intervallet", @@ -249,8 +250,13 @@ "blocking_ipv6": "IPv6-blokering", "dns_over_https": "DNS-over-HTTPS", "dns_over_tls": "DNS-over-TLS", + "dns_over_quic": "DNS-over-Quic", + "client_id": "Klient-ID", + "client_id_placeholder": "Indtast klient-ID", + "client_id_desc": "Forskellige klienter kan identificeres ved hjælp af et specielt klient-ID. Her kan du lære mere om, hvordan du identificerer klienter.", "download_mobileconfig_doh": "Download .mobileconfig til DNS-over-HTTPS", "download_mobileconfig_dot": "Download .mobileconfig til DNS-over-TLS", + "download_mobileconfig": "Download konfigurationsfil", "plain_dns": "Almindelig DNS", "form_enter_rate_limit": "Indtast hyppighedsgrænse", "rate_limit": "Hyppighedsgrænse", @@ -269,7 +275,7 @@ "source_label": "Kilde", "found_in_known_domain_db": "Fundet i databasen med kendte domæner.", "category_label": "Kategori", - "rule_label": "Regel", + "rule_label": "Regel(regler)", "list_label": "Liste", "unknown_filter": "Ukendt filter {{filterId}}", "known_tracker": "Kendt tracker", @@ -330,7 +336,7 @@ "encryption_config_saved": "Krypteringskonfiguration gemt", "encryption_server": "Servernavn", "encryption_server_enter": "Indtast dit domænenavn", - "encryption_server_desc": "For at kunne bruge HTTPS skal du indtaste servernavnet, der matcher dit SSL-certifikat.", + "encryption_server_desc": "For at kunne bruge HTTPS skal du indtaste det servernavn, der matcher dit SSL-certifikat eller wildcard-certifikat. Hvis feltet ikke er indstillet, accepterer det TLS-forbindelser til ethvert domæne.", "encryption_redirect": "Omdiriger automatisk til HTTPS", "encryption_redirect_desc": "Hvis afkrydset, vil AdGuard Home automatisk omdirigere dig fra HTTP til HTTPS-adresser.", "encryption_https": "HTTPS-port", @@ -386,7 +392,7 @@ "client_edit": "Rediger Klient", "client_identifier": "Identifikator", "ip_address": "IP-adresse", - "client_identifier_desc": "Klienter kan identificeres ud fra IP-adressen, CIDR eller MAC-adressen. Bemærk, at det kun er muligt at bruge MAC som identifikator, hvis AdGuard Home også er en <0>DHCP-server", + "client_identifier_desc": "Klienter kan identificeres ud fra IP-adressen, CIDR eller MAC-adressen eller et specielt klient-ID (kan bruges til DoT/DoH/DoQ). <0>Her kan du lære mere om, hvordan du identificerer klienter.", "form_enter_ip": "Indtast IP", "form_enter_mac": "Indtast MAC", "form_enter_id": "Indtast identifikator", @@ -430,6 +436,7 @@ "setup_dns_privacy_other_3": "<0>dnscrypt-proxy understøtter <1>DNS-over-HTTPS.", "setup_dns_privacy_other_4": "<0>Mozilla Firefox understøtter <1>DNS-over-HTTPS.", "setup_dns_privacy_other_5": "Du kan finde flere implementeringer <0>her og <1>her.", + "setup_dns_privacy_ioc_mac": "iOS- og macOS-konfiguration", "setup_dns_notice": "For at kunne bruge <1>DNS-over-HTTPS eller <1>DNS-over-TLS, skal du <0>konfigurere Krypteringen i indstillingerne i AdGuard Home.", "rewrite_added": "DNS-omskrivning for \"{{key}}\" blev tilføjet", "rewrite_deleted": "DNS-omskrivning for \"{{key}}\" blev slettet", @@ -529,7 +536,6 @@ "check_ip": "IP-adresser: {{ip}}", "check_cname": "CNAME: {{cname}}", "check_reason": "Årsag: {{reason}}", - "check_rule": "Regel: {{rule}}", "check_service": "Servicenavn: {{service}}", "service_name": "Navn på tjeneste", "check_not_found": "Ikke fundet i dine filterlister", diff --git a/client/src/__locales/de.json b/client/src/__locales/de.json index 2a63b548..7e35661e 100644 --- a/client/src/__locales/de.json +++ b/client/src/__locales/de.json @@ -32,6 +32,7 @@ "form_error_ip_format": "Ungültiges IPv4-Format", "form_error_mac_format": "Ungültiges MAC-Format", "form_error_client_id_format": "Ungültiges Client-ID-Format", + "form_error_server_name": "Ungültiger Servername", "form_error_positive": "Muss größer als 0 sein.", "form_error_negative": "Muss gleich oder größer als 0 (Null) sein", "range_end_error": "Muss größer als der Bereichsbeginn sein", @@ -249,8 +250,13 @@ "blocking_ipv6": "IPv6-Sperren", "dns_over_https": "DNS-over-HTTPS (DNS-Abrage über HTTPS)", "dns_over_tls": "DNS-over-TLS (DNS-Abrage über TLS)", + "dns_over_quic": "DNS-over-QUIC", + "client_id": "Client-ID", + "client_id_placeholder": "Client-ID eingeben", + "client_id_desc": "Verschiedene Clients können durch eine spezielle Client-ID identifiziert werden. Hier können Sie mehr darüber erfahren, wie Sie Clients identifizieren können.", "download_mobileconfig_doh": ".mobileconfig für DNS-über-HTTPS herunterladen", "download_mobileconfig_dot": ".mobileconfig für DNS-über-TLS herunterladen", + "download_mobileconfig": "Konfigurationsdatei herunterladen", "plain_dns": "Einfaches DNS", "form_enter_rate_limit": "Begrenzungswert eingeben", "rate_limit": "Begrenzungswert", @@ -269,7 +275,7 @@ "source_label": "Quelle", "found_in_known_domain_db": "In der Datenbank der bekannten Domains gefunden.", "category_label": "Kategorie", - "rule_label": "Regel", + "rule_label": "Regel(n)", "list_label": "Liste", "unknown_filter": "Unbekannter Filter {{filterId}}", "known_tracker": "Bekannte Tracker", @@ -430,6 +436,7 @@ "setup_dns_privacy_other_3": "<0>dnscrypt-proxy unterstützt <1>DNS-over-HTTPS.", "setup_dns_privacy_other_4": "<0>Mozilla Firefox unterstützt <1>DNS-over-HTTPS.", "setup_dns_privacy_other_5": "Weitere Umsetzungen finden Sie <0>hier und <1>hier.", + "setup_dns_privacy_ioc_mac": "Konfiguration für iOS und macOS", "setup_dns_notice": "Um <1>DNS-over-HTTTPS oder <1>DNS-over-TLS verwenden zu können, müssen Sie in den AdGuard Home Einstellungen die <0>Verschlüsselung konfigurieren.", "rewrite_added": "DNS-Umschreibung für „{{key}}” erfolgreich hinzugefügt", "rewrite_deleted": "DNS-Umschreibung für „{{key}}” erfolgreich entfernt", @@ -529,7 +536,6 @@ "check_ip": "IP-Adressen: {{ip}}", "check_cname": "CNAME: {{cname}}", "check_reason": "Grund: {{reason}}", - "check_rule": "Regel: {{rule}}", "check_service": "Dienstname: {{service}}", "service_name": "Name des Dienstes", "check_not_found": "Nicht in Ihren Filterlisten enthalten", diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json index 5f37ac36..697a7307 100644 --- a/client/src/__locales/en.json +++ b/client/src/__locales/en.json @@ -32,6 +32,7 @@ "form_error_ip_format": "Invalid IP format", "form_error_mac_format": "Invalid MAC format", "form_error_client_id_format": "Invalid client ID format", + "form_error_server_name": "Invalid server name", "form_error_positive": "Must be greater than 0", "form_error_negative": "Must be equal to 0 or greater", "range_end_error": "Must be greater than range start", @@ -249,8 +250,13 @@ "blocking_ipv6": "Blocking IPv6", "dns_over_https": "DNS-over-HTTPS", "dns_over_tls": "DNS-over-TLS", + "dns_over_quic": "DNS-over-QUIC", + "client_id": "Client ID", + "client_id_placeholder": "Enter client ID", + "client_id_desc": "Different clients can be identified by a special client ID. Here you can learn more about how to identify clients.", "download_mobileconfig_doh": "Download .mobileconfig for DNS-over-HTTPS", "download_mobileconfig_dot": "Download .mobileconfig for DNS-over-TLS", + "download_mobileconfig": "Download configuration file", "plain_dns": "Plain DNS", "form_enter_rate_limit": "Enter rate limit", "rate_limit": "Rate limit", @@ -269,7 +275,7 @@ "source_label": "Source", "found_in_known_domain_db": "Found in the known domains database.", "category_label": "Category", - "rule_label": "Rule", + "rule_label": "Rule(s)", "list_label": "List", "unknown_filter": "Unknown filter {{filterId}}", "known_tracker": "Known tracker", @@ -330,7 +336,7 @@ "encryption_config_saved": "Encryption config saved", "encryption_server": "Server name", "encryption_server_enter": "Enter your domain name", - "encryption_server_desc": "In order to use HTTPS, you need to enter the server name that matches your SSL certificate.", + "encryption_server_desc": "In order to use HTTPS, you need to enter the server name that matches your SSL certificate or wildcard certificate. If the field is not set, it will accept TLS connections for any domain.", "encryption_redirect": "Redirect to HTTPS automatically", "encryption_redirect_desc": "If checked, AdGuard Home will automatically redirect you from HTTP to HTTPS addresses.", "encryption_https": "HTTPS port", @@ -386,7 +392,7 @@ "client_edit": "Edit Client", "client_identifier": "Identifier", "ip_address": "IP address", - "client_identifier_desc": "Clients can be identified by the IP address, CIDR, MAC address. Please note that using MAC as identifier is possible only if AdGuard Home is also a <0>DHCP server", + "client_identifier_desc": "Clients can be identified by the IP address, CIDR, MAC address or a special client ID (can be used for DoT/DoH/DoQ). <0>Here you can learn more about how to identify clients.", "form_enter_ip": "Enter IP", "form_enter_mac": "Enter MAC", "form_enter_id": "Enter identifier", @@ -430,6 +436,7 @@ "setup_dns_privacy_other_3": "<0>dnscrypt-proxy supports <1>DNS-over-HTTPS.", "setup_dns_privacy_other_4": "<0>Mozilla Firefox supports <1>DNS-over-HTTPS.", "setup_dns_privacy_other_5": "You will find more implementations <0>here and <1>here.", + "setup_dns_privacy_ioc_mac": "iOS and macOS configuration", "setup_dns_notice": "In order to use <1>DNS-over-HTTPS or <1>DNS-over-TLS, you need to <0>configure Encryption in AdGuard Home settings.", "rewrite_added": "DNS rewrite for \"{{key}}\" successfully added", "rewrite_deleted": "DNS rewrite for \"{{key}}\" successfully deleted", @@ -529,7 +536,6 @@ "check_ip": "IP addresses: {{ip}}", "check_cname": "CNAME: {{cname}}", "check_reason": "Reason: {{reason}}", - "check_rule": "Rule: {{rule}}", "check_service": "Service name: {{service}}", "service_name": "Service name", "check_not_found": "Not found in your filter lists", diff --git a/client/src/__locales/es.json b/client/src/__locales/es.json index 8b335431..3be25fac 100644 --- a/client/src/__locales/es.json +++ b/client/src/__locales/es.json @@ -32,6 +32,7 @@ "form_error_ip_format": "Formato IP no válido", "form_error_mac_format": "Formato MAC no válido", "form_error_client_id_format": "Formato de ID de cliente no válido", + "form_error_server_name": "Nombre de servidor no válido", "form_error_positive": "Debe ser mayor que 0", "form_error_negative": "Debe ser igual o mayor que 0", "range_end_error": "Debe ser mayor que el inicio de rango", @@ -249,8 +250,13 @@ "blocking_ipv6": "Bloqueo de IPv6", "dns_over_https": "DNS mediante HTTPS", "dns_over_tls": "DNS mediante TLS", + "dns_over_quic": "DNS-over-QUIC", + "client_id": "ID de cliente", + "client_id_placeholder": "Ingresa tu ID de cliente", + "client_id_desc": "Varios clientes se pueden identificar mediante un ID de cliente especial. Aquí puede aprender más sobre cómo identificar clientes.", "download_mobileconfig_doh": "Descargar .mobileconfig para DNS mediante HTTPS", "download_mobileconfig_dot": "Descargar .mobileconfig para DNS mediante TLS", + "download_mobileconfig": "Descargar el archivo de configuración", "plain_dns": "DNS simple", "form_enter_rate_limit": "Ingresa el límite de cantidad", "rate_limit": "Límite de cantidad", @@ -269,7 +275,7 @@ "source_label": "Fuente", "found_in_known_domain_db": "Encontrado en la base de datos de dominios conocidos.", "category_label": "Categoría", - "rule_label": "Regla", + "rule_label": "Regla(s)", "list_label": "Lista", "unknown_filter": "Filtro desconocido {{filterId}}", "known_tracker": "Rastreador conocido", @@ -330,7 +336,7 @@ "encryption_config_saved": "Configuración de cifrado guardado", "encryption_server": "Nombre del servidor", "encryption_server_enter": "Ingresa el nombre del dominio", - "encryption_server_desc": "Para utilizar HTTPS, debes ingresar el nombre del servidor que coincida con tu certificado SSL.", + "encryption_server_desc": "Para utilizar HTTPS, debes ingresar el nombre del servidor que coincida con tu certificado SSL o certificado Wildcard. Si el campo no está establecido, el servidor aceptará conexiones TLS para cualquier dominio.", "encryption_redirect": "Redireccionar a HTTPS automáticamente", "encryption_redirect_desc": "Si está marcado, AdGuard Home redireccionará automáticamente de HTTP a las direcciones HTTPS.", "encryption_https": "Puerto HTTPS", @@ -386,7 +392,7 @@ "client_edit": "Editar cliente", "client_identifier": "Identificador", "ip_address": "Dirección IP", - "client_identifier_desc": "Los clientes pueden ser identificados por la dirección IP, MAC y CIDR. Ten en cuenta que el uso de MAC como identificador solo es posible si AdGuard Home también es un <0>servidor DHCP", + "client_identifier_desc": "Los clientes pueden ser identificados por la dirección IP, MAC, CIDR o un especial ID de cliente (puede ser utilizado para DoT/DoH/DoQ). <0>Aquí puede obtener más información sobre cómo identificar clientes.", "form_enter_ip": "Ingresa la IP", "form_enter_mac": "Ingresa la MAC", "form_enter_id": "Ingresa el identificador", @@ -430,6 +436,7 @@ "setup_dns_privacy_other_3": "<0>dnscrypt-proxy soporta <1>DNS mediante HTTPS.", "setup_dns_privacy_other_4": "<0>Mozilla Firefox soporta <1>DNS mediante HTTPS.", "setup_dns_privacy_other_5": "Encontrarás más implementaciones <0>aquí y <1>aquí.", + "setup_dns_privacy_ioc_mac": "La configuración de iOS y macOS ", "setup_dns_notice": "Para utilizar <1>DNS mediante HTTPS o <1>DNS mediante TLS, debes <0>configurar el cifrado en la configuración de AdGuard Home.", "rewrite_added": "Reescritura DNS para \"{{key}}\" añadido correctamente", "rewrite_deleted": "Reescritura DNS para \"{{key}}\" eliminado correctamente", @@ -529,7 +536,6 @@ "check_ip": "Direcciones IP: {{ip}}", "check_cname": "CNAME: {{cname}}", "check_reason": "Razón: {{reason}}", - "check_rule": "Regla: {{rule}}", "check_service": "Nombre del servicio: {{service}}", "service_name": "Nombre del servicio", "check_not_found": "No se ha encontrado en tus listas de filtros", diff --git a/client/src/__locales/fa.json b/client/src/__locales/fa.json index 105ccddd..af9ab9c6 100644 --- a/client/src/__locales/fa.json +++ b/client/src/__locales/fa.json @@ -239,7 +239,6 @@ "source_label": "منبع", "found_in_known_domain_db": "در پایگاه داده دامنه های شناخته شده پیدا شد", "category_label": "دسته بندی", - "rule_label": "دستور", "list_label": "لیست", "unknown_filter": "فیلتر ناشناخته {{filterId}}", "known_tracker": "ردیاب های شناخته شده", @@ -300,7 +299,6 @@ "encryption_config_saved": "پیکربندی رمزگذاری ذخیره شد", "encryption_server": "نام سرور", "encryption_server_enter": "نام دامنه خود را وارد کنید", - "encryption_server_desc": "به منظور استفاده از HTTPS،شما باید نام سرور مطابق با گواهینامه اِس اِس اِل را وارد کنید.", "encryption_redirect": "تغییر مسیر خودکار به HTTPS", "encryption_redirect_desc": "اگر انتخاب شده باشد،AdGuard Home خودکار شما را از آدرس HTTP به HTTPS منتقل می کند", "encryption_https": "پورت HTTPS", @@ -354,7 +352,6 @@ "client_edit": "ویرایش کلاینت", "client_identifier": "احراز با", "ip_address": "آدرس آی پی", - "client_identifier_desc": "کلاینت میتواند با آدرس آی پی یا آدرس مَک احراز شود. لطفا توجه کنید،که استفاده از مَک بعنوان عامل احراز زمانی امکان دارد که AdGuard Home نیز <0>سرور DHCP باشد", "form_enter_ip": "آی پی را وارد کنید", "form_enter_mac": "مَک را وارد کنید", "form_enter_id": "خطای احرازکننده", @@ -487,7 +484,6 @@ "check_ip": "آدرس آی پی: {{ip}}", "check_cname": "CNAME: {{cname}}", "check_reason": "علت: {{reason}}", - "check_rule": "دستور: {{rule}}", "check_service": "نام سرویس: {{service}}", "check_not_found": "در لیست فیلترهای شما یافت نشد", "client_confirm_block": "آیا واقعا میخواهید کلاینت \"{{ip}}\" را مسدود کنید؟", diff --git a/client/src/__locales/fr.json b/client/src/__locales/fr.json index 589acb71..36ec94e7 100644 --- a/client/src/__locales/fr.json +++ b/client/src/__locales/fr.json @@ -32,6 +32,7 @@ "form_error_ip_format": "Format IPv4 invalide", "form_error_mac_format": "Format MAC invalide", "form_error_client_id_format": "Format d'ID client non valide", + "form_error_server_name": "Nom de serveur invalide", "form_error_positive": "Doit être supérieur à 0", "form_error_negative": "Doit être égal à 0 ou supérieur", "range_end_error": "Doit être supérieur au début de la gamme", @@ -249,8 +250,13 @@ "blocking_ipv6": "Blocage IPv6", "dns_over_https": "DNS-over-HTTPS", "dns_over_tls": "DNS-over-TLS", + "dns_over_quic": "DNS-over-QUIC", + "client_id": "ID du client", + "client_id_placeholder": "Saisissez le ID du client", + "client_id_desc": "Les clients différents peuvent être identifiés par aide d'un ID client spécial. Vous trouverez plus d'information sur l'identification des clients ici .", "download_mobileconfig_doh": "Télécharger .mobileconfig pour DNS-sur-HTTPS", "download_mobileconfig_dot": "Télécharger .mobileconfig pour DNS-sur-TLS", + "download_mobileconfig": "Télécharger le fichier de configuration", "plain_dns": "DNS brut", "form_enter_rate_limit": "Entrez la limite de taux", "rate_limit": "Limite de taux", @@ -269,7 +275,7 @@ "source_label": "Source", "found_in_known_domain_db": "Trouvé dans la base de données des domaines connus", "category_label": "Catégorie", - "rule_label": "Règle", + "rule_label": "Règle(s)", "list_label": "Liste", "unknown_filter": "Filtre inconnu {{filterId}}", "known_tracker": "Pisteur connu", @@ -330,7 +336,7 @@ "encryption_config_saved": "Configuration de chiffrement enregistrée", "encryption_server": "Nom du serveur", "encryption_server_enter": "Entrez votre nom de domaine", - "encryption_server_desc": "Pour utiliser HTTPS, vous devez entrer le nom du serveur qui correspond à votre certificat SSL.", + "encryption_server_desc": "Pour utiliser HTTPS, vous devez saisir le nom du serveur qui correspond à votre certificat SSL ou wildcard. Si le champ n'est pas configuré, les connexions TLS pour tous les domaines seront acceptées.", "encryption_redirect": "Redirection automatiquement vers HTTPS", "encryption_redirect_desc": "Si coché, AdGuard Home vous redirigera automatiquement d'adresses HTTP vers HTTPS.", "encryption_https": "Port HTTPS", @@ -386,7 +392,7 @@ "client_edit": "Modifier le client", "client_identifier": "Identifiant", "ip_address": "Adresse IP", - "client_identifier_desc": "Les clients peuvent être identifiés par les adresses IP ou MAC. Veuillez noter que l'utilisation de l'adresse MAC comme identifiant est possible uniquement si AdGuard Home est aussi un <0>serveur DHCP", + "client_identifier_desc": "Les clients peuvent être identifiés par les adresses IP, CIDR, MAC ou un ID client spécial (qui peut être utilisé pour DoT/DoH/DoQ). Vous trouverez plus d'information sur l'identification des clients <0>ici .", "form_enter_ip": "Saisissez l'IP", "form_enter_mac": "Saisissez MAC", "form_enter_id": "Entrer identifiant", @@ -430,6 +436,7 @@ "setup_dns_privacy_other_3": "<0>dnscrypt-proxy supporte le <1>DNS-over-HTTPS.", "setup_dns_privacy_other_4": "<0>Mozilla Firefox supporte le <1>DNS-over-HTTPS.", "setup_dns_privacy_other_5": "Vous trouverez plus d'implémentations <0>ici et <1>ici.", + "setup_dns_privacy_ioc_mac": "Configuration sur iOS et macOS", "setup_dns_notice": "Pour utiliser le <1>DNS-over-HTTPS ou le <1>DNS-over-TLS, vous devez <0>configurer le Chiffrement dans les paramètres de AdGuard Home.", "rewrite_added": "Réécriture DNS pour \"{{key}}\" ajoutée", "rewrite_deleted": "Réécriture DNS pour \"{{key}}\" supprimée", @@ -529,7 +536,6 @@ "check_ip": "Adresses IP : {{ip}}", "check_cname": "CNAME : {{cname}}", "check_reason": "Raison : {{reason}}", - "check_rule": "Règle : {{rule}}", "check_service": "Nom du service : {{service}}", "service_name": "Nom du service", "check_not_found": "Introuvable dans vos listes de filtres", diff --git a/client/src/__locales/hr.json b/client/src/__locales/hr.json index baab39ed..7b02b4d0 100644 --- a/client/src/__locales/hr.json +++ b/client/src/__locales/hr.json @@ -269,7 +269,6 @@ "source_label": "Izvor", "found_in_known_domain_db": "Pronađeno u bazi poznatih domena.", "category_label": "Kategorija", - "rule_label": "Pravilo", "list_label": "Popis", "unknown_filter": "Nepoznati filtar {{filterId}}", "known_tracker": "Poznati pratitelj", @@ -330,7 +329,6 @@ "encryption_config_saved": "Spremljene postavke šifriranja", "encryption_server": "Naziv poslužitelja", "encryption_server_enter": "Unesite naziv domene", - "encryption_server_desc": "Kako biste koristili HTTPS, morate unijeti naziv poslužitelja koji odgovara vašem SSL certifikatu.", "encryption_redirect": "Automatski preusmjeri na HTTPS", "encryption_redirect_desc": "Ako je omogućeno, AdGuard Home će vas automatski preusmjeravati s HTTP na HTTPS adrese.", "encryption_https": "HTTPS port", @@ -386,7 +384,6 @@ "client_edit": "Uredi klijenta", "client_identifier": "Identifikator", "ip_address": "IP adresa", - "client_identifier_desc": "Klijenti se mogu prepoznati po IP adresi, CIDR-u ili MAC adresi. Imajte na umu da je upotreba MAC-a kao identifikatora, moguća samo ako je AdGuard Home također i <0>DHCP poslužitelj", "form_enter_ip": "Unesite IP adresu", "form_enter_mac": "Unesite MAC adresu", "form_enter_id": "Unesi identifikator", @@ -529,7 +526,6 @@ "check_ip": "IP adrese: {{ip}}", "check_cname": "CNAME: {{cname}}", "check_reason": "Razlog: {{reason}}", - "check_rule": "Pravilo: {{rule}}", "check_service": "Naziv usluge: {{service}}", "service_name": "Naziv usluge", "check_not_found": "Nije pronađeno na vašoj listi filtara", diff --git a/client/src/__locales/hu.json b/client/src/__locales/hu.json index bda17f80..73b31907 100644 --- a/client/src/__locales/hu.json +++ b/client/src/__locales/hu.json @@ -269,7 +269,6 @@ "source_label": "Forrás", "found_in_known_domain_db": "Benne van az ismert domainek listájában.", "category_label": "Kategória", - "rule_label": "Szabály", "list_label": "Lista", "unknown_filter": "Ismeretlen szűrő: {{filterId}}", "known_tracker": "Ismert követő", @@ -330,7 +329,6 @@ "encryption_config_saved": "Titkosítási beállítások mentve", "encryption_server": "Szerver neve", "encryption_server_enter": "Adja meg az Ön domain címét", - "encryption_server_desc": "A HTTPS használatához be kell írnia egy, az SSL-tanúsítvánnyal megegyező kiszolgálónevet.", "encryption_redirect": "Automatikus átirányítás HTTPS kapcsolatra", "encryption_redirect_desc": "Ha be van jelölve, az AdGuard Home automatikusan átirányítja a HTTP kapcsolatokat a biztonságos HTTPS protokollra.", "encryption_https": "HTTPS port", @@ -386,7 +384,6 @@ "client_edit": "Kliens módosítása", "client_identifier": "Azonosító", "ip_address": "IP cím", - "client_identifier_desc": "A klienseket be lehet azonosítani IP-cím, CIDR, valamint MAC-cím alapján. Kérjük, vegye figyelembe, hogy a MAC-cím alapján történő azonosítás csak akkor működik, ha az AdGuard Home egyben <0>DHCP szerverként is funkcionál", "form_enter_ip": "IP-cím megadása", "form_enter_mac": "MAC-cím megadása", "form_enter_id": "Azonosító megadása", @@ -529,7 +526,6 @@ "check_ip": "IP-címek: {{ip}}", "check_cname": "CNAME: {{cname}}", "check_reason": "Indok: {{reason}}", - "check_rule": "Szabály: {{rule}}", "check_service": "Szolgáltatás neve: {{service}}", "service_name": "Szolgáltatás neve", "check_not_found": "Nem található az Ön szűrőlistái között", diff --git a/client/src/__locales/id.json b/client/src/__locales/id.json index 6d0a3d39..2bd91ea8 100644 --- a/client/src/__locales/id.json +++ b/client/src/__locales/id.json @@ -269,7 +269,6 @@ "source_label": "Sumber", "found_in_known_domain_db": "Ditemukan di database domain dikenal", "category_label": "Kategori", - "rule_label": "Aturan", "list_label": "Daftar", "unknown_filter": "Penyaringan {{filterId}} tidak dikenal", "known_tracker": "Pelacak yang dikenal", @@ -330,7 +329,6 @@ "encryption_config_saved": "Pengaturan enkripsi telah tersimpan", "encryption_server": "Nama server", "encryption_server_enter": "Masukkan nama domain anda", - "encryption_server_desc": "Untuk menggunakan HTTPS, Anda harus memasukkan nama server yang cocok dengan sertifikat SSL Anda.", "encryption_redirect": "Alihkan ke HTTPS secara otomatis", "encryption_redirect_desc": "Jika dicentang, AdGuard Home akan secara otomatis mengarahkan anda dari HTTP ke alamat HTTPS.", "encryption_https": "Port HTTPS", @@ -386,7 +384,6 @@ "client_edit": "Ubah Klien", "client_identifier": "Identifikasi", "ip_address": "Alamat IP", - "client_identifier_desc": "Klien dapat diidentifikasi dengan alamat IP atau alamat MAC. Harap dicatat bahwa menggunakan MAC sebagai pengidentifikasi hanya dimungkinkan jika AdGuard Home juga merupakan <0>server DHCP", "form_enter_ip": "Masukkan IP", "form_enter_mac": "Masukkan MAC", "form_enter_id": "Masukkan pengenal", @@ -529,7 +526,6 @@ "check_ip": "Alamat IP: {{ip}}", "check_cname": "CNAME: {{cname}}", "check_reason": "Alasan: {{reason}}", - "check_rule": "Aturan: {{rule}}", "check_service": "Nama layanan: {{service}}", "service_name": "Nama layanan", "check_not_found": "Tidak di temukan di daftar penyaringan anda", diff --git a/client/src/__locales/it.json b/client/src/__locales/it.json index 47f8aa71..5079165b 100644 --- a/client/src/__locales/it.json +++ b/client/src/__locales/it.json @@ -32,6 +32,7 @@ "form_error_ip_format": "Formato IPv4 non valido", "form_error_mac_format": "Formato MAC non valido", "form_error_client_id_format": "Formato ID cliente non valido", + "form_error_server_name": "Nome server non valido", "form_error_positive": "Deve essere maggiore di 0", "form_error_negative": "Deve essere maggiore o uguale a 0 (zero)", "range_end_error": "Deve essere maggiore dell'intervallo di inizio", @@ -249,8 +250,13 @@ "blocking_ipv6": "Blocca IPv6", "dns_over_https": "DNS su HTTPS", "dns_over_tls": "DNS su TLS", + "dns_over_quic": "DNS su Quic", + "client_id": "ID client", + "client_id_placeholder": "Inserisci ID client", + "client_id_desc": "Client differenti possono essere identificati da uno speciale ID. Qui potrai saperne di più sui metodi per identificarli.", "download_mobileconfig_doh": "Scarica .mobileconfig per DNS su HTTPS", "download_mobileconfig_dot": "Scarica .mobileconfig per DNS su TLS", + "download_mobileconfig": "Scarica file di configurazione", "plain_dns": "DNS semplice", "form_enter_rate_limit": "Imposta limite delle richieste", "rate_limit": "Limite delle richieste", @@ -269,7 +275,7 @@ "source_label": "Fonte", "found_in_known_domain_db": "Trovato nel database dei domini conosciuti.", "category_label": "Categoria", - "rule_label": "Regola", + "rule_label": "Regola(e)", "list_label": "Lista", "unknown_filter": "Filtro sconosciuto {{filterId}}", "known_tracker": "Tracker conosciuto", @@ -330,7 +336,7 @@ "encryption_config_saved": "Configurazione della crittografia salvata", "encryption_server": "Nome server", "encryption_server_enter": "Inserisci il tuo nome di dominio", - "encryption_server_desc": "Per utilizzare HTTPS, è necessario inserire il nome del server che corrisponde al certificato SSL.", + "encryption_server_desc": "Per utilizzare HTTPS, è necessario immettere il nome del server che corrisponde al certificato SSL o al certificato wildcard. Se il campo risulterà vuoto, accetterà connessioni TLS per qualsiasi dominio.", "encryption_redirect": "Reindirizza automaticamente a HTTPS", "encryption_redirect_desc": "Se selezionato, AdGuard Home ti reindirizzerà automaticamente da indirizzi HTTP a HTTPS.", "encryption_https": "Porta HTTPS", @@ -386,7 +392,7 @@ "client_edit": "Modifica Client", "client_identifier": "Identificatore", "ip_address": "Indirizzo IP", - "client_identifier_desc": "I client possono essere identificati dall indirizzo IP o dall' indirizzo MAC. Nota che l' utilizzo dell' indirizzo MAC come identificatore è consentito solo se AdGuard Home è anche il <0>server DHCP", + "client_identifier_desc": "I client possono essere identificati dall'indirizzo IP, CIDR, indirizzo MAC o un ID speciale (che può essere utilizzato per DoT/DoH/DoQ). <0>Qui potrai saperne di più sui metodi per identificarli.", "form_enter_ip": "Inserisci IP", "form_enter_mac": "Inserisci MAC", "form_enter_id": "Inserisci identificatore", @@ -430,6 +436,7 @@ "setup_dns_privacy_other_3": "<0>dnscrypt-proxy supporta <1>DNS su HTTPS.", "setup_dns_privacy_other_4": "<0>Mozilla Firefox supporta <1>DNS su HTTPS.", "setup_dns_privacy_other_5": "Troverai più implementazioni <0>qui e <1>qui.", + "setup_dns_privacy_ioc_mac": "configurazione iOS e macOS", "setup_dns_notice": "Per utilizzare <1>DNS su HTTPS o <1>DNS su TLS, è necessario <0>configurare la crittografia nelle impostazioni di AdGuard Home.", "rewrite_added": "Riscrittura DNS per \"{{key}}\" aggiunta correttamente", "rewrite_deleted": "La riscrittura DNS per \"{{key}}\" è stata eliminata correttamente", @@ -529,7 +536,6 @@ "check_ip": "Indirizzi IP: {{ip}}", "check_cname": "CNAME: {{cname}}", "check_reason": "Motivo: {{reason}}", - "check_rule": "Regola: {{rule}}", "check_service": "Nome servizio: {{service}}", "service_name": "Nome servizio", "check_not_found": "Non trovato negli elenchi dei filtri", diff --git a/client/src/__locales/ja.json b/client/src/__locales/ja.json index c40264c8..6c4ef890 100644 --- a/client/src/__locales/ja.json +++ b/client/src/__locales/ja.json @@ -32,6 +32,7 @@ "form_error_ip_format": "IPv4フォーマットではありません", "form_error_mac_format": "MACフォーマットではありません", "form_error_client_id_format": "Client IDの形式が無効です", + "form_error_server_name": "サーバ名が無効です", "form_error_positive": "0より大きい必要があります", "form_error_negative": "0以上である必要があります", "range_end_error": "範囲開始よりも大きくなければなりません", @@ -249,8 +250,13 @@ "blocking_ipv6": "ブロック中のIPv6", "dns_over_https": "DNS-over-HTTPS", "dns_over_tls": "DNS-over-TLS", + "dns_over_quic": "DNS-over-QUIC", + "client_id": "Client ID(クライアントID)", + "client_id_placeholder": "クライアントIDを入力してください", + "client_id_desc": "それぞれのクライアントは、特別なクライアントIDで識別できます。 ここでは、クライアントを特定する方法について詳しく知ることができます。", "download_mobileconfig_doh": "DNS-over-HTTPS用の .mobileconfig をダウンロード", "download_mobileconfig_dot": "DNS-over-TLS用の .mobileconfig をダウンロード", + "download_mobileconfig": "設定ファイルをダウンロードする", "plain_dns": "通常のDNS", "form_enter_rate_limit": "頻度制限を入力してください", "rate_limit": "頻度制限", @@ -330,7 +336,7 @@ "encryption_config_saved": "暗号化の設定を保存しました", "encryption_server": "サーバ名", "encryption_server_enter": "ドメイン名を入力してください", - "encryption_server_desc": "HTTPSを使用するには、SSL証明書と一致するサーバ名を入力する必要があります。", + "encryption_server_desc": "HTTPSを使用するには、SSL証明書またはワイルドカード証明書と一致するサーバー名を入力する必要があります。このフィールドが設定されていない場合は、任意のドメインのTLS接続を受け入れます。", "encryption_redirect": "HTTPSに自動的にリダイレクト", "encryption_redirect_desc": "チェックすると、AdGuard Homeは自動的にHTTPからHTTPSアドレスへリダイレクトします。", "encryption_https": "HTTPS ポート", @@ -386,7 +392,7 @@ "client_edit": "クライアントの編集", "client_identifier": "識別子", "ip_address": "IPアドレス", - "client_identifier_desc": "クライアントはIPアドレスまたはMACアドレスで識別できます。AdGuard Homeが<0>DHCPサーバでもある場合にのみ、識別子としてMACを使用することが可能であることにご注意ください。", + "client_identifier_desc": "クライアントは、IPアドレス、CIDR、MACアドレス、または特別なクライアントID(DoT/DoH/DoQで使用可能)によって識別することができます。<0>ここでは、クライアントの識別方法についてより詳しく説明しています。", "form_enter_ip": "IPアドレスを入力してください", "form_enter_mac": "MACアドレスを入力してください", "form_enter_id": "識別子を入力してください", @@ -430,6 +436,7 @@ "setup_dns_privacy_other_3": "<0>dnscrypt-proxyは<1>DNS-over-HTTPSをサポートします。", "setup_dns_privacy_other_4": "<0>Mozilla Firefoxは<1>DNS-over-HTTPSをサポートしています。", "setup_dns_privacy_other_5": "もっと多くの実装を<0>ここや<1>ここで見つけられます。", + "setup_dns_privacy_ioc_mac": "iOS と macOS での設定", "setup_dns_notice": "<1>DNS-over-HTTPSまたは<1>DNS-over-TLSを使用するには、AdGuard Home 設定の<0>暗号化設定が必要です。", "rewrite_added": "\"{{key}}\" のためのDNS書き換え情報を追加完了しました", "rewrite_deleted": "\"{{key}}\" のためのDNS書き換え情報を削除完了しました", @@ -529,7 +536,6 @@ "check_ip": "IPアドレス: {{ip}}", "check_cname": "CNAME: {{cname}}", "check_reason": "理由: {{reason}}", - "check_rule": "ルール: {{rule}}", "check_service": "サービス名: {{service}}", "service_name": "サービス名", "check_not_found": "フィルタ一覧には見つかりません", diff --git a/client/src/__locales/ko.json b/client/src/__locales/ko.json index eb80de35..c266c8c4 100644 --- a/client/src/__locales/ko.json +++ b/client/src/__locales/ko.json @@ -32,6 +32,7 @@ "form_error_ip_format": "잘못된 IP 형식", "form_error_mac_format": "잘못된 MAC 형식", "form_error_client_id_format": "잘못된 클라이언트 ID 형식", + "form_error_server_name": "유효하지 않은 서버 이름입니다", "form_error_positive": "0보다 커야 합니다", "form_error_negative": "반드시 0 이상이여야 합니다", "range_end_error": "입력 값은 범위의 시작 지점보다 큰 값 이여야 합니다.", @@ -249,8 +250,13 @@ "blocking_ipv6": "IPv6 차단", "dns_over_https": "DNS-over-HTTPS", "dns_over_tls": "DNS-over-TLS", + "dns_over_quic": "DNS-over-QUIC", + "client_id": "클라이언트 ID", + "client_id_placeholder": "클라이언트 ID 입력", + "client_id_desc": "클라이언트는 특별한 클라이언트 ID를 기반으로 구분됩니다. 여기에서 클라이언트를 구분하는 방법을 자세히 알아보세요.", "download_mobileconfig_doh": "DNS-over-HTTPS용 .mobileconfig 다운로드", "download_mobileconfig_dot": "DNS-over-TLS용 .mobileconfig 다운로드", + "download_mobileconfig": "설정 파일 내려받기", "plain_dns": "평문 DNS", "form_enter_rate_limit": "한도 제한 입력하기", "rate_limit": "한도 제한", @@ -386,7 +392,7 @@ "client_edit": "클라이언트 수정", "client_identifier": "식별자", "ip_address": "IP 주소", - "client_identifier_desc": "사용자는 IP 주소 또는 MAC 주소로 식별할 수 있지만 AdGuard Home이 <0>DHCP 서버인 경우에만 사용자는 MAC 주소로 식별할 수 있습니다.", + "client_identifier_desc": "클라이언트는 IP 주소, CIDR, MAC 주소 또는 특수 클라이언트 ID로 식별할 수 있습니다 (DoT/DoH/DoQ에 사용 가능). <0>여기에서 클라이언트를 식별하는 방법에 대한 자세한 내용은 확인하실 수 있습니다.", "form_enter_ip": "IP 입력", "form_enter_mac": "MAC 입력", "form_enter_id": "식별자 입력", @@ -430,6 +436,7 @@ "setup_dns_privacy_other_3": "<0>dnscrypt-proxy <1>DNS-over-HTTPS 지원합니다.", "setup_dns_privacy_other_4": "<0>Mozilla Firefox<1>DNS-over-HTTPS지원합니다.", "setup_dns_privacy_other_5": "<0>이곳이나 <1>이곳을 클릭하여 더 많은 구현에 대한 정보를 확인하세요.", + "setup_dns_privacy_ioc_mac": "iOS 및 macOS 설정", "setup_dns_notice": "<1>DNS-over-HTTPS 또는 <1>DNS-over-TLS를 사용하려면 AdGuard Home 설정에서 <0>암호화를 구성해야 합니다.", "rewrite_added": "\"{{key}}\"에 대한 DNS 수정 정보를 성공적으로 추가 됩니다.", "rewrite_deleted": "\"{{key}}\"에 대한 DNS 수정 정보를 성공적으로 삭제 됩니다.", @@ -529,7 +536,6 @@ "check_ip": "IP 주소: {{ip}}", "check_cname": "CNAME: {{cname}}", "check_reason": "이유: {{reason}}", - "check_rule": "규칙: {{rule}}", "check_service": "서비스 이름: {{service}}", "service_name": "서비스 이름", "check_not_found": "필터 목록에서 찾을 수 없음", diff --git a/client/src/__locales/nl.json b/client/src/__locales/nl.json index 14f3fc58..d5be0f99 100644 --- a/client/src/__locales/nl.json +++ b/client/src/__locales/nl.json @@ -32,6 +32,7 @@ "form_error_ip_format": "Ongeldig IPv4 formaat", "form_error_mac_format": "Ongeldig MAC formaat.", "form_error_client_id_format": "Opmaak cliënt-ID is ongeldig", + "form_error_server_name": "Ongeldige servernaam", "form_error_positive": "Moet groter zijn dan 0", "form_error_negative": "Moet 0 of hoger dan 0 zijn", "range_end_error": "Moet groter zijn dan het startbereik", @@ -249,8 +250,13 @@ "blocking_ipv6": "Blokkeren IP6", "dns_over_https": "DNS-via-HTTPS", "dns_over_tls": "DNS-via-TLS", + "dns_over_quic": "DNS-via-QUIC", + "client_id": "Apparaat-ID", + "client_id_placeholder": "Apparaat-ID invoeren", + "client_id_desc": "Verschillende apparaten kunnen worden geïdentificeerd door hun specifiek apparaat-ID. Hier vind je meer informatie over het identificeren van apparaten.", "download_mobileconfig_doh": ".mobileconfig voor DNS-via-HTTPS downloaden", "download_mobileconfig_dot": ".mobileconfig voor DNS-via-TLS downloaden", + "download_mobileconfig": "Configuratiebestand downloaden", "plain_dns": "Gewone DNS", "form_enter_rate_limit": "Voer ratio limiet in", "rate_limit": "Ratio limiet", @@ -269,7 +275,7 @@ "source_label": "Bron", "found_in_known_domain_db": "Gevonden in de bekende domeingegevensbank.", "category_label": "Categorie", - "rule_label": "Regel", + "rule_label": "Regel(s)", "list_label": "Lijst", "unknown_filter": "Onbekend filter {{filterId}}", "known_tracker": "Bekende volger", @@ -330,7 +336,7 @@ "encryption_config_saved": "Encryptie configuratie opgeslagen", "encryption_server": "Server naam", "encryption_server_enter": "Voer domein naam in", - "encryption_server_desc": "Om HTTPS te gebruiken, voer de naam in van de server overeenkomstig met het SSL certificaat.", + "encryption_server_desc": "Om HTTPS te gebruiken, moet je de servernaam invoeren die overeenkomt met je SSL-certificaat of jokerteken-certificaat. Als het veld niet is ingesteld, accepteert het TLS-verbindingen voor elk domein.", "encryption_redirect": "Herleid automatisch naar HTTPS", "encryption_redirect_desc": "Indien ingeschakeld, zal AdGuard Home je automatisch herleiden van HTTP naar HTTPS.", "encryption_https": "HTTPS poort", @@ -386,7 +392,7 @@ "client_edit": "Wijzig gebruiker", "client_identifier": "Identificeer via", "ip_address": "IP adres", - "client_identifier_desc": "Gebruikers kunnen worden geïdentificeerd door het IP-adres, CIDR of MAC-adres. Hou er rekening mee dat het gebruik van MAC als ID alleen mogelijk is als AdGuard Home ook een <0>DHCP-server is", + "client_identifier_desc": "Apparaten kunnen worden geïdentificeerd door hun IP-adres, CIDR, MAC-adres of een speciaal apparaat-ID (kan gebruikt worden voor DoT/DoH/DoQ). <0>Hier kan je meer lezen over het identificeren van apparaten.", "form_enter_ip": "Vul IP in", "form_enter_mac": "Vul MAC in", "form_enter_id": "ID invoeren", @@ -430,6 +436,7 @@ "setup_dns_privacy_other_3": "<0>dnscrypt-proxy ondersteunt <1>DNS-via-HTTPS.", "setup_dns_privacy_other_4": "<0>Mozilla Firefox ondersteunt <1>DNS-via-HTTPS.", "setup_dns_privacy_other_5": "U vindt meer implementaties <0> hier en <1> hier .", + "setup_dns_privacy_ioc_mac": "iOS en macOS configuratie", "setup_dns_notice": "Om <1>DNS-via-HTTPS of <1>DNS-via-TLS te gebruiken, moet je <0>Versleuteling configureren in de AdGuard Home instellingen.", "rewrite_added": "DNS-herschrijving voor \"{{key}}\" met succes toegevoegd", "rewrite_deleted": "DNS-herschrijving voor \"{{key}}\" met succes verwijderd", @@ -529,7 +536,6 @@ "check_ip": "IP-adressen: {{ip}}", "check_cname": "CNAME: {{cname}}", "check_reason": "Reden: {{reason}}", - "check_rule": "Regel: {{rule}}", "check_service": "Servicenaam: {{service}}", "service_name": "Naam service", "check_not_found": "Niet in je lijst met filters gevonden", diff --git a/client/src/__locales/no.json b/client/src/__locales/no.json index c876eb69..58637b4a 100644 --- a/client/src/__locales/no.json +++ b/client/src/__locales/no.json @@ -269,7 +269,6 @@ "source_label": "Kilde", "found_in_known_domain_db": "Funnet i databasen over kjente domener.", "category_label": "Kategori", - "rule_label": "Oppføring", "list_label": "Liste", "unknown_filter": "Ukjent filter {{filterId}}", "known_tracker": "Kjent sporer", @@ -330,7 +329,6 @@ "encryption_config_saved": "Krypteringsoppsettet ble lagret", "encryption_server": "Tjenerens navn", "encryption_server_enter": "Skriv inn domenenavnet ditt", - "encryption_server_desc": "For å kunne bruke HTTPS, må du skrive inn tjenernavnet som samsvarer med ditt SSL-sertifikat.", "encryption_redirect": "Automatisk omdiriger til HTTPS", "encryption_redirect_desc": "Dersom dette er valgt, vil AdGuard Home automatisk omdirigere deg fra HTTP til HTTPS-adresser.", "encryption_https": "HTTPS-port", @@ -386,7 +384,6 @@ "client_edit": "Rediger klienten", "client_identifier": "Identifikator", "ip_address": "IP-adresse", - "client_identifier_desc": "Klienter kan bli identifisert gjennom IP-adressen eller MAC-adressen. Vennligst bemerk at å bruke MAC som en identifikator, bare er mulig dersom AdGuard Home også er en <0>DHCP-tjener", "form_enter_ip": "Skriv inn IP", "form_enter_mac": "Skriv inn MAC", "form_enter_id": "Skriv inn identifikator", @@ -528,7 +525,6 @@ "check_ip": "IP-adresser: {{ip}}", "check_cname": "CNAME: {{cname}}", "check_reason": "Årsak: {{reason}}", - "check_rule": "Oppføring: {{rule}}", "check_service": "Tjenestenavn: {{service}}", "service_name": "Tjenestenavn", "check_not_found": "Ikke funnet i filterlistene dine", diff --git a/client/src/__locales/pl.json b/client/src/__locales/pl.json index 56d59db0..55f172b3 100644 --- a/client/src/__locales/pl.json +++ b/client/src/__locales/pl.json @@ -32,6 +32,7 @@ "form_error_ip_format": "Nieprawidłowy format IP", "form_error_mac_format": "Nieprawidłowy format MAC", "form_error_client_id_format": "Nieprawidłowy format identyfikatora klienta", + "form_error_server_name": "Nieprawidłowa nazwa serwera", "form_error_positive": "Musi być większa niż 0", "form_error_negative": "Musi być równy 0 lub większy", "range_end_error": "Zakres musi być większy niż początkowy", @@ -249,8 +250,13 @@ "blocking_ipv6": "Blokowanie IPv6", "dns_over_https": "DNS-over-HTTPS", "dns_over_tls": "DNS-over-TLS", + "dns_over_quic": "DNS-over-QUIC", + "client_id": "ID klienta", + "client_id_placeholder": "Wpisz ID klienta", + "client_id_desc": "Różnych klientów można zidentyfikować za pomocą specjalnego ID klienta. Tutaj możesz dowiedzieć się więcej o tym, jak identyfikować klientów.", "download_mobileconfig_doh": "Pobierz plik .mobileconfig dla DNS-over-HTTPS", "download_mobileconfig_dot": "Pobierz plik .mobileconfig dla DNS-over-TLS", + "download_mobileconfig": "Pobierz plik konfiguracyjny", "plain_dns": "Zwykły DNS", "form_enter_rate_limit": "Wpisz limit ilościowy", "rate_limit": "Limit ilościowy", @@ -269,7 +275,7 @@ "source_label": "Źródło", "found_in_known_domain_db": "Znaleziono w bazie danych znanych domen.", "category_label": "Kategoria", - "rule_label": "Reguła", + "rule_label": "Reguła(y)", "list_label": "Lista", "unknown_filter": "Nieznany filtr {{filterId}}", "known_tracker": "Znany element śledzący", @@ -330,7 +336,7 @@ "encryption_config_saved": "Zapisano konfigurację szyfrowania", "encryption_server": "Nazwa serwera", "encryption_server_enter": "Wpisz swoją nazwę domeny", - "encryption_server_desc": "Aby korzystać z protokołu HTTPS, musisz wprowadzić nazwę serwera zgodną z certyfikatem SSL.", + "encryption_server_desc": "Aby korzystać z protokołu HTTPS, musisz wprowadzić nazwę serwera, która jest zgodna z certyfikatem SSL lub certyfikatem typu wildcard. Jeśli pole nie jest ustawione, będzie akceptować połączenia TLS dla dowolnej domeny.", "encryption_redirect": "Przekieruj automatycznie do HTTPS", "encryption_redirect_desc": "Jeśli zaznaczone, AdGuard Home automatycznie przekieruje Cię z adresów HTTP na HTTPS.", "encryption_https": "Port HTTPS", @@ -386,7 +392,7 @@ "client_edit": "Edytuj klienta", "client_identifier": "Identyfikator", "ip_address": "Adres IP", - "client_identifier_desc": "Klienci mogą być identyfikowani na podstawie adresu IP, CIDR, adresu MAC. Pamiętaj, że użycie MAC jako identyfikatora jest możliwe tylko wtedy, gdy AdGuard Home jest również <0>serwerem DHCP", + "client_identifier_desc": "Klientów można zidentyfikować po adresie IP, CIDR, adresie MAC lub specjalnym identyfikatorze klienta (może służyć do DoT/DoH/DoQ). <0>Tutaj możesz dowiedzieć się więcej o tym, jak identyfikować klientów.", "form_enter_ip": "Wpisz adres IP", "form_enter_mac": "Wpisz adres MAC", "form_enter_id": "Wpisz identyfikator", @@ -430,6 +436,7 @@ "setup_dns_privacy_other_3": "<0>dnscrypt-proxy obsługuje <1>DNS-over-HTTPS.", "setup_dns_privacy_other_4": "<0>Mozilla Firefox obsługuje <1>DNS-over-HTTPS.", "setup_dns_privacy_other_5": "Znajdziesz więcej implementacji <0>tutaj i <1>tutaj.", + "setup_dns_privacy_ioc_mac": "Konfiguracja iOS i macOS", "setup_dns_notice": "Aby skorzystać z <1>DNS-over-HTTPS lub <1>DNS-over-TLS, musisz w ustawieniach AdGuard Home <0>skonfigurować szyfrowanie.", "rewrite_added": "Pomyślnie dodano przepisanie DNS dla „{{key}}”", "rewrite_deleted": "Przepisanie DNS dla „{{key}}” zostało pomyślnie usunięte", @@ -529,7 +536,6 @@ "check_ip": "Adresy IP: {{ip}}", "check_cname": "CNAME: {{cname}}", "check_reason": "Powód: {{reason}}", - "check_rule": "Reguła: {{rule}}", "check_service": "Nazwa usługi: {{service}}", "service_name": "Nazwa usługi", "check_not_found": "Nie znaleziono na Twoich listach filtrów", diff --git a/client/src/__locales/pt-br.json b/client/src/__locales/pt-br.json index 0452b5fd..01a3a933 100644 --- a/client/src/__locales/pt-br.json +++ b/client/src/__locales/pt-br.json @@ -32,6 +32,7 @@ "form_error_ip_format": "Formato de endereço IPv inválido", "form_error_mac_format": "Formato do endereço MAC inválido", "form_error_client_id_format": "Formato do ID de cliente inválido", + "form_error_server_name": "Nome de servidor inválido", "form_error_positive": "Deve ser maior que 0", "form_error_negative": "Deve ser igual ou superior a 0", "range_end_error": "Deve ser maior que o início do intervalo", @@ -249,8 +250,13 @@ "blocking_ipv6": "Bloqueando IPv6", "dns_over_https": "DNS-sobre-HTTPS", "dns_over_tls": "DNS-sobre-TLS", + "dns_over_quic": "DNS-sobre-QUIC", + "client_id": "ID do cliente", + "client_id_placeholder": "Digite o ID do cliente", + "client_id_desc": "Diferentes clientes podem ser identificados por um ID de cliente especial. Aqui você pode aprender mais sobre como identificar clientes.", "download_mobileconfig_doh": "BAixar .mobileconfig para DNS-sobre-HTTPS", "download_mobileconfig_dot": "BAixar .mobileconfig para DNS-sobre-TLS", + "download_mobileconfig": "Baixar arquivo de configuração", "plain_dns": "DNS simples", "form_enter_rate_limit": "Insira a taxa limite", "rate_limit": "Taxa limite", @@ -269,7 +275,7 @@ "source_label": "Fonte", "found_in_known_domain_db": "Encontrado no banco de dados de domínios conhecidos.", "category_label": "Categoria", - "rule_label": "Regra", + "rule_label": "Regra(s)", "list_label": "Lista", "unknown_filter": "Filtro desconhecido {{filterId}}", "known_tracker": "Rastreador conhecido", @@ -330,7 +336,7 @@ "encryption_config_saved": "Configuração de criptografia salva", "encryption_server": "Nome do servidor", "encryption_server_enter": "Digite seu nome de domínio", - "encryption_server_desc": "Para usar o protocolo HTTPS, você precisa digitar o nome do servidor que corresponde ao seu certificado SSL.", + "encryption_server_desc": "Para usar HTTPS, você precisa inserir o nome do servidor que corresponda ao seu certificado SSL ou certificado curinga. Se o campo não estiver definido, ele aceitará conexões TLS para qualquer domínio.", "encryption_redirect": "Redirecionar automaticamente para HTTPS", "encryption_redirect_desc": "Se marcado, o AdGuard Home irá redirecionar automaticamente os endereços HTTP para HTTPS.", "encryption_https": "Porta HTTPS", @@ -386,7 +392,7 @@ "client_edit": "Editar cliente", "client_identifier": "Identificador", "ip_address": "Endereço de IP", - "client_identifier_desc": "Clientes podem ser identificados pelo endereço de IP ou pelo endereço MAC. Observe que o uso do endereço MAC como identificador só é possível se o AdGuard Home também for um <0>servidor DHCP", + "client_identifier_desc": "Os clientes podem ser identificados pelo endereço IP, CIDR, Endereço MAC ou um ID de cliente especial (pode ser usado para DoT/DoH/DoQ). <0>Aqui você pode aprender mais sobre como identificar clientes.", "form_enter_ip": "Digite o endereço de IP", "form_enter_mac": "Digite o endereço MAC", "form_enter_id": "Inserir identificador", @@ -430,6 +436,7 @@ "setup_dns_privacy_other_3": "<0>dnscrypt-proxy suporta <1>DNS-sobre-HTTPS", "setup_dns_privacy_other_4": "<0>Mozilla Firefox suporta <1>DNS-sobre-HTTPS.", "setup_dns_privacy_other_5": "Você encontrará mais implementações <0>aqui e <1>aqui.", + "setup_dns_privacy_ioc_mac": "configuração para iOS e macOS", "setup_dns_notice": "Para usar o <1>DNS-sobre-HTTPS ou <1>DNS-sobre-TLS, você precisa <0>configurar a criptografia nas configurações do AdGuard Home.", "rewrite_added": "Reescrita de DNS para \"{{key}}\" adicionada com sucesso", "rewrite_deleted": "Reescrita de DNS para \"{{key}}\" excluída com sucesso", @@ -529,7 +536,6 @@ "check_ip": "Endereços de IP: {{ip}}", "check_cname": "CNAME: {{cname}}", "check_reason": "Motivo: {{reason}}", - "check_rule": "Regra: {{rule}}", "check_service": "Nome do serviço: {{service}}", "service_name": "Nome do serviço", "check_not_found": "Não encontrado em suas listas de filtros", diff --git a/client/src/__locales/pt-pt.json b/client/src/__locales/pt-pt.json index 63929d92..e8c70cc9 100644 --- a/client/src/__locales/pt-pt.json +++ b/client/src/__locales/pt-pt.json @@ -32,6 +32,7 @@ "form_error_ip_format": "Formato de endereço IPv4 inválido", "form_error_mac_format": "Formato do endereço MAC inválido", "form_error_client_id_format": "Formato inválido", + "form_error_server_name": "Nome de servidor inválido", "form_error_positive": "Deve ser maior que 0", "form_error_negative": "Deve ser igual ou superior a 0", "range_end_error": "Deve ser maior que o início do intervalo", @@ -249,8 +250,13 @@ "blocking_ipv6": "A bloquear IPv6", "dns_over_https": "DNS-sobre-HTTPS", "dns_over_tls": "DNS-sobre-TLS", + "dns_over_quic": "DNS-sobre-QUIC", + "client_id": "ID do cliente", + "client_id_placeholder": "Insira o ID do cliente", + "client_id_desc": "Diferentes clientes podem ser identificados por um ID de cliente especial. Aqui você pode aprender mais sobre como identificar clientes.", "download_mobileconfig_doh": "Transferir .mobileconfig para DNS-sobre-HTTPS", "download_mobileconfig_dot": "Transferir .mobileconfig para DNS-sobre-TLS", + "download_mobileconfig": "Transferir arquivo de configuração", "plain_dns": "DNS simples", "form_enter_rate_limit": "Insira o limite de taxa", "rate_limit": "Limite de taxa", @@ -269,7 +275,7 @@ "source_label": "Fonte", "found_in_known_domain_db": "Encontrado no banco de dados de domínios conhecido.", "category_label": "Categoria", - "rule_label": "Regra", + "rule_label": "Regra(s)", "list_label": "Lista", "unknown_filter": "Filtro desconhecido {{filterId}}", "known_tracker": "Rastreador conhecido", @@ -330,7 +336,7 @@ "encryption_config_saved": "Definição de criptografia guardada", "encryption_server": "Nome do servidor", "encryption_server_enter": "Insira o seu nome de domínio", - "encryption_server_desc": "Para usar o protocolo HTTPS, precisa de inserir o nome do servidor que corresponde ao seu certificado SSL.", + "encryption_server_desc": "Para usar HTTPS, você precisa inserir o nome do servidor que corresponda ao seu certificado SSL ou certificado curinga. Se o campo não estiver definido, ele aceitará conexões TLS para qualquer domínio.", "encryption_redirect": "Redireccionar automaticamente para HTTPS", "encryption_redirect_desc": "Se marcado, o AdGuard Home irá redireccionar automaticamente os endereços HTTP para HTTPS.", "encryption_https": "Porta HTTPS", @@ -386,7 +392,7 @@ "client_edit": "Editar cliente", "client_identifier": "Identificador", "ip_address": "Endereço de IP", - "client_identifier_desc": "Os clientes podem ser identificados pelo endereço de IP, CIDR, ou pelo endereço MAC. Observe que o uso do endereço MAC como identificador só é possível se o AdGuard Home também for um <0>servidor DHCP", + "client_identifier_desc": "Os clientes podem ser identificados pelo endereço IP, CIDR, Endereço MAC ou um ID de cliente especial (pode ser usado para DoT/DoH/DoQ). <0>Aqui você pode aprender mais sobre como identificar clientes.", "form_enter_ip": "Insira IP", "form_enter_mac": "Insira o endereço MAC", "form_enter_id": "Inserir identificador", @@ -430,6 +436,7 @@ "setup_dns_privacy_other_3": "<0>dnscrypt-proxy suporta <1>DNS-sobre-HTTPS.", "setup_dns_privacy_other_4": "<0>Mozilla Firefox suporta <1>DNS-sobre-HTTPS.", "setup_dns_privacy_other_5": "Encontrará mais implementações <0>aqui e <1>aqui.", + "setup_dns_privacy_ioc_mac": "configuração para iOS e macOS", "setup_dns_notice": "Para usar o <1>DNS-sobre-HTTPS ou <1>DNS-sobre-TLS, precisa de <0>configurar a criptografia nas configurações do AdGuard Home.", "rewrite_added": "Reescrita de DNS para \"{{key}}\" adicionada com sucesso", "rewrite_deleted": "Reescrita de DNS para \"{{key}}\" excluída com sucesso", @@ -529,7 +536,6 @@ "check_ip": "Endereços de IP: {{ip}}", "check_cname": "CNAME: {{cname}}", "check_reason": "Motivo: {{reason}}", - "check_rule": "Regra: {{rule}}", "check_service": "Nome do serviço: {{service}}", "service_name": "Nome do serviço", "check_not_found": "Não encontrado nas tuas listas de filtros", diff --git a/client/src/__locales/ro.json b/client/src/__locales/ro.json index 21238763..6523bd30 100644 --- a/client/src/__locales/ro.json +++ b/client/src/__locales/ro.json @@ -32,6 +32,7 @@ "form_error_ip_format": "Format IP invalid", "form_error_mac_format": "Format MAC invalid", "form_error_client_id_format": "Format ID de client invalid", + "form_error_server_name": "Nume de server nevalid", "form_error_positive": "Trebuie să fie mai mare de 0", "form_error_negative": "Trebuie să fie egală cu 0 sau mai mare", "range_end_error": "Trebuie să fie mai mare decât începutul intervalului", @@ -249,8 +250,13 @@ "blocking_ipv6": "Blocarea IPv6", "dns_over_https": "DNS-over-HTTPS", "dns_over_tls": "DNS-over-TLS", + "dns_over_quic": "DNS-over-QUIC", + "client_id": "ID Client", + "client_id_placeholder": "Introduceți ID client", + "client_id_desc": "Diferiți clienți pot fi identificați printr-un ID special al clientului. Aici puteți afla mai multe despre cum să identificați clienții.", "download_mobileconfig_doh": "Descărcați .mobileconfig pentru DNS-over-HTTPS", "download_mobileconfig_dot": "Descărcați .mobileconfig pentru DNS-over-TLS", + "download_mobileconfig": "Descărcați fișierul de configurare", "plain_dns": "DNS simplu", "form_enter_rate_limit": "Introduceți limita ratei", "rate_limit": "Limita ratei", @@ -269,7 +275,7 @@ "source_label": "Sursă", "found_in_known_domain_db": "Găsit în baza de date de domenii cunoscută.", "category_label": "Categorie", - "rule_label": "Regulă", + "rule_label": "Regulă(reguli)", "list_label": "Listă", "unknown_filter": "Filtru necunoscut {{filterId}}", "known_tracker": "Tracker cunoscut", @@ -330,7 +336,7 @@ "encryption_config_saved": "Configurația de criptare salvată", "encryption_server": "Nume de server", "encryption_server_enter": "Introduceți numele domeniului", - "encryption_server_desc": "Pentru a utiliza HTTPS, trebuie introdus numele serverului care corespunde certificatului SSL.", + "encryption_server_desc": "Pentru a utiliza HTTPS, trebuie să introduceți numele serverului care se potrivește cu certificatul SSL sau certificatul wildcard al dvs. În cazul în care câmpul nu este setat, va accepta conexiuni TLS pentru orice domeniu.", "encryption_redirect": "Redirecționați automat la HTTPS", "encryption_redirect_desc": "Dacă este bifat, AdGuard Home vă va redirecționa automat de la adrese HTTP la HTTPS.", "encryption_https": "Port HTTPS", @@ -386,7 +392,7 @@ "client_edit": "Editare client", "client_identifier": "Identificator", "ip_address": "Adresa IP", - "client_identifier_desc": "Clienții pot fi identificați prin adresa IP, CIDR, adresa MAC. Vă rugăm să rețineți că utilizarea MAC ca identificator este posibilă numai dacă AdGuard Home este și un <0>server DHCP", + "client_identifier_desc": "Clienții pot fi identificați prin adresa IP, CIDR, adresa MAC sau un ID special al clientului (poate fi folosit pentru DoT/DoH/DoQ). <0>Aici puteți afla mai multe despre cum să identificați clienții.", "form_enter_ip": "Introduceți IP", "form_enter_mac": "Introduceți MAC", "form_enter_id": "Introduceți identificator", @@ -430,6 +436,7 @@ "setup_dns_privacy_other_3": "<0>dnscrypt-proxy acceptă <1>DNS-over-HTTPS.", "setup_dns_privacy_other_4": "<0>Mozilla Firefox acceptă <1>DNS-over-HTTPS.", "setup_dns_privacy_other_5": "Veți găsi mai multe implementări <0>aici și <1>aici.", + "setup_dns_privacy_ioc_mac": "Configurarea iOS și macOS", "setup_dns_notice": "Pentru a utiliza <1>DNS-over-HTTPS sau <1>DNS-over-TLS, trebuie să <0>configurați Criptarea în setările AdGuard Home.", "rewrite_added": "Rescriere DNS pentru \"{{key}}\" adăugată cu succes", "rewrite_deleted": "Rescriere DNS pentru \"{{key}}\" ștearsă cu succes", @@ -529,7 +536,6 @@ "check_ip": "Adrese IP: {{ip}}", "check_cname": "CNAME: {{cname}}", "check_reason": "Cauza: {{reason}}", - "check_rule": "Regula: {{rule}}", "check_service": "Nume servici: {{service}}", "service_name": "Numele serviciului", "check_not_found": "Nu se găsește în listele de filtre", diff --git a/client/src/__locales/ru.json b/client/src/__locales/ru.json index 9c9b02ff..ea58c7f3 100644 --- a/client/src/__locales/ru.json +++ b/client/src/__locales/ru.json @@ -32,6 +32,7 @@ "form_error_ip_format": "Неверный формат IP-адреса", "form_error_mac_format": "Некорректный формат MAC", "form_error_client_id_format": "Неверный формат ID клиента", + "form_error_server_name": "Неверное имя сервера", "form_error_positive": "Должно быть больше 0", "form_error_negative": "Должно быть не меньше 0", "range_end_error": "Должно превышать начало диапазона", @@ -249,8 +250,13 @@ "blocking_ipv6": "Блокировка IPv6", "dns_over_https": "DNS-over-HTTPS", "dns_over_tls": "DNS-over-TLS", + "dns_over_quic": "DNS-over-QUIC", + "client_id": "Идентификатор клиента", + "client_id_placeholder": "Введите идентификатор клиента", + "client_id_desc": "Различные клиенты могут идентифицироваться по специальному идентификатору клиента. Здесь вы можете узнать больше об идентификации клиентов.", "download_mobileconfig_doh": "Скачать .mobileconfig для DNS-over-HTTPS", "download_mobileconfig_dot": "Скачать .mobileconfig для DNS-over-TLS", + "download_mobileconfig": "Загрузить файл конфигурации", "plain_dns": "Нешифрованный DNS", "form_enter_rate_limit": "Введите rate limit", "rate_limit": "Rate limit", @@ -269,7 +275,7 @@ "source_label": "Источник", "found_in_known_domain_db": "Найден в базе известных доменов.", "category_label": "Категория", - "rule_label": "Правило", + "rule_label": "Правило(-а)", "list_label": "Список", "unknown_filter": "Неизвестный фильтр {{filterId}}", "known_tracker": "Известный трекер", @@ -330,7 +336,7 @@ "encryption_config_saved": "Настройки шифрования сохранены", "encryption_server": "Имя сервера", "encryption_server_enter": "Введите ваше доменное имя", - "encryption_server_desc": "Для использования HTTPS вам необходимо ввести имя сервера, которое подходит вашему SSL-сертификату.", + "encryption_server_desc": "Для использования HTTPS вам необходимо ввести имя сервера, которое подходит вашему SSL-сертификату или сертификату с поддержкой поддоменов. Если это поле не задано, сервер будет принимать TLS-соединения для любого домена.", "encryption_redirect": "Автоматически перенаправлять на HTTPS", "encryption_redirect_desc": "Если включено, AdGuard Home будет автоматически перенаправлять вас с HTTP на HTTPS адрес.", "encryption_https": "Порт HTTPS", @@ -386,7 +392,7 @@ "client_edit": "Редактировать клиента", "client_identifier": "Идентификатор", "ip_address": "IP-адрес", - "client_identifier_desc": "Клиенты могут быть идентифицированы по IP-адресу, CIDR или MAC-адресу. Обратите внимание, что использование MAC как идентификатора возможно, только если AdGuard Home также является и <0>DHCP-сервером", + "client_identifier_desc": "Клиенты могут быть идентифицированы по IP-адресу, CIDR или MAC-адресу или специальному ID (можно использовать для DoT/DoH/DoQ). <0>Здесь вы можете узнать больше об идентификации клиентов.", "form_enter_ip": "Введите IP", "form_enter_mac": "Введите MAC", "form_enter_id": "Введите идентификатор", @@ -430,6 +436,7 @@ "setup_dns_privacy_other_3": "<0>dnscrypt-proxy поддерживает <1>DNS-over-HTTPS.", "setup_dns_privacy_other_4": "<0>Mozilla Firefox поддерживает <1>DNS-over-HTTPS.", "setup_dns_privacy_other_5": "Вы можете найти еще варианты <0>тут и <1>тут.", + "setup_dns_privacy_ioc_mac": "Конфигурация для iOS и macOS", "setup_dns_notice": "Чтобы использовать <1>DNS-over-HTTPS или <1>DNS-over-TLS, вам нужно <0>настроить шифрование в настройках AdGuard Home.", "rewrite_added": "Правило перенаправления DNS для \"{{key}}\" успешно добавлено", "rewrite_deleted": "Правило перенаправления DNS для \"{{key}}\" успешно удалено", @@ -529,7 +536,6 @@ "check_ip": "IP-адреса: {{ip}}", "check_cname": "CNAME: {{cname}}", "check_reason": "Причина: {{reason}}", - "check_rule": "Правило: {{rule}}", "check_service": "Название сервиса: {{service}}", "service_name": "Имя сервиса", "check_not_found": "Не найдено в вашем списке фильтров", diff --git a/client/src/__locales/si-lk.json b/client/src/__locales/si-lk.json index d39bda0b..015c477c 100644 --- a/client/src/__locales/si-lk.json +++ b/client/src/__locales/si-lk.json @@ -72,7 +72,7 @@ "dns_query": "ව.නා.ප. (DNS) විමසුම්", "blocked_by": "<0>පෙරහන් මගින් අවහිර කරන ලද", "stats_malware_phishing": "අවහිර කළ ද්වේශාංග/තතුබෑම්", - "stats_adult": "අවහිර කළ වැඩිහිටි වෙබ් අඩවි", + "stats_adult": "අවහිර කළ වැඩිහිටි වියමන අඩවි", "stats_query_domain": "ජනප්‍රිය විමසන ලද වසම්", "for_last_24_hours": "පසුගිය පැය 24 සඳහා", "for_last_days": "පසුගිය දින {{count}} සඳහා", @@ -83,22 +83,22 @@ "top_clients": "ජනප්‍රිය අනුග්‍රාහකයන්", "general_statistics": "පොදු සංඛ්‍යාලේඛන", "number_of_dns_query_blocked_24_hours": "දැන්වීම් වාරණ පෙරහන් සහ ධාරක වාරණ ලැයිස්තු මගින් අවහිර කරන ලද ව.නා.ප. ඉල්ලීම් ගණන", - "number_of_dns_query_blocked_24_hours_by_sec": "AdGuard browsing security ඒකකය මගින් අවහිර කරන ලද ව.නා.ප. ඉල්ලීම් ගණන", - "number_of_dns_query_blocked_24_hours_adult": "අවහිර කළ වැඩිහිටි වෙබ් අඩවි ගණන", + "number_of_dns_query_blocked_24_hours_by_sec": "ඇඩ්ගාර්ඩ් පිරික්සුම් ආරක්ෂණ ඒකකය මගින් අවහිර කරන ලද ව.නා.ප. ඉල්ලීම් ගණන", + "number_of_dns_query_blocked_24_hours_adult": "අවහිර කළ වැඩිහිටි වියමන අඩවි ගණන", "enforced_save_search": "ආරක්ෂිත සෙවීම බලාත්මක කරන ලද", "number_of_dns_query_to_safe_search": "ආරක්ෂිත සෙවීම බලාත්මක කළ සෙවුම් යන්ත්‍ර සඳහා ව.නා.ප. ඉල්ලීම් ගණන", "average_processing_time": "සාමාන්‍ය සැකසුම් කාලය", "average_processing_time_hint": "ව.නා.ප. ඉල්ලීමක් සැකසීමේ සාමාන්‍ය කාලය මිලි තත්පර වලින්", "block_domain_use_filters_and_hosts": "පෙරහන් සහ ධාරක ගොනු භාවිතා කරමින් වසම් අවහිර කරන්න", "filters_block_toggle_hint": "ඔබට අවහිර කිරීමේ නීති පෙරහන් තුළ පිහිටුවිය හැකිය.", - "use_adguard_browsing_sec": "AdGuard browsing security වෙබ් සේවාව භාවිතා කරන්න", - "use_adguard_parental": "AdGuard parental control වෙබ් සේවාව භාවිතා කරන්න", - "use_adguard_parental_hint": "වසමේ වැඩිහිටියන්ට අදාල කරුණු අඩංගු දැයි ඇඩ්ගාර්ඩ් හෝම් විසින් පරීක්ෂා කරනු ඇත. එය බ්‍රව්සින් සෙකියුරිටි වෙබ් සේවාව මෙන් රහස්‍යතා හිතකාමී යෙ.ක්‍ර. අ.මු. (API) භාවිතා කරයි.", + "use_adguard_browsing_sec": "ඇඩ්ගාර්ඩ් පිරික්සුම් ආරක්ෂණ වියමන සේවාව භාවිතා කරන්න", + "use_adguard_parental": "ඇඩ්ගාර්ඩ් දෙමාපිය පාලන වියමන සේවාව භාවිතා කරන්න", + "use_adguard_parental_hint": "වසමේ වැඩිහිටියන්ට අදාල කරුණු අඩංගු දැයි ඇඩ්ගාර්ඩ් හෝම් විසින් පරීක්ෂා කරනු ඇත. එය පිරික්සුම් ආරක්ෂණ වියමන සේවාව මෙන් රහස්‍යතා හිතකාමී යෙ.ක්‍ර. අ.මු. (API) භාවිතා කරයි.", "enforce_safe_search": "ආරක්ෂිත සෙවීම බලාත්මක කරන්න", "enforce_save_search_hint": "ඇඩ්ගාර්ඩ් හෝම් හට පහත සෙවුම් යන්ත්‍ර තුළ ආරක්ෂිත සෙවීම බලාත්මක කළ හැකිය: ගූගල්, යූටියුබ්, බින්ග්, ඩක්ඩක්ගෝ, යාන්ඩෙක්ස් සහ පික්සාබේ.", "no_servers_specified": "සේවාදායක කිසිවක් නිශ්චිතව දක්වා නැත", "general_settings": "පොදු සැකසුම්", - "dns_settings": "DNS සැකසුම්", + "dns_settings": "ව.නා.ප. සැකසුම්", "dns_blocklists": "ව.නා.ප. අවහිර කිරී‌‌‌‌‌මේ ලැයිස්තු", "dns_allowlists": "ව.නා.ප. අවසර දීමේ ලැයිස්තු", "dns_blocklists_desc": "ඇඩ්ගාර්ඩ් හෝම් විසින් අවහිර කිරී‌‌‌‌‌මේ ලැයිස්තු වලට ගැලපෙන වසම් අවහිර කරනු ඇත.", @@ -156,11 +156,11 @@ "example_comment_meaning": "විස්තර කිරීමක්", "example_comment_hash": "# එසේම අදහස් දැක්වීමක්", "example_regex_meaning": "නිශ්චිතව දක්වා ඇති නිත්‍ය වාක්‍යවිධියට ගැලපෙන වසම් වෙත පිවිසීම අවහිර කරයි", - "example_upstream_regular": "සාමාන්‍ය DNS (UDP හරහා)", + "example_upstream_regular": "සාමාන්‍ය ව.නා.ප. (UDP හරහා)", "example_upstream_dot": "සංකේතාංකනය කළ <0>DNS-over-TLS", "example_upstream_doh": "සංකේතාංකනය කළ <0>DNS-over-HTTPS", "example_upstream_doq": "සංකේතාංකනය කළ <0>DNS-over-QUIC", - "example_upstream_tcp": "සාමාන්‍ය DNS (TCP හරහා)", + "example_upstream_tcp": "සාමාන්‍ය ව.නා.ප. (TCP/ස.පා.කෙ. හරහා) ", "all_lists_up_to_date_toast": "සියලුම ලැයිස්තු දැනටමත් යාවත්කාලීනයි", "dns_test_ok_toast": "සඳහන් කළ ව.නා.ප. සේවාදායකයන් නිවැරදිව ක්‍රියා කරයි", "dns_test_not_ok_toast": "සේවාදායක \"{{key}}\": භාවිතා කළ නොහැකි විය, කරුණාකර ඔබ එය නිවැරදිව ලියා ඇත්දැයි පරීක්ෂා කරන්න", @@ -227,22 +227,21 @@ "source_label": "මූලාශ්‍රය", "found_in_known_domain_db": "දැනුවත් වසම් දත්ත ගබඩාවේ හමු විය.", "category_label": "ප්‍රවර්ගය", - "rule_label": "නීතිය", "list_label": "ලැයිස්තුව", "unknown_filter": "{{filterId}} නොදන්නා පෙරහනකි", "known_tracker": "දැනුවත් ලුහුබැඳීමක්", "install_welcome_title": "ඇඩ්ගාර්ඩ් හෝම් වෙත සාදරයෙන් පිළිගනිමු!", "install_welcome_desc": "ඇඩ්ගාර්ඩ් හෝම් යනු ජාලය පුරා ඇති දැන්වීම් සහ ලුහුබැඳීම අවහිර කරන ව.නා.ප. සේවාදායකි. ඔබගේ මුළු ජාලය සහ සියලුම උපාංග පාලනය කිරීමට ඉඩ සලසා දීම එහි පරමාර්ථය යි, එයට අනුග්‍රාහක පාර්ශවීය වැඩසටහනක් භාවිතා කිරීම අවශ්‍ය නොවේ.", - "install_settings_title": "පරිපාලක වෙබ් අතුරු මුහුණත", + "install_settings_title": "පරිපාලක වියමන අතුරු මුහුණත", "install_settings_listen": "සවන් දෙන අතුරු මුහුණත", "install_settings_port": "කවුළුව", - "install_settings_interface_link": "ඔබගේ ඇඩ්ගාර්ඩ් හෝම් පරිපාලක වෙබ් අතුරු මුහුණත පහත ලිපිනයන්ගෙන් ප්‍රවේශ විය හැකිය:", + "install_settings_interface_link": "ඔබගේ ඇඩ්ගාර්ඩ් හෝම් පරිපාලක වියමන අතුරු මුහුණත පහත ලිපිනයන්ගෙන් ප්‍රවේශ විය හැකිය:", "form_error_port": "වලංගු කවුළුවක අගයක් ඇතුළත් කරන්න", "install_settings_dns": "ව.නා.ප. සේවාදායකය", "install_settings_dns_desc": "පහත ලිපිනයන්හි ව.නා.ප. සේවාදායකය භාවිතා කිරීම සඳහා ඔබගේ උපාංග හෝ මාර්ගකාරකය වින්‍යාසගත කිරීමට අවශ්‍ය වනු ඇත:", "install_settings_all_interfaces": "සියලුම අතුරුමුහුණත්", "install_auth_title": "සත්‍යාපනය", - "install_auth_desc": "ඔබගේ ඇඩ්ගාර්ඩ් හෝම් පරිපාලක වෙබ් අතුරු මුහුණතට මුරපද සත්‍යාපනය වින්‍යාසගත කිරීම අතිශයින් නිර්දේශ කෙරේ. එය ඔබගේ ස්ථානීය ජාල‌යෙන් පමණක් ප්‍රවේශ විය හැකි වුවද, එය තව දුරටත් සීමා රහිත ප්‍රවේශයකින් ආරක්ෂා කර ගැනීම වැදගත් ය.", + "install_auth_desc": "ඔබගේ ඇඩ්ගාර්ඩ් හෝම් පරිපාලක වියමන අතුරු මුහුණතට මුරපද සත්‍යාපනය වින්‍යාසගත කිරීම අතිශයින් නිර්දේශ කෙරේ. එය ඔබගේ ස්ථානීය ජාල‌යෙන් පමණක් ප්‍රවේශ විය හැකි වුවද, එය තව දුරටත් සීමා රහිත ප්‍රවේශයකින් ආරක්ෂා කර ගැනීම වැදගත් ය.", "install_auth_username": "පරිශීලක නාමය", "install_auth_password": "මුරපදය", "install_auth_confirm": "මුරපදය තහවුරු කරන්න", @@ -284,7 +283,7 @@ "open_dashboard": "උපකරණ පුවරුව විවෘත කරන්න", "install_saved": "සාර්ථකව සුරකින ලදි", "encryption_title": "සංකේතාංකනය", - "encryption_desc": "ගුප්තකේතනය (HTTPS/TLS) සඳහා ව.නා.ප. සහ පරිපාලක වෙබ් අතුරු මුහුණත සහය දක්වයි", + "encryption_desc": "ගුප්තකේතනය (HTTPS/TLS) සඳහා ව.නා.ප. සහ පරිපාලක වියමන අතුරු මුහුණත සහය දක්වයි", "encryption_config_saved": "සංකේතාංකන වින්‍යාසය සුරකින ලදි", "encryption_server": "සේවාදායක‌‌‌‌යේ නම", "encryption_server_enter": "ඔබගේ වසම් නාමය ඇතුළත් කරන්න", @@ -340,7 +339,6 @@ "client_edit": "අනුග්‍රාහකය සංස්කරණය කරන්න", "client_identifier": "හඳුන්වනය", "ip_address": "අ.ජා. කෙ. (IP) ලිපිනය", - "client_identifier_desc": "අනුග්‍රාහකයන් අ.ජා. කෙ. (IP) ලිපිනයක් හෝ මා.ප්‍ර.පා. (MAC) ලිපිනයක් මගින් හඳුනාගත හැකිය. මා.ප්‍ර.පා. හඳුන්වනයක් ලෙස භාවිතා කළ හැක්කේ ඇඩ්ගාර්ඩ් හෝම් ද <0>DHCP සේවාදායකයක් නම් පමණක් බව කරුණාවෙන් සලකන්න. ", "form_enter_ip": "අ.ජා. කෙ. (IP) ඇතුළත් කරන්න", "form_enter_mac": "මා.ප්‍ර.පා. (MAC) ඇතුළත් කරන්න", "form_enter_id": "හඳුන්වනය ඇතුළත් කරන්න", @@ -462,7 +460,6 @@ "check_ip": "අ.ජා. කෙ. (IP) ලිපින: {{ip}}", "check_cname": "අන්. නාමය (CNAME): {{cname}}", "check_reason": "හේතුව: {{reason}}", - "check_rule": "නීතිය: {{rule}}", "check_service": "සේවාවෙහි නම: {{service}}", "service_name": "සේවාවේ නම", "check_not_found": "ඔබගේ පෙරහන් ලැයිස්තු තුළ සොයා ගත නොහැක", @@ -483,7 +480,7 @@ "show_blocked_responses": "අවහිර කර ඇත", "show_whitelisted_responses": "සුදු ලැයිස්තුගත කර ඇත", "blocked_safebrowsing": "ආරක්ෂිත සෙවීම මගින් අවහිර කරන ලද", - "blocked_adult_websites": "අවහිර කළ වැඩිහිටි වෙබ් අඩවි", + "blocked_adult_websites": "අවහිර කළ වැඩිහිටි වියමන අඩවි", "blocked_threats": "අවහිර කළ තර්ජන", "allowed": "අවසර ලත්", "filtered": "පෙරහන් කරන ලද", diff --git a/client/src/__locales/sk.json b/client/src/__locales/sk.json index 82a6dd72..af88853a 100644 --- a/client/src/__locales/sk.json +++ b/client/src/__locales/sk.json @@ -32,6 +32,7 @@ "form_error_ip_format": "Nesprávny formát IPv4", "form_error_mac_format": "Nesprávny MAC formát", "form_error_client_id_format": "Neplatný formát client ID", + "form_error_server_name": "Neplatné meno servera", "form_error_positive": "Musí byť väčšie ako 0", "form_error_negative": "Musí byť číslo 0 alebo viac", "range_end_error": "Musí byť väčšie ako začiatok rozsahu", @@ -249,8 +250,13 @@ "blocking_ipv6": "Blokovanie IPv6", "dns_over_https": "DNS-over-HTTPS", "dns_over_tls": "DNS-over-TLS", + "dns_over_quic": "DNS-over-QUIC", + "client_id": "ID klienta", + "client_id_placeholder": "Zadať ID klienta", + "client_id_desc": "Rôznych klientov možno identifikovať podľa špeciálneho ID klienta. Tu sa dozviete viac o tom, ako identifikovať klientov.", "download_mobileconfig_doh": "Prevziať .mobileconfig pre DNS-over-HTTPS", "download_mobileconfig_dot": "Prevziať .mobileconfig pre DNS-over-TLS", + "download_mobileconfig": "Stiahnuť konfiguračný súbor", "plain_dns": "Obyčajné DNS", "form_enter_rate_limit": "Zadajte rýchlostný limit", "rate_limit": "Rýchlostný limit", @@ -269,7 +275,6 @@ "source_label": "Zdroj", "found_in_known_domain_db": "Nájdené v databáze známych domén.", "category_label": "Kategória", - "rule_label": "Pravidlo", "list_label": "Zoznam", "unknown_filter": "Neznámy filter {{filterId}}", "known_tracker": "Známy sledovač", @@ -330,7 +335,6 @@ "encryption_config_saved": "Konfigurácia šifrovania uložená", "encryption_server": "Meno servera", "encryption_server_enter": "Zadajte meno Vašej domény", - "encryption_server_desc": "Ak chcete používať HTTPS, musíte zadať meno servera, ktoré zodpovedá Vášmu SSL certifikátu.", "encryption_redirect": "Automaticky presmerovať na HTTPS", "encryption_redirect_desc": "Ak je táto možnosť začiarknutá, služba AdGuard Home Vás automaticky presmeruje z adresy HTTP na adresy HTTPS.", "encryption_https": "HTTPS port", @@ -386,7 +390,6 @@ "client_edit": "Upraviť klienta", "client_identifier": "Identifikátor", "ip_address": "IP adresa", - "client_identifier_desc": "Klienti môžu byť identifikovaní podľa IP adresy, CIDR alebo MAC adresy. Upozorňujeme, že používanie MAC ako identifikátora je možné len vtedy, ak je AdGuard Home tiež <0>DHCP server", "form_enter_ip": "Zadajte IP adresu", "form_enter_mac": "Zadajte MAC adresu", "form_enter_id": "Zadajte identifikátor", @@ -430,6 +433,7 @@ "setup_dns_privacy_other_3": "<0>dnscrypt-proxy podporuje <1>DNS-over-HTTPS.", "setup_dns_privacy_other_4": "<0>Mozilla Firefox podporuje <1>DNS-over-HTTPS.", "setup_dns_privacy_other_5": "Viac implementácií nájdete <0>tu a <1>tu.", + "setup_dns_privacy_ioc_mac": "Konfigurácia iOS a macOS", "setup_dns_notice": "Pre použitie <1>DNS-over-HTTPS alebo <1>DNS-over-TLS, potrebujete v nastaveniach AdGuard Home <0>nakonfigurovať šifrovanie.", "rewrite_added": "DNS prepísanie pre \"{{key}}\" bolo úspešne pridané", "rewrite_deleted": "DNS prepísanie pre \"{{key}}\" bolo úspešne vymazané", @@ -529,7 +533,6 @@ "check_ip": "IP adresy: {{ip}}", "check_cname": "CNAME: {{cname}}", "check_reason": "Dôvod: {{reason}}", - "check_rule": "Pravidlo: {{rule}}", "check_service": "Meno služby: {{service}}", "service_name": "Názov služby", "check_not_found": "Nenašlo sa vo Vašom zozname filtrov", diff --git a/client/src/__locales/sl.json b/client/src/__locales/sl.json index 2678693c..4a35e53a 100644 --- a/client/src/__locales/sl.json +++ b/client/src/__locales/sl.json @@ -32,6 +32,7 @@ "form_error_ip_format": "Neveljaven format IP", "form_error_mac_format": "Neveljaven MAC format", "form_error_client_id_format": "Neveljaven format ID odjemalca", + "form_error_server_name": "Neveljavno ime strežnika", "form_error_positive": "Mora biti večja od 0", "form_error_negative": "Mora biti enako ali več kot 0", "range_end_error": "Mora biti večji od začtka razpona", @@ -249,8 +250,13 @@ "blocking_ipv6": "Onemogočanje IPv6", "dns_over_https": "DNS-prek-HTTPS", "dns_over_tls": "DNS-prek-TLS", + "dns_over_quic": "DNS-prek-QIUC", + "client_id": "ID odjemalca", + "client_id_placeholder": "Vnesite ID odjemalca", + "client_id_desc": "Različne odjemalce je mogoče prepoznati s posebnim ID-jem odjemalca. Tukaj lahko izveste več o prepoznavanju odjemalcev.", "download_mobileconfig_doh": "Prenos .mobileconfig za DNS-preko-HTTPS", "download_mobileconfig_dot": "Prenos .mobileconfig za DNS-preko-TLS", + "download_mobileconfig": "Prenesi nastavitveno datoteko", "plain_dns": "Navadni DNS", "form_enter_rate_limit": "Vnesite omejitev hitrosti", "rate_limit": "Omejitev hitrosti", @@ -269,7 +275,7 @@ "source_label": "Vir", "found_in_known_domain_db": "Najdeno v zbirki podatkov znanih domen.", "category_label": "Kategorija", - "rule_label": "Pravilo", + "rule_label": "Pravila", "list_label": "Seznam", "unknown_filter": "Neznan filter {{filterId}}", "known_tracker": "Znan sledilec", @@ -386,7 +392,7 @@ "client_edit": "Uredi odjemalca", "client_identifier": "Identifikator", "ip_address": "IP naslov", - "client_identifier_desc": "Odjemalce je mogoče identificirati po naslovu IP, CIDR, MAC naslovu. Upoštevajte, da je uporaba MAC kot identifikatorja mogoča le, če je AdGuard Home tudi <0>strežnik DHCP", + "client_identifier_desc": "Odjemalce je mogoče prepoznati po naslovu IP, CIDR, naslovu MAC ali posebnem ID-ju odjemalca (lahko se uporablja za DoT/DoH/DoQ). <0>Tukaj lahko izveste več o prepoznavanju odjemalcev.", "form_enter_ip": "Vnesite IP", "form_enter_mac": "Vnesite MAC", "form_enter_id": "Vnesi identifikatorja", @@ -430,6 +436,7 @@ "setup_dns_privacy_other_3": "<0>dnscrypt-proxy podpira <1>DNS-prek-HTTPS.", "setup_dns_privacy_other_4": "<0>Mozilla Firefox podpira <1>DNS-prek-HTTPS.", "setup_dns_privacy_other_5": "Našli boste več izvedb <0>tukaj in <1>tukaj.", + "setup_dns_privacy_ioc_mac": "Nastavitve iOS in macOS", "setup_dns_notice": "Za uporabo <1>DNS-prek-HTTPS ali <1>DNS-prek-TLS, morate <0>konfigurirati šifriranje v nastavitvah AdGuard Home.", "rewrite_added": "Uspešno je dodano DNS prepisovanje za \"{{key}}\"", "rewrite_deleted": "Uspešno je izbrisano DNS prepisovanje za \"{{key}}\"", @@ -529,7 +536,6 @@ "check_ip": "IP naslovi: {{ip}}", "check_cname": "CNAME: {{cname}}", "check_reason": "Razlog: {{reason}}", - "check_rule": "Pravilo: {{rule}}", "check_service": "Ime storitve: {{service}}", "service_name": "Ime storitve", "check_not_found": "Ni najdeno na vašem seznamu filtrov", diff --git a/client/src/__locales/sr-cs.json b/client/src/__locales/sr-cs.json index 060b2616..8f63a450 100644 --- a/client/src/__locales/sr-cs.json +++ b/client/src/__locales/sr-cs.json @@ -269,7 +269,6 @@ "source_label": "Izvor", "found_in_known_domain_db": "Pronađeno u poznatim bazama podataka domena.", "category_label": "Kategorija", - "rule_label": "Pravilo", "list_label": "Lista", "unknown_filter": "Nepoznat filter {{filterId}}", "known_tracker": "Poznato praćenje", @@ -330,7 +329,6 @@ "encryption_config_saved": "Konfiguracija šifrovanja je sačuvana", "encryption_server": "Ime servera", "encryption_server_enter": "Unesite vaše ime domena", - "encryption_server_desc": "Kako biste koristili HTTPS, potrebno je da unesete ime servera koje se podudara sa SSL sertifikatom.", "encryption_redirect": "Automatski preusmeri na HTTPS", "encryption_redirect_desc": "Ako je označeno, AdGuard Home će vas automatski preusmeravati sa HTTP na HTTPS adrese.", "encryption_https": "HTTPS port", @@ -386,7 +384,6 @@ "client_edit": "Izmeni klijent", "client_identifier": "Identifikator", "ip_address": "IP adresa", - "client_identifier_desc": "Klijenti mogu da budu prepoznati po IP adresi ili MAC adresi. Imajte na umu da je korišćenje MAC adrese kao identifikatora moguće samo ako je AdGuard Home takođe a <0>DHCP server", "form_enter_ip": "Unesite IP", "form_enter_mac": "Unesite MAC", "form_enter_id": "Unesite identifikator", @@ -529,7 +526,6 @@ "check_ip": "IP adrese: {{ip}}", "check_cname": "CNAME: {{cname}}", "check_reason": "Razlog: {{reason}}", - "check_rule": "Pravilo: {{rule}}", "check_service": "Ime usluge: {{service}}", "service_name": "Ime usluge", "check_not_found": "Nije pronađeno na vašoj listi filtera", diff --git a/client/src/__locales/sv.json b/client/src/__locales/sv.json index 69105d28..0fae1dda 100644 --- a/client/src/__locales/sv.json +++ b/client/src/__locales/sv.json @@ -177,7 +177,6 @@ "source_label": "Källa", "found_in_known_domain_db": "Hittad i domändatabas.", "category_label": "Kategori", - "rule_label": "Regel", "unknown_filter": "Okänt filter {{filterId}}", "install_welcome_title": "Välkommen till AdGuard Home!", "install_welcome_desc": "AdGuard Home är en DNS-server för nätverkstäckande annons- och spårningsblockering. Dess syfte är att de dig kontroll över hela nätverket och alla dina enheter, utan behov av att använda klientbaserade program.", @@ -235,7 +234,6 @@ "encryption_config_saved": "Krypteringsinställningar sparade", "encryption_server": "Servernamn", "encryption_server_enter": "Skriv in ditt domännamn", - "encryption_server_desc": "För att använda HTTPS behöver du skriva in servernamnet som stämmer överens med ditt SSL-certifikat.", "encryption_redirect": "Omdirigera till HTTPS automatiskt", "encryption_redirect_desc": "Om bockad kommer AdGuard Home automatiskt att omdirigera dig från HTTP till HTTPS-adresser.", "encryption_https": "HTTPS-port", @@ -287,7 +285,6 @@ "client_edit": "Redigera klient", "client_identifier": "Identifikator", "ip_address": "IP-adress", - "client_identifier_desc": "Klienter kan identifieras genom IP-adresser eller MAC-adresser. Notera att användning av MAC som identifierare bara är möjligt om AdGuard Home också är en <0>DHCP-server", "form_enter_ip": "Skriv in IP", "form_enter_mac": "Skriv in MAC", "form_client_name": "Skriv in klientnamn", diff --git a/client/src/__locales/th.json b/client/src/__locales/th.json index b5197b4c..aeb75325 100644 --- a/client/src/__locales/th.json +++ b/client/src/__locales/th.json @@ -200,7 +200,6 @@ "source_label": "ที่มา", "found_in_known_domain_db": "พบในฐานข้อมูลโดเมนที่รู้จัก", "category_label": "ประเภท", - "rule_label": "กฎ", "unknown_filter": "ตัวกรองที่ไม่รู้จัก {{filterId}}", "install_welcome_title": "ยินดีต้อนรับสู่ AdGuard Home", "install_welcome_desc": "AdGuard Home เป็นเซิร์ฟเวอร์ DNS ปิดกั้นโฆษณาและติดตามทั่วทั้งเครือข่าย วัตถุประสงค์คือเพื่อให้คุณควบคุมเครือข่ายทั้งหมดและอุปกรณ์ทั้งหมดของคุณและไม่จำเป็นต้องใช้โปรแกรมฝั่งไคลเอ็นต์", @@ -258,7 +257,6 @@ "encryption_config_saved": "บันทึกการตั้งค่าเข้ารหัสเรียบร้อยแล้ว", "encryption_server": "ชื่อเซิร์ฟเวอร์", "encryption_server_enter": "ป้อนชื่อโดเมน", - "encryption_server_desc": "ในการใช้ HTTPS คุณต้องป้อนชื่อเซิร์ฟเวอร์ที่ตรงกับใบรับรอง SSL ของคุณ", "encryption_redirect": "ไปเส้นทาง HTTPS อัตโนมัติ", "encryption_redirect_desc": "หากเลือกตัวเลือกนี้ AdGuard Home จะเปลี่ยนเส้นทางคุณจากที่อยู่ HTTP ไปยัง HTTPS โดยอัตโนมัติ", "encryption_https": "พอร์ท HTTPS", @@ -312,7 +310,6 @@ "client_edit": "แก้ไขเครื่องลูกข่าย", "client_identifier": "ตรวจสอบโดย", "ip_address": "IP addresses", - "client_identifier_desc": "ลูกค้าสามารถระบุได้โดยที่อยู่ IP, CIDR, ที่อยู่ MAC โปรดทราบว่าการใช้ MAC เป็นตัวระบุเป็นไปได้ก็ต่อเมื่อ AdGuard Home เป็น <0>เซิร์ฟเวอร์ DHCP ด้วย", "form_enter_ip": "กรอก IP", "form_enter_mac": "กรอก MAC", "form_enter_id": "ป้อนตัวระบุ", diff --git a/client/src/__locales/tr.json b/client/src/__locales/tr.json index 0f9b2047..0276740d 100644 --- a/client/src/__locales/tr.json +++ b/client/src/__locales/tr.json @@ -32,6 +32,7 @@ "form_error_ip_format": "Geçersiz IP biçimi", "form_error_mac_format": "Geçersiz MAC biçimi", "form_error_client_id_format": "Geçersiz istemci kimliği biçimi", + "form_error_server_name": "Geçersiz sunucu adı", "form_error_positive": "0'dan büyük olmalı", "form_error_negative": "0 veya daha büyük olmalıdır", "range_end_error": "Başlangıç aralığından daha büyük olmalı", @@ -249,8 +250,13 @@ "blocking_ipv6": "IPv6 engelleme", "dns_over_https": "DNS-over-HTTPS", "dns_over_tls": "DNS-over-TLS", + "dns_over_quic": "DNS-over-QUIC", + "client_id": "İstemci Kimliği", + "client_id_placeholder": "İstemci Kimliği girin", + "client_id_desc": "Farklı istemciler, özel bir istemci kimliği ile tanımlanabilir. Burada istemcileri nasıl belirleyeceğiniz hakkında daha fazla bilgi edinebilirsiniz.", "download_mobileconfig_doh": "DNS-over-HTTPS için .mobileconfig dosyasını indir", "download_mobileconfig_dot": "DNS-over-TLS için .mobileconfig dosyasını indir", + "download_mobileconfig": "Yapılandırma dosyasını indir", "plain_dns": "Sade DNS", "form_enter_rate_limit": "Sıklık limitini girin", "rate_limit": "Sıklık limiti", @@ -330,7 +336,7 @@ "encryption_config_saved": "Şifreleme ayarı kaydedildi", "encryption_server": "Sunucu adı", "encryption_server_enter": "Alan adınızı girin", - "encryption_server_desc": "HTTPS kullanmak için SSL sertifikanızla eşleşen sunucu adını girmeniz gerekir", + "encryption_server_desc": "HTTPS kullanmak için, SSL sertifikanız veya joker karakter sertifikanızla eşleşen sunucu adını girmeniz gerekir. Alan ayarlanmazsa, herhangi bir alan adı için TKG bağlantılarını kabul eder.", "encryption_redirect": "Otomatik olarak HTTPS'e yönlendir", "encryption_redirect_desc": "Etkinleştirirseniz AdGuard Home sizi HTTP yerine HTTPS adreslerine yönlendirir.", "encryption_https": "HTTPS bağlantı noktası", @@ -386,7 +392,7 @@ "client_edit": "İstemciyi düzenle", "client_identifier": "Tanımlayıcı", "ip_address": "IP adresi", - "client_identifier_desc": "İstemciler IP adresleri veya MAC adresleri ile tanımlanabilir. Lütfen not edin, MAC adresi ile tanımlamayı kullanmak için AdGuard Home'un <0>DHCP Sunucusu olması gerekir.", + "client_identifier_desc": "İstemciler IP adresi, CIDR, MAC adresi veya özel bir istemci kimliği ile tanımlanabilir (DoT/DoH/DoQ için kullanılabilir). <0>Burada istemcileri nasıl belirleyeceğiniz hakkında daha fazla bilgi edinebilirsiniz.", "form_enter_ip": "IP Girin", "form_enter_mac": "MAC Girin", "form_enter_id": "Tanımlayıcı girin", @@ -430,6 +436,7 @@ "setup_dns_privacy_other_3": "<0>dnscrypt-proxy, <1>DNS-over-HTTPS destekler.", "setup_dns_privacy_other_4": "<0>Mozilla Firefox, <1>DNS-over-HTTPS desteklemektedir.", "setup_dns_privacy_other_5": "<0>Burada ve <1>burada daha fazla uygulama bulacaksınız.", + "setup_dns_privacy_ioc_mac": "iOS ve macOS yapılandırması", "setup_dns_notice": "<1>DNS-over-HTTPS veya <1>DNS-over-TLS kullanmak için, AdGuard Home ayarlarında <0>Şifreleme yapılandırmasını yapmanız gerekir.", "rewrite_added": "\"{{key}}\" için DNS yeniden yazımı başarıyla eklendi", "rewrite_deleted": "\"{{key}}\" için DNS yeniden yazımı başarıyla silindi", @@ -529,7 +536,6 @@ "check_ip": "IP adresleri: {{ip}}", "check_cname": "CNAME: {{cname}}", "check_reason": "Sebep: {{reason}}", - "check_rule": "Kural: {{rule}}", "check_service": "Hizmet adı: {{service}}", "service_name": "Servis adı", "check_not_found": "Filtre listelerinizde bulunamadı", diff --git a/client/src/__locales/vi.json b/client/src/__locales/vi.json index 6e0a2faf..85a5df2f 100644 --- a/client/src/__locales/vi.json +++ b/client/src/__locales/vi.json @@ -32,6 +32,7 @@ "form_error_ip_format": "Định dạng IPv4 không hợp lệ", "form_error_mac_format": "Định dạng MAC không hợp lệ", "form_error_client_id_format": "Định dạng client ID không hợp lệ", + "form_error_server_name": "Tên máy chủ không hợp lệ", "form_error_positive": "Phải lớn hơn 0", "form_error_negative": "Phải lớn hơn hoặc bằng 0", "range_end_error": "Phải lớn hơn khoảng bắt đầu", @@ -133,6 +134,7 @@ "encryption_settings": "Cài đặt mã hóa", "dhcp_settings": "Cài đặt DHCP", "upstream_dns": "Máy chủ DNS tìm kiếm", + "upstream_dns_help": "Nhập địa chỉ máy chủ một trên mỗi dòng. Tìm hiểu thêm về cách định cấu hình máy chủ DNS ngược dòng.", "upstream_dns_configured_in_file": "Cấu hình tại {{path}}", "test_upstream_btn": "Kiểm tra", "upstreams": "Nguồn", @@ -197,6 +199,9 @@ "unblock": "Bỏ chặn", "block": "Chặn", "disallow_this_client": "Không cho phép client này", + "allow_this_client": "Cho phép ứng dụng khách này", + "block_for_this_client_only": "Chỉ chặn ứng dụng khách này", + "unblock_for_this_client_only": "Chỉ hủy chặn ứng dụng khách này", "time_table_header": "Thời gian", "date": "Ngày", "domain_name_table_header": "Tên miền", @@ -245,6 +250,13 @@ "blocking_ipv6": "Chặn IPv6", "dns_over_https": "DNS-over-HTTPS", "dns_over_tls": "DNS-over-TLS", + "dns_over_quic": "DNS-over-QUIC", + "client_id": "ID khách hàng", + "client_id_placeholder": "Nhập ID khách hàng", + "client_id_desc": "Các khách hàng khác nhau có thể được xác định bằng một ID khách hàng đặc biệt. Tại đây bạn có thể tìm hiểu thêm về cách xác định khách hàng.", + "download_mobileconfig_doh": "Tải xuống .mobileconfig cho DNS-over-HTTPS", + "download_mobileconfig_dot": "Tải xuống .mobileconfig cho DNS-over-TLS", + "download_mobileconfig": "Tải xuống tệp cấu hình", "plain_dns": "DNS thuần", "form_enter_rate_limit": "Nhập giới hạn yêu cầu", "rate_limit": "Giới hạn yêu cầu", @@ -254,6 +266,7 @@ "blocking_ipv4_desc": "Địa chỉ IP được trả lại cho một yêu cầu A bị chặn", "blocking_ipv6_desc": "Địa chỉ IP được trả lại cho một yêu cầu AAA bị chặn", "blocking_mode_default": "Mặc định: Trả lời với NXDOMAIN khi bị chặn bởi quy tắc kiểu Adblock; phản hồi với địa chỉ IP được chỉ định trong quy tắc khi bị chặn bởi quy tắc / etc / hosts-style", + "blocking_mode_refused": "REFUSED: Trả lời bằng mã REFUSED", "blocking_mode_nxdomain": "NXDOMAIN: Phản hổi với mã NXDOMAIN", "blocking_mode_null_ip": "Null IP: Trả lời bằng không địa chỉ IP (0.0.0.0 cho A; :: cho AAAA)", "blocking_mode_custom_ip": "IP tùy chỉnh: Phản hồi với địa chỉ IP đã được tiết lập", @@ -262,7 +275,6 @@ "source_label": "Nguồn", "found_in_known_domain_db": "Tìm thấy trong cơ sở dữ liệu tên miền", "category_label": "Thể loại", - "rule_label": "Quy tắc", "list_label": "Danh sách", "unknown_filter": "Bộ lọc không rõ {{filterId}}", "known_tracker": "Theo dõi đã biết", @@ -323,13 +335,14 @@ "encryption_config_saved": "Đã lưu cấu hình mã hóa", "encryption_server": "Tên máy chủ", "encryption_server_enter": "Nhập tên miền của bạn", - "encryption_server_desc": "Để sử dụng HTTPS, bạn cần nhập tên máy chủ phù hợp với chứng chỉ SSL của bạn.", "encryption_redirect": "Tự động chuyển hướng đến HTTPS", "encryption_redirect_desc": "Nếu được chọn, AdGuard Home sẽ tự động chuyển hướng bạn từ địa chỉ HTTP sang địa chỉ HTTPS.", "encryption_https": "Cổng HTTPS", "encryption_https_desc": "Nếu cổng HTTPS được định cấu hình, giao diện quản trị viên AdGuard Home sẽ có thể truy cập thông qua HTTPS và nó cũng sẽ cung cấp DNS-over-HTTPS trên vị trí '/dns-query'.", "encryption_dot": "Cổng DNS-over-TLS", "encryption_dot_desc": "Nếu cổng này được định cấu hình, AdGuard Home sẽ chạy máy chủ DNS-over-TLS trên cổng này.", + "encryption_doq": "Cổng DNS-over-QUIC", + "encryption_doq_desc": "Nếu cổng này được định cấu hình, AdGuard Home sẽ chạy máy chủ DNS qua QUIC trên cổng này. Đó là thử nghiệm và có thể không đáng tin cậy. Ngoài ra, không có quá nhiều khách hàng hỗ trợ nó vào lúc này.", "encryption_certificates": "Chứng chỉ", "encryption_certificates_desc": "Để sử dụng mã hóa, bạn cần cung cấp chuỗi chứng chỉ SSL hợp lệ cho miền của mình. Bạn có thể nhận chứng chỉ miễn phí trên <0>{{link}} hoặc bạn có thể mua chứng chỉ từ một trong các Cơ Quan Chứng Nhận tin cậy.", "encryption_certificates_input": "Sao chép/dán chứng chỉ được mã hóa PEM của bạn tại đây.", @@ -377,7 +390,6 @@ "client_edit": "Chỉnh Sửa Máy Khách", "client_identifier": "Định danh", "ip_address": "Địa chỉ IP", - "client_identifier_desc": "Các máy khách có thể được xác định bằng địa chỉ IP hoặc địa chỉ MAC. Xin lưu ý rằng chỉ có thể sử dụng MAC làm định danh nếu AdGuard Home cũng là <0>máy chủ DHCP", "form_enter_ip": "Nhập IP", "form_enter_mac": "Nhập MAC", "form_enter_id": "Nhập định danh", @@ -408,6 +420,7 @@ "dns_privacy": "DNS Riêng Tư", "setup_dns_privacy_1": "<0>DNS-over-TLS: Sử dụng chuỗi <1>{{address}}.", "setup_dns_privacy_2": "<0>DNS-over-HTTPS: Sử dụng chuỗi <1>{{address}}.", + "setup_dns_privacy_3": "<0>Đây là danh sách phần mềm bạn có thể sử dụng.", "setup_dns_privacy_4": "Trên thiết bị chạy iOS 14 hoặc macOS Big Sur bạn có thể tải tệp '.mobileconfig' đặc biệt có chứa máy chủ DNS-over-HTTPS hoặc DNS-over-TLS trong thiết lập DNS.", "setup_dns_privacy_android_1": "Android 9 hỗ trợ DNS trên TLS nguyên bản. Để định cấu hình, hãy đi tới Cài đặt → Mạng & internet → Nâng cao → DNS Riêng Tư và nhập tên miền của bạn vào đó.", "setup_dns_privacy_android_2": "<0>AdGuard for Android hỗ trợ <1>DNS-over-HTTPS và <1>DNS-over-TLS.", @@ -420,6 +433,7 @@ "setup_dns_privacy_other_3": "<0>dnscrypt-proxy hỗ trợ <1>DNS-over-HTTPS.", "setup_dns_privacy_other_4": "<0>Mozilla Firefox hỗ trợ <1>DNS-over-HTTPS.", "setup_dns_privacy_other_5": "Bạn sẽ tìm thấy nhiều triển khai hơn <0>tại đây và <1>tại đây.", + "setup_dns_privacy_ioc_mac": "Cấu hình iOS và macOS", "setup_dns_notice": "Để sử dụng <1>DNS-over-HTTPS hoặc <1>DNS-over-TLS, bạn cần <0>định cấu hình Mã hóa trong cài đặt AdGuard Home.", "rewrite_added": "DNS viết lại cho \"{{key}}\" đã thêm thành công", "rewrite_deleted": "DNS viết lại cho \"{{key}}\" đã xóa thành công", @@ -519,8 +533,8 @@ "check_ip": "Địa chỉ IP: {{ip}}", "check_cname": "CNAME: {{cname}}", "check_reason": "Lý do: {{reason}}", - "check_rule": "Quy tắc: {{rule}}", "check_service": "Tên dịch vụ: {{service}}", + "service_name": "Tên dịch vụ", "check_not_found": "Không tìm thấy trong danh sách bộ lọc của bạn", "client_confirm_block": "Bạn có muốn chặn người dùng {{ip}}?", "client_confirm_unblock": "Bạn có muốn bỏ chặn người dùng {{ip}}?", @@ -555,6 +569,12 @@ "cache_size_desc": "Kích thước cache DNS (bytes)", "cache_ttl_min_override": "Ghi đè TTL tối thiểu", "cache_ttl_max_override": "Ghi đè TTL tối đa", + "enter_cache_size": "Nhập kích thước bộ nhớ cache (byte)", + "enter_cache_ttl_min_override": "Nhập TTL tối thiểu (giây)", + "enter_cache_ttl_max_override": "Nhập TTL tối đa (giây)", + "cache_ttl_min_override_desc": "Mở rộng giá trị thời gian tồn tại ngắn (giây) nhận được từ máy chủ ngược dòng khi phản hồi DNS vào bộ nhớ đệm", + "cache_ttl_max_override_desc": "Đặt giá trị thời gian tồn tại tối đa (giây) cho các mục nhập trong bộ nhớ cache DNS", + "ttl_cache_validation": "Giá trị TTL trong bộ nhớ cache tối thiểu phải nhỏ hơn hoặc bằng giá trị lớn nhất", "filter_category_general": "Chung", "filter_category_security": "Bảo mật", "filter_category_regional": "Khu vực", @@ -566,5 +586,8 @@ "setup_config_to_enable_dhcp_server": "Thiết lập cấu hình để bật máy chủ DHCP", "original_response": "Phản hồi gốc", "click_to_view_queries": "Nhấp để xem truy xuất", - "port_53_faq_link": "Cổng 53 thường được sử dụng \"DNSStubListener\" hoặc \"systemd-resolved\". Vui lòng đọc <0>hướng dẫn để giải quyết vấn đề này." + "port_53_faq_link": "Cổng 53 thường được sử dụng \"DNSStubListener\" hoặc \"systemd-resolved\". Vui lòng đọc <0>hướng dẫn để giải quyết vấn đề này.", + "adg_will_drop_dns_queries": "AdGuard Home sẽ loại bỏ tất cả các truy vấn DNS từ ứng dụng khách này.", + "client_not_in_allowed_clients": "Ứng dụng khách không được phép vì nó không có trong danh sách \"Ứng dụng khách được phép\".", + "experimental": "Thử nghiệm" } \ No newline at end of file diff --git a/client/src/__locales/zh-cn.json b/client/src/__locales/zh-cn.json index 41199e56..54b72644 100644 --- a/client/src/__locales/zh-cn.json +++ b/client/src/__locales/zh-cn.json @@ -32,6 +32,7 @@ "form_error_ip_format": "无效的 IPv4 格式", "form_error_mac_format": "无效的 MAC 格式", "form_error_client_id_format": "无效的客户端 ID 格式", + "form_error_server_name": "无效的服务器名", "form_error_positive": "必须大于 0", "form_error_negative": "必须大于等于 0", "range_end_error": "必须大于范围起始值", @@ -249,8 +250,13 @@ "blocking_ipv6": "拦截 IPv6", "dns_over_https": "DNS-over-HTTPS", "dns_over_tls": "DNS-over-TLS", + "dns_over_quic": "DNS-over-QUIC", + "client_id": "客户端 ID", + "client_id_placeholder": "输入客户端 ID", + "client_id_desc": "可根据一个特殊的客户端 ID 识别不同客户端。在 这里你可以了解到更多关于如何识别客户端的信息。", "download_mobileconfig_doh": "下载适用于 DNS-over-HTTPS 的 .mobileconfig", "download_mobileconfig_dot": "下载适用于 DNS-over-TLS 的 .mobileconfig", + "download_mobileconfig": "下载配置文件", "plain_dns": "无加密DNS", "form_enter_rate_limit": "输入限制速率", "rate_limit": "速度限制", @@ -330,7 +336,7 @@ "encryption_config_saved": "加密配置已保存", "encryption_server": "服务器名称", "encryption_server_enter": "输入您的域名", - "encryption_server_desc": "若要使用 HTTPS ,您需要输入与 SSL 证书相匹配的服务器名称。", + "encryption_server_desc": "为了使用 HTTPS,请您输入与 SSL 证书或通配证书相匹配的服务器名称。如此字段未设置,服务器将要为所有域名接受 TLS 连接。", "encryption_redirect": "HTTPS 自动重定向", "encryption_redirect_desc": "如果勾选此选项,AdGuard Home 将自动将您从 HTTP 重定向到 HTTPS 地址。", "encryption_https": "HTTPS 端口", @@ -386,7 +392,7 @@ "client_edit": "编辑客户端", "client_identifier": "标识符", "ip_address": "IP 地址", - "client_identifier_desc": "客户端可通过 IP 地址或 MAC 地址识别。请注意,如 AdGuard Home 也是 <0>DHCP 服务器,则仅能将 MAC 用作标识符", + "client_identifier_desc": "客户端可通过 IP 、MAC 地址、CIDR 或特殊 ID(可用于 DoT/DoH/DoQ)被识别。<0>这里您可多了解如何识别客户端。", "form_enter_ip": "输入 IP", "form_enter_mac": "输入 MAC", "form_enter_id": "输入标识符", @@ -430,6 +436,7 @@ "setup_dns_privacy_other_3": "<0>dnscrypt-proxy 支持 <1>DNS-over-HTTPS。", "setup_dns_privacy_other_4": "<0>Mozilla Firefox 支持 <1>DNS-over-HTTPS。", "setup_dns_privacy_other_5": "您可以从 <0>这里 和 <1>这里 找到更多的实施方案。", + "setup_dns_privacy_ioc_mac": "iOS 和 macOS 配置", "setup_dns_notice": "为了使用 <1>DNS-over-HTTPS 或者 <1>DNS-over-TLS ,您需要在 AdGuard Home 设置中 <0>配置加密 。", "rewrite_added": "已成功添加 \"{{key}}\" 的 DNS 重写", "rewrite_deleted": "已成功删除 \"{{key}}\" 的 DNS 重写", @@ -529,7 +536,6 @@ "check_ip": "IP地址:{{ip}}", "check_cname": "CNAME: {{cname}}", "check_reason": "原因:{{reason}}", - "check_rule": "规则:{{rule}}", "check_service": "服务名称:{{service}}", "service_name": "服务名称", "check_not_found": "未在您的筛选列表中找到", diff --git a/client/src/__locales/zh-hk.json b/client/src/__locales/zh-hk.json index 044980c1..2b52d4ed 100644 --- a/client/src/__locales/zh-hk.json +++ b/client/src/__locales/zh-hk.json @@ -269,7 +269,6 @@ "source_label": "來源", "found_in_known_domain_db": "在已知網域資料庫中找到。", "category_label": "類別", - "rule_label": "規則", "list_label": "清單", "unknown_filter": "未知過濾器 {{filterId}}", "known_tracker": "已知追蹤器", @@ -330,7 +329,6 @@ "encryption_config_saved": "加密設定已儲存", "encryption_server": "伺服器名稱", "encryption_server_enter": "輸入您的網域名稱", - "encryption_server_desc": "要使用 HTTPS,您必須輸入與您 SSL 憑證相符的伺服器名稱。", "encryption_redirect": "自動重新導向到 HTTPS", "encryption_redirect_desc": "如果啟用,AdGuard Home 將會自動導向 HTTP 到 HTTPS。", "encryption_https": "HTTPS 連接埠", @@ -386,7 +384,6 @@ "client_edit": "編輯用戶端", "client_identifier": "識別碼", "ip_address": "IP 位址", - "client_identifier_desc": "可通過 IP 地址、CIDR、MAC 地址來辨識使用者裝置。注意:必須使用 AdGuard Home 內建 <0>DHCP 伺服器 才能偵測 MAC 地址。", "form_enter_ip": "輸入 IP", "form_enter_mac": "輸入 MAC 地址", "form_enter_id": "輸入識別碼", @@ -529,7 +526,6 @@ "check_ip": "IP 位址:{{ip}}", "check_cname": "CNAME:{{cname}}", "check_reason": "原因:{{reason}}", - "check_rule": "規則:{{rule}}", "check_service": "服務名稱:{{service}}", "service_name": "服務名稱", "check_not_found": "未在您的過濾清單中找到", diff --git a/client/src/__locales/zh-tw.json b/client/src/__locales/zh-tw.json index fb3ed64a..258c661c 100644 --- a/client/src/__locales/zh-tw.json +++ b/client/src/__locales/zh-tw.json @@ -330,7 +330,7 @@ "encryption_config_saved": "加密配置被儲存", "encryption_server": "伺服器名稱", "encryption_server_enter": "輸入您的域名", - "encryption_server_desc": "為了使用 HTTPS,您需要輸入與您的安全通訊端層(SSL)憑證相符的伺服器名稱。", + "encryption_server_desc": "為了使用 HTTPS,您需要輸入與您的安全通訊端層(SSL)憑證或萬用字元憑證相符的伺服器名稱。如果此欄位未被設定,它將接受向任何網域的傳輸層安全性協定(TLS)連線。", "encryption_redirect": "自動地重新導向到 HTTPS", "encryption_redirect_desc": "如果被勾選,AdGuard Home 將自動地重新導向您從 HTTP 到 HTTPS 位址。", "encryption_https": "HTTPS 連接埠", @@ -386,7 +386,6 @@ "client_edit": "編輯用戶端", "client_identifier": "識別碼", "ip_address": "IP 位址", - "client_identifier_desc": "用戶端可被 IP 位址、無類別網域間路由(CIDR)或媒體存取控制(MAC)位址識別。請注意,只要 AdGuard Home 也是<0>動態主機設定協定(DHCP)伺服器,使用 MAC 作為識別碼是可能的", "form_enter_ip": "輸入 IP", "form_enter_mac": "輸入媒體存取控制(MAC)", "form_enter_id": "輸入識別碼", @@ -529,7 +528,6 @@ "check_ip": "IP 位址:{{ip}}", "check_cname": "正規名稱(CNAME):{{cname}}", "check_reason": "原因:{{reason}}", - "check_rule": "規則:{{rule}}", "check_service": "服務名稱:{{service}}", "service_name": "服務名稱", "check_not_found": "未在您的過濾器中被找到", diff --git a/scripts/translations/download.js b/scripts/translations/download.js index ef176c8d..d3db8e12 100644 --- a/scripts/translations/download.js +++ b/scripts/translations/download.js @@ -80,10 +80,18 @@ const request = (url, locale) => ( return `${locale} - Not OK`; })); +/** + * Sleep. + * @param {number} ms + */ +const sleep = (ms) => new Promise((resolve) => { + setTimeout(resolve, ms); +}); + /** * Download locales */ -const download = () => { +const download = async () => { const locales = LOCALES_LIST; if (!TWOSKY_URI) { @@ -91,10 +99,16 @@ const download = () => { return; } - const requests = locales.map((locale) => { + const requests = []; + for (let i = 0; i < locales.length; i++) { + const locale = locales[i]; const url = getRequestUrl(locale, TWOSKY_URI, TWOSKY_PROJECT_ID); - return request(url, locale); - }); + requests.push(request(url, locale)); + + // Don't request the Crowdin API too aggressively to prevent spurious + // 400 errors. + await sleep(200); + } Promise .all(requests) diff --git a/scripts/translations/package-lock.json b/scripts/translations/package-lock.json index cd75b683..5361d859 100644 --- a/scripts/translations/package-lock.json +++ b/scripts/translations/package-lock.json @@ -1,6 +1,6 @@ { "name": "translations", - "version": "0.2.0", + "version": "0.1.0", "lockfileVersion": 1, "requires": true, "dependencies": { From 2bf2d5a19d6f4fe12db349b8f6ab9db504dfc225 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Wed, 10 Feb 2021 16:59:23 +0300 Subject: [PATCH 109/141] Pull request: all: set release date Merge in DNS/adguard-home from rel-date to master Squashed commit of the following: commit e9e03a408c70587fa28da94803b054a7557ac86b Author: Ainar Garipov Date: Wed Feb 10 16:44:52 2021 +0300 all: set release date --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dc3ebd4f..cb3239eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,9 +10,11 @@ and this project adheres to ## [Unreleased] +## [v0.105.0] - 2021-02-10 + ### Added - Added more services to the "Blocked services" list ([#2224], [#2401]). From 890f0322d8d4c20fb55604c3bda3995a64bf2502 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Wed, 10 Feb 2021 19:10:17 +0300 Subject: [PATCH 110/141] Pull request: all: imp changelog Merge in DNS/adguard-home from fix-chlog to master Squashed commit of the following: commit 9b6d68c6592ceeab50f30c5c9eb6363d0d772867 Author: Ainar Garipov Date: Wed Feb 10 17:19:14 2021 +0300 all: imp changelog --- CHANGELOG.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb3239eb..f68a9dc9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,10 @@ and this project adheres to ## [v0.106.0] - 2021-04-26 --> + + ## [v0.105.0] - 2021-02-10 ### Added @@ -151,9 +155,10 @@ and this project adheres to [Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.105.0...HEAD [v0.105.0]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.104.3...v0.105.0 ---> -[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.104.3...HEAD [v0.104.3]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.104.2...v0.104.3 [v0.104.2]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.104.1...v0.104.2 From e64df20e74e0bef9e29a1d4575963b1a3b59e8ba Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Thu, 11 Feb 2021 12:55:23 +0300 Subject: [PATCH 111/141] Pull request #991: scripts: fix docker version handling Merge in DNS/adguard-home from 2663-docker-version to master Closes #2663. Squashed commit of the following: commit f9b03fd12543a3975ea6dc45115e4ec0f73eba1e Author: Ainar Garipov Date: Thu Feb 11 12:40:06 2021 +0300 all: document changes commit 8bce414c9f25210420b6026cb4c21908c8b9c74f Author: Ainar Garipov Date: Thu Feb 11 12:22:45 2021 +0300 scripts: fix docker version handling --- CHANGELOG.md | 6 ++++++ scripts/make/build-docker.sh | 11 ++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f68a9dc9..f16494d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,12 @@ and this project adheres to ## [v0.105.1] - 2021-02-24 --> +### Fixed + +- Incorrect version tag in the Docker release ([#2663]). + +[#2663]: https://github.com/AdguardTeam/AdGuardHome/issues/2663 + ## [v0.105.0] - 2021-02-10 ### Added diff --git a/scripts/make/build-docker.sh b/scripts/make/build-docker.sh index d8eb2737..29b86ba6 100644 --- a/scripts/make/build-docker.sh +++ b/scripts/make/build-docker.sh @@ -17,7 +17,16 @@ set -e -f -u readonly channel="$CHANNEL" readonly commit="$COMMIT" readonly dist_dir="$DIST_DIR" -readonly version="$VERSION" + +if [ "${VERSION:-}" = 'v0.0.0' -o "${VERSION:-}" = '' ] +then + readonly version="$(sh ./scripts/make/version.sh)" +else + readonly version="$VERSION" +fi + +echo $version +exit 0 # Allow users to use sudo. readonly sudo_cmd="${SUDO:-}" From 44168292d5f466b9e7cda271987f03363ab6d6a8 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Thu, 11 Feb 2021 13:46:59 +0300 Subject: [PATCH 112/141] Pull request: 2662 dnscrypt logs Merge in DNS/adguard-home from 2662-dnscrypt-logs to master Closes #2662. Squashed commit of the following: commit 05f6742b5c73e1d150834965ae3a54ca06ef8e24 Author: Ainar Garipov Date: Thu Feb 11 12:58:08 2021 +0300 all: imp docs commit ee0b8c574c1cb5302a5ffb62d2fec4126509b2e8 Merge: aaa8c6b8 e64df20e Author: Ainar Garipov Date: Thu Feb 11 12:56:44 2021 +0300 Merge branch 'master' into 2662-dnscrypt-logs commit aaa8c6b8085679f4acd234527bd03cb0b2520b4f Author: Ainar Garipov Date: Thu Feb 11 12:38:49 2021 +0300 all: document changes commit 57b6a4d8e95e87d928274d095dc2004f1591d940 Author: Ainar Garipov Date: Thu Feb 11 12:36:22 2021 +0300 all: fix dnscrypt in logs --- CHANGELOG.md | 4 ++++ client/src/__locales/en.json | 3 ++- client/src/helpers/constants.js | 1 + internal/querylog/qlog.go | 1 + 4 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f16494d9..1b17ff8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,9 +20,13 @@ and this project adheres to ### Fixed - Incorrect version tag in the Docker release ([#2663]). +- DNSCrypt queries weren't marked as such in logs ([#2662]). +[#2662]: https://github.com/AdguardTeam/AdGuardHome/issues/2662 [#2663]: https://github.com/AdguardTeam/AdGuardHome/issues/2663 + + ## [v0.105.0] - 2021-02-10 ### Added diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json index 697a7307..b558dd60 100644 --- a/client/src/__locales/en.json +++ b/client/src/__locales/en.json @@ -248,6 +248,7 @@ "custom_ip": "Custom IP", "blocking_ipv4": "Blocking IPv4", "blocking_ipv6": "Blocking IPv6", + "dnscrypt": "DNSCrypt", "dns_over_https": "DNS-over-HTTPS", "dns_over_tls": "DNS-over-TLS", "dns_over_quic": "DNS-over-QUIC", @@ -593,4 +594,4 @@ "adg_will_drop_dns_queries": "AdGuard Home will be dropping all DNS queries from this client.", "client_not_in_allowed_clients": "The client is not allowed because it is not in the \"Allowed clients\" list.", "experimental": "Experimental" -} \ No newline at end of file +} diff --git a/client/src/helpers/constants.js b/client/src/helpers/constants.js index bab8c925..0c9919ac 100644 --- a/client/src/helpers/constants.js +++ b/client/src/helpers/constants.js @@ -534,6 +534,7 @@ export const BLOCK_ACTIONS = { }; export const SCHEME_TO_PROTOCOL_MAP = { + dnscrypt: 'dnscrypt', doh: 'dns_over_https', dot: 'dns_over_tls', doq: 'dns_over_quic', diff --git a/internal/querylog/qlog.go b/internal/querylog/qlog.go index 41ce9823..4726f075 100644 --- a/internal/querylog/qlog.go +++ b/internal/querylog/qlog.go @@ -53,6 +53,7 @@ func NewClientProto(s string) (cp ClientProto, err error) { ClientProtoDOH, ClientProtoDOQ, ClientProtoDOT, + ClientProtoDNSCrypt, ClientProtoPlain: return cp, nil From f016ae172c640b55691c6701f9590766dd005c96 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Thu, 11 Feb 2021 14:10:42 +0300 Subject: [PATCH 113/141] Pull request: home: inc req size for some apis Merge in DNS/adguard-home from 2666-req-body-lim to master Updates #2666. Squashed commit of the following: commit a525974aee54831963e3f95c8186d44f1752e9c7 Merge: 947703f3 44168292 Author: Ainar Garipov Date: Thu Feb 11 13:48:06 2021 +0300 Merge branch 'master' into 2666-req-body-lim commit 947703f36e1ee0ab08f938850f76824b7899d7e1 Author: Ainar Garipov Date: Thu Feb 11 13:28:54 2021 +0300 home: inc req size for some apis --- CHANGELOG.md | 7 +++++++ internal/home/middlewares.go | 12 +++++++++++- internal/home/web.go | 2 +- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b17ff8b..7560957b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,13 +17,20 @@ and this project adheres to ## [v0.105.1] - 2021-02-24 --> +### Changed + +- Increase the HTTP API request body size limit for the `/control/access/set` + API ([#2666]). + ### Fixed +- Set the request body size limit for HTTPS reqeusts as well. - Incorrect version tag in the Docker release ([#2663]). - DNSCrypt queries weren't marked as such in logs ([#2662]). [#2662]: https://github.com/AdguardTeam/AdGuardHome/issues/2662 [#2663]: https://github.com/AdguardTeam/AdGuardHome/issues/2663 +[#2666]: https://github.com/AdguardTeam/AdGuardHome/issues/2666 diff --git a/internal/home/middlewares.go b/internal/home/middlewares.go index d530ace8..7d34f73e 100644 --- a/internal/home/middlewares.go +++ b/internal/home/middlewares.go @@ -30,7 +30,17 @@ const RequestBodySizeLimit = 64 * 1024 func limitRequestBody(h http.Handler) (limited http.Handler) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var err error - r.Body, err = aghio.LimitReadCloser(r.Body, RequestBodySizeLimit) + + var bodySizeLimit int64 = RequestBodySizeLimit + if u := r.URL; u.Path == "/control/access/set" { + // An exception for a poorly designed API. Remove once + // the new, better API is up. + // + // See https://github.com/AdguardTeam/AdGuardHome/issues/2666. + bodySizeLimit *= 4 + } + + r.Body, err = aghio.LimitReadCloser(r.Body, bodySizeLimit) if err != nil { log.Error("limitRequestBody: %s", err) diff --git a/internal/home/web.go b/internal/home/web.go index 0048f427..cdd19aba 100644 --- a/internal/home/web.go +++ b/internal/home/web.go @@ -259,7 +259,7 @@ func (web *Web) tlsServerLoop() { RootCAs: Context.tlsRoots, CipherSuites: Context.tlsCiphers, }, - Handler: Context.mux, + Handler: withMiddlewares(Context.mux, limitRequestBody), ReadTimeout: web.conf.ReadTimeout, ReadHeaderTimeout: web.conf.ReadHeaderTimeout, WriteTimeout: web.conf.WriteTimeout, From 841bb9bc35786c710414706ef38c2830024fbfed Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Thu, 11 Feb 2021 15:20:30 +0300 Subject: [PATCH 114/141] Pull request: dnsforward: do not check client srv name unless asked Merge in DNS/adguard-home from 2664-non-strict-sni to master Updates #2664. Squashed commit of the following: commit e8d625fe3b1f06f97328809a3330b37e5bd578d7 Author: Ainar Garipov Date: Thu Feb 11 14:46:52 2021 +0300 all: imp doc commit 10537b8bdf126eca9608353e57d92edba632232a Author: Ainar Garipov Date: Thu Feb 11 14:30:25 2021 +0300 dnsforward: do not check client srv name unless asked --- CHANGELOG.md | 9 +- internal/dnsforward/clientid.go | 165 ++++++++++++++++++ .../{dns_test.go => clientid_test.go} | 80 ++++++--- internal/dnsforward/dns.go | 152 ---------------- 4 files changed, 230 insertions(+), 176 deletions(-) create mode 100644 internal/dnsforward/clientid.go rename internal/dnsforward/{dns_test.go => clientid_test.go} (73%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7560957b..bfae45f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,17 +19,20 @@ and this project adheres to ### Changed -- Increase the HTTP API request body size limit for the `/control/access/set` - API ([#2666]). +- The server name sent by clients of TLS APIs is not only checked when + `strict_sni_check` is enabled ([#2664]). +- HTTP API request body size limit for the `/control/access/set` API is + increased ([#2666]). ### Fixed -- Set the request body size limit for HTTPS reqeusts as well. +- The request body size limit is now set for HTTPS requests as well. - Incorrect version tag in the Docker release ([#2663]). - DNSCrypt queries weren't marked as such in logs ([#2662]). [#2662]: https://github.com/AdguardTeam/AdGuardHome/issues/2662 [#2663]: https://github.com/AdguardTeam/AdGuardHome/issues/2663 +[#2664]: https://github.com/AdguardTeam/AdGuardHome/issues/2664 [#2666]: https://github.com/AdguardTeam/AdGuardHome/issues/2666 diff --git a/internal/dnsforward/clientid.go b/internal/dnsforward/clientid.go new file mode 100644 index 00000000..c497c7b7 --- /dev/null +++ b/internal/dnsforward/clientid.go @@ -0,0 +1,165 @@ +package dnsforward + +import ( + "crypto/tls" + "fmt" + "path" + "strings" + + "github.com/AdguardTeam/dnsproxy/proxy" + "github.com/lucas-clemente/quic-go" +) + +const maxDomainPartLen = 64 + +// ValidateClientID returns an error if clientID is not a valid client ID. +func ValidateClientID(clientID string) (err error) { + if len(clientID) > maxDomainPartLen { + return fmt.Errorf("client id %q is too long, max: %d", clientID, maxDomainPartLen) + } + + for i, r := range clientID { + if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == '-' { + continue + } + + return fmt.Errorf("invalid char %q at index %d in client id %q", r, i, clientID) + } + + return nil +} + +// clientIDFromClientServerName extracts and validates a client ID. hostSrvName +// is the server name of the host. cliSrvName is the server name as sent by the +// client. When strict is true, and client and host server name don't match, +// clientIDFromClientServerName will return an error. +func clientIDFromClientServerName(hostSrvName, cliSrvName string, strict bool) (clientID string, err error) { + if hostSrvName == cliSrvName { + return "", nil + } + + if !strings.HasSuffix(cliSrvName, hostSrvName) { + if !strict { + return "", nil + } + + return "", fmt.Errorf("client server name %q doesn't match host server name %q", cliSrvName, hostSrvName) + } + + clientID = cliSrvName[:len(cliSrvName)-len(hostSrvName)-1] + err = ValidateClientID(clientID) + if err != nil { + return "", fmt.Errorf("invalid client id: %w", err) + } + + return clientID, nil +} + +// processClientIDHTTPS extracts the client's ID from the path of the +// client's DNS-over-HTTPS request. +func processClientIDHTTPS(ctx *dnsContext) (rc resultCode) { + pctx := ctx.proxyCtx + r := pctx.HTTPRequest + if r == nil { + ctx.err = fmt.Errorf("proxy ctx http request of proto %s is nil", pctx.Proto) + + return resultCodeError + } + + origPath := r.URL.Path + parts := strings.Split(path.Clean(origPath), "/") + if parts[0] == "" { + parts = parts[1:] + } + + if len(parts) == 0 || parts[0] != "dns-query" { + ctx.err = fmt.Errorf("client id check: invalid path %q", origPath) + + return resultCodeError + } + + clientID := "" + switch len(parts) { + case 1: + // Just /dns-query, no client ID. + return resultCodeSuccess + case 2: + clientID = parts[1] + default: + ctx.err = fmt.Errorf("client id check: invalid path %q: extra parts", origPath) + + return resultCodeError + } + + err := ValidateClientID(clientID) + if err != nil { + ctx.err = fmt.Errorf("client id check: invalid client id: %w", err) + + return resultCodeError + } + + ctx.clientID = clientID + + return resultCodeSuccess +} + +// tlsConn is a narrow interface for *tls.Conn to simplify testing. +type tlsConn interface { + ConnectionState() (cs tls.ConnectionState) +} + +// quicSession is a narrow interface for quic.Session to simplify testing. +type quicSession interface { + ConnectionState() (cs quic.ConnectionState) +} + +// processClientID extracts the client's ID from the server name of the client's +// DOT or DOQ request or the path of the client's DOH. +func processClientID(dctx *dnsContext) (rc resultCode) { + pctx := dctx.proxyCtx + proto := pctx.Proto + if proto == proxy.ProtoHTTPS { + return processClientIDHTTPS(dctx) + } else if proto != proxy.ProtoTLS && proto != proxy.ProtoQUIC { + return resultCodeSuccess + } + + srvConf := dctx.srv.conf + hostSrvName := srvConf.TLSConfig.ServerName + if hostSrvName == "" { + return resultCodeSuccess + } + + cliSrvName := "" + if proto == proxy.ProtoTLS { + conn := pctx.Conn + tc, ok := conn.(tlsConn) + if !ok { + dctx.err = fmt.Errorf("proxy ctx conn of proto %s is %T, want *tls.Conn", proto, conn) + + return resultCodeError + } + + cliSrvName = tc.ConnectionState().ServerName + } else if proto == proxy.ProtoQUIC { + qs, ok := pctx.QUICSession.(quicSession) + if !ok { + dctx.err = fmt.Errorf("proxy ctx quic session of proto %s is %T, want quic.Session", proto, pctx.QUICSession) + + return resultCodeError + } + + cliSrvName = qs.ConnectionState().ServerName + } + + clientID, err := clientIDFromClientServerName(hostSrvName, cliSrvName, srvConf.StrictSNICheck) + if err != nil { + dctx.err = fmt.Errorf("client id check: %w", err) + + return resultCodeError + } + + dctx.clientID = clientID + + return resultCodeSuccess +} diff --git a/internal/dnsforward/dns_test.go b/internal/dnsforward/clientid_test.go similarity index 73% rename from internal/dnsforward/dns_test.go rename to internal/dnsforward/clientid_test.go index bd0ef4ab..503203f9 100644 --- a/internal/dnsforward/dns_test.go +++ b/internal/dnsforward/clientid_test.go @@ -10,6 +10,7 @@ import ( "github.com/AdguardTeam/dnsproxy/proxy" "github.com/lucas-clemente/quic-go" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) // testTLSConn is a tlsConn for tests. @@ -53,6 +54,7 @@ func TestProcessClientID(t *testing.T) { wantClientID string wantErrMsg string wantRes resultCode + strictSNI bool }{{ name: "udp", proto: proxy.ProtoUDP, @@ -61,6 +63,7 @@ func TestProcessClientID(t *testing.T) { wantClientID: "", wantErrMsg: "", wantRes: resultCodeSuccess, + strictSNI: false, }, { name: "tls_no_client_id", proto: proxy.ProtoTLS, @@ -69,6 +72,26 @@ func TestProcessClientID(t *testing.T) { wantClientID: "", wantErrMsg: "", wantRes: resultCodeSuccess, + strictSNI: true, + }, { + name: "tls_no_client_server_name", + proto: proxy.ProtoTLS, + hostSrvName: "example.com", + cliSrvName: "", + wantClientID: "", + wantErrMsg: `client id check: client server name "" ` + + `doesn't match host server name "example.com"`, + wantRes: resultCodeError, + strictSNI: true, + }, { + name: "tls_no_client_server_name_no_strict", + proto: proxy.ProtoTLS, + hostSrvName: "example.com", + cliSrvName: "", + wantClientID: "", + wantErrMsg: "", + wantRes: resultCodeSuccess, + strictSNI: false, }, { name: "tls_client_id", proto: proxy.ProtoTLS, @@ -77,30 +100,39 @@ func TestProcessClientID(t *testing.T) { wantClientID: "cli", wantErrMsg: "", wantRes: resultCodeSuccess, + strictSNI: true, }, { name: "tls_client_id_hostname_error", proto: proxy.ProtoTLS, hostSrvName: "example.com", cliSrvName: "cli.example.net", wantClientID: "", - wantErrMsg: `client id check: client server name "cli.example.net" doesn't match host server name "example.com"`, - wantRes: resultCodeError, + wantErrMsg: `client id check: client server name "cli.example.net" ` + + `doesn't match host server name "example.com"`, + wantRes: resultCodeError, + strictSNI: true, }, { name: "tls_invalid_client_id", proto: proxy.ProtoTLS, hostSrvName: "example.com", cliSrvName: "!!!.example.com", wantClientID: "", - wantErrMsg: `client id check: invalid client id: invalid char '!' at index 0 in client id "!!!"`, - wantRes: resultCodeError, + wantErrMsg: `client id check: invalid client id: invalid char '!' ` + + `at index 0 in client id "!!!"`, + wantRes: resultCodeError, + strictSNI: true, }, { - name: "tls_client_id_too_long", - proto: proxy.ProtoTLS, - hostSrvName: "example.com", - cliSrvName: "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789.example.com", + name: "tls_client_id_too_long", + proto: proxy.ProtoTLS, + hostSrvName: "example.com", + cliSrvName: `abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmno` + + `pqrstuvwxyz0123456789.example.com`, wantClientID: "", - wantErrMsg: `client id check: invalid client id: client id "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789" is too long, max: 64`, - wantRes: resultCodeError, + wantErrMsg: `client id check: invalid client id: client id "abcdefghijklmno` + + `pqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789" ` + + `is too long, max: 64`, + wantRes: resultCodeError, + strictSNI: true, }, { name: "quic_client_id", proto: proxy.ProtoQUIC, @@ -109,14 +141,17 @@ func TestProcessClientID(t *testing.T) { wantClientID: "cli", wantErrMsg: "", wantRes: resultCodeSuccess, + strictSNI: true, }} for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { + tlsConf := TLSConfig{ + ServerName: tc.hostSrvName, + StrictSNICheck: tc.strictSNI, + } srv := &Server{ - conf: ServerConfig{ - TLSConfig: TLSConfig{ServerName: tc.hostSrvName}, - }, + conf: ServerConfig{TLSConfig: tlsConf}, } var conn net.Conn @@ -146,10 +181,11 @@ func TestProcessClientID(t *testing.T) { assert.Equal(t, tc.wantRes, res) assert.Equal(t, tc.wantClientID, dctx.clientID) - if tc.wantErrMsg != "" && assert.NotNil(t, dctx.err) { - assert.Equal(t, tc.wantErrMsg, dctx.err.Error()) - } else { + if tc.wantErrMsg == "" { assert.Nil(t, dctx.err) + } else { + require.NotNil(t, dctx.err) + assert.Equal(t, tc.wantErrMsg, dctx.err.Error()) } }) } @@ -202,8 +238,9 @@ func TestProcessClientID_https(t *testing.T) { name: "invalid_client_id", path: "/dns-query/!!!", wantClientID: "", - wantErrMsg: `client id check: invalid client id: invalid char '!' at index 0 in client id "!!!"`, - wantRes: resultCodeError, + wantErrMsg: `client id check: invalid client id: invalid char '!'` + + ` at index 0 in client id "!!!"`, + wantRes: resultCodeError, }} for _, tc := range testCases { @@ -225,10 +262,11 @@ func TestProcessClientID_https(t *testing.T) { assert.Equal(t, tc.wantRes, res) assert.Equal(t, tc.wantClientID, dctx.clientID) - if tc.wantErrMsg != "" && assert.NotNil(t, dctx.err) { - assert.Equal(t, tc.wantErrMsg, dctx.err.Error()) - } else { + if tc.wantErrMsg == "" { assert.Nil(t, dctx.err) + } else { + require.NotNil(t, dctx.err) + assert.Equal(t, tc.wantErrMsg, dctx.err.Error()) } }) } diff --git a/internal/dnsforward/dns.go b/internal/dnsforward/dns.go index f8e7bff0..acc6aa86 100644 --- a/internal/dnsforward/dns.go +++ b/internal/dnsforward/dns.go @@ -1,10 +1,7 @@ package dnsforward import ( - "crypto/tls" - "fmt" "net" - "path" "strings" "time" @@ -13,7 +10,6 @@ import ( "github.com/AdguardTeam/AdGuardHome/internal/util" "github.com/AdguardTeam/dnsproxy/proxy" "github.com/AdguardTeam/golibs/log" - "github.com/lucas-clemente/quic-go" "github.com/miekg/dns" ) @@ -234,154 +230,6 @@ func processInternalHosts(ctx *dnsContext) (rc resultCode) { return resultCodeSuccess } -const maxDomainPartLen = 64 - -// ValidateClientID returns an error if clientID is not a valid client ID. -func ValidateClientID(clientID string) (err error) { - if len(clientID) > maxDomainPartLen { - return fmt.Errorf("client id %q is too long, max: %d", clientID, maxDomainPartLen) - } - - for i, r := range clientID { - if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == '-' { - continue - } - - return fmt.Errorf("invalid char %q at index %d in client id %q", r, i, clientID) - } - - return nil -} - -// clientIDFromClientServerName extracts and validates a client ID. hostSrvName -// is the server name of the host. cliSrvName is the server name as sent by the -// client. -func clientIDFromClientServerName(hostSrvName, cliSrvName string) (clientID string, err error) { - if hostSrvName == cliSrvName { - return "", nil - } - - if !strings.HasSuffix(cliSrvName, hostSrvName) { - return "", fmt.Errorf("client server name %q doesn't match host server name %q", cliSrvName, hostSrvName) - } - - clientID = cliSrvName[:len(cliSrvName)-len(hostSrvName)-1] - err = ValidateClientID(clientID) - if err != nil { - return "", fmt.Errorf("invalid client id: %w", err) - } - - return clientID, nil -} - -// processClientIDHTTPS extracts the client's ID from the path of the -// client's DNS-over-HTTPS request. -func processClientIDHTTPS(ctx *dnsContext) (rc resultCode) { - pctx := ctx.proxyCtx - r := pctx.HTTPRequest - if r == nil { - ctx.err = fmt.Errorf("proxy ctx http request of proto %s is nil", pctx.Proto) - - return resultCodeError - } - - origPath := r.URL.Path - parts := strings.Split(path.Clean(origPath), "/") - if parts[0] == "" { - parts = parts[1:] - } - - if len(parts) == 0 || parts[0] != "dns-query" { - ctx.err = fmt.Errorf("client id check: invalid path %q", origPath) - - return resultCodeError - } - - clientID := "" - switch len(parts) { - case 1: - // Just /dns-query, no client ID. - return resultCodeSuccess - case 2: - clientID = parts[1] - default: - ctx.err = fmt.Errorf("client id check: invalid path %q: extra parts", origPath) - - return resultCodeError - } - - err := ValidateClientID(clientID) - if err != nil { - ctx.err = fmt.Errorf("client id check: invalid client id: %w", err) - - return resultCodeError - } - - ctx.clientID = clientID - - return resultCodeSuccess -} - -// tlsConn is a narrow interface for *tls.Conn to simplify testing. -type tlsConn interface { - ConnectionState() (cs tls.ConnectionState) -} - -// quicSession is a narrow interface for quic.Session to simplify testing. -type quicSession interface { - ConnectionState() (cs quic.ConnectionState) -} - -// processClientID extracts the client's ID from the server name of the client's -// DOT or DOQ request or the path of the client's DOH. -func processClientID(ctx *dnsContext) (rc resultCode) { - pctx := ctx.proxyCtx - proto := pctx.Proto - if proto == proxy.ProtoHTTPS { - return processClientIDHTTPS(ctx) - } else if proto != proxy.ProtoTLS && proto != proxy.ProtoQUIC { - return resultCodeSuccess - } - - hostSrvName := ctx.srv.conf.TLSConfig.ServerName - if hostSrvName == "" { - return resultCodeSuccess - } - - cliSrvName := "" - if proto == proxy.ProtoTLS { - conn := pctx.Conn - tc, ok := conn.(tlsConn) - if !ok { - ctx.err = fmt.Errorf("proxy ctx conn of proto %s is %T, want *tls.Conn", proto, conn) - - return resultCodeError - } - - cliSrvName = tc.ConnectionState().ServerName - } else if proto == proxy.ProtoQUIC { - qs, ok := pctx.QUICSession.(quicSession) - if !ok { - ctx.err = fmt.Errorf("proxy ctx quic session of proto %s is %T, want quic.Session", proto, pctx.QUICSession) - - return resultCodeError - } - - cliSrvName = qs.ConnectionState().ServerName - } - - clientID, err := clientIDFromClientServerName(hostSrvName, cliSrvName) - if err != nil { - ctx.err = fmt.Errorf("client id check: %w", err) - - return resultCodeError - } - - ctx.clientID = clientID - - return resultCodeSuccess -} - // Respond to PTR requests if the target IP address is leased by our DHCP server func processInternalIPAddrs(ctx *dnsContext) (rc resultCode) { s := ctx.srv From 7e085652123eace8209bb3037874a98ef62c9331 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Thu, 11 Feb 2021 15:41:03 +0300 Subject: [PATCH 115/141] Pull request: openapi: doc client id better Merge in DNS/adguard-home from doc-client-id to master Squashed commit of the following: commit ea03887d505296e5033964e8227ed906b102d990 Merge: 693453b5 841bb9bc Author: Ainar Garipov Date: Thu Feb 11 15:20:56 2021 +0300 Merge branch 'master' into doc-client-id commit 693453b5eab40e201501d6881418ee42191a1bc5 Author: Ainar Garipov Date: Thu Feb 11 12:55:01 2021 +0300 openapi: doc client id better --- openapi/CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/openapi/CHANGELOG.md b/openapi/CHANGELOG.md index fab1099e..7cbc626b 100644 --- a/openapi/CHANGELOG.md +++ b/openapi/CHANGELOG.md @@ -4,6 +4,12 @@ ## v0.105: API changes +### New `"client_id"` field in `GET /querylog` response + +* The new field `"client_id"` of `QueryLogItem` objects is the ID sent by the + client for encrypted requests, if there was any. See the + "[Identifying clients]" section of our wiki. + ### New `"dnscrypt"` `"client_proto"` value in `GET /querylog` response * The field `"client_proto"` can now have the value `"dnscrypt"` when the @@ -69,6 +75,8 @@ As well as other documentation fixes. +[Identifying clients]: https://github.com/AdguardTeam/AdGuardHome/wiki/Clients#idclient + ## v0.103: API changes ### API: replace settings in GET /control/dns_info & POST /control/dns_config From 7dd2d0af964fe820748ff935612187b50ea6ea6f Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Thu, 11 Feb 2021 16:32:27 +0300 Subject: [PATCH 116/141] Pull request: all: doc make -j in readme Merge in DNS/adguard-home from 2668-doc-make-j to master Updates #2668. Squashed commit of the following: commit b52c2a18c46f8a6e5badf9db104c04a10765e96d Author: Ainar Garipov Date: Thu Feb 11 16:21:54 2021 +0300 all: doc make -j in readme --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 519119e9..f9ee5dae 100644 --- a/README.md +++ b/README.md @@ -155,7 +155,7 @@ It depends. "DNS sinkholing" is capable of blocking a big percentage of ads, but it lacks flexibility and power of traditional ad blockers. You can get a good impression about the difference between these methods by reading [this article](https://adguard.com/en/blog/adguard-vs-adaway-dns66/). It compares AdGuard for Android (a traditional ad blocker) to hosts-level ad blockers (which are almost identical to DNS-based blockers in their capabilities). -However, this level of protection is enough for some users. Additionally, using a DNS-based blocker can help to block ads, tracking and analytics requests on other types of devices, such as SmartTVs, smart speakers or other kinds of IoT devices (on which you can't install tradtional ad blockers). +However, this level of protection is enough for some users. Additionally, using a DNS-based blocker can help to block ads, tracking and analytics requests on other types of devices, such as SmartTVs, smart speakers or other kinds of IoT devices (on which you can't install traditional ad blockers). **Known limitations** @@ -192,6 +192,12 @@ cd AdGuardHome make ``` +Please note, that the non-standard `-j` flag is currently not supported, so +building with `make -j 4` or setting your `MAKEFLAGS` to include, for example, +`-j 4` is likely to break the build. If you do have your `MAKEFLAGS` set to +that, and you don't want to change it, you can override it by running +`make -j 1`. + Check the [`Makefile`](https://github.com/AdguardTeam/AdGuardHome/blob/master/Makefile) to learn about other commands. **Building for a different platform.** You can build AdGuard for any OS/ARCH just like any other Go project. From a623ac694ba5d7acf45464941057bc79975142d8 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Thu, 11 Feb 2021 17:55:37 +0300 Subject: [PATCH 117/141] Pull request: stats: imp err handling, logs Merge in DNS/adguard-home from 2661-imp-stats-logging to master Updates #2661. Squashed commit of the following: commit 474735a5c6ab650973343a1323ebf3c00edd71cf Author: Ainar Garipov Date: Thu Feb 11 17:44:34 2021 +0300 stats: imp err handling, logs --- internal/stats/unit.go | 66 +++++++++++++++++++++++++++++------------- 1 file changed, 46 insertions(+), 20 deletions(-) diff --git a/internal/stats/unit.go b/internal/stats/unit.go index d955d04f..96775c72 100644 --- a/internal/stats/unit.go +++ b/internal/stats/unit.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/binary" "encoding/gob" + "errors" "fmt" "net" "os" @@ -11,10 +12,14 @@ import ( "sync" "time" + "github.com/AdguardTeam/AdGuardHome/internal/agherr" "github.com/AdguardTeam/golibs/log" bolt "go.etcd.io/bbolt" ) +// TODO(a.garipov): Rewrite all of this. Add proper error handling and +// inspection. Improve logging. Decrease complexity. + const ( maxDomains = 100 // max number of top domains to store in file or return via Get() maxClients = 100 // max number of top clients to store in file or return via Get() @@ -61,11 +66,12 @@ type unitDB struct { TimeAvg uint32 // usec } -func createObject(conf Config) (*statsCtx, error) { - s := statsCtx{} +func createObject(conf Config) (s *statsCtx, err error) { + s = &statsCtx{} if !checkInterval(conf.LimitDays) { conf.LimitDays = 1 } + s.conf = &Config{} *s.conf = conf s.conf.limit = conf.LimitDays * 24 @@ -84,27 +90,43 @@ func createObject(conf Config) (*statsCtx, error) { log.Tracef("Deleting old units...") firstID := id - s.conf.limit - 1 unitDel := 0 - forEachBkt := func(name []byte, b *bolt.Bucket) error { - id := uint32(btoi(name)) - if id < firstID { - err := tx.DeleteBucket(name) - if err != nil { - log.Debug("tx.DeleteBucket: %s", err) + + // TODO(a.garipov): See if this is actually necessary. Looks + // like a rather bizarre solution. + errStop := agherr.Error("stop iteration") + forEachBkt := func(name []byte, _ *bolt.Bucket) (cberr error) { + nameID := uint32(btoi(name)) + if nameID < firstID { + cberr = tx.DeleteBucket(name) + if cberr != nil { + log.Debug("stats: tx.DeleteBucket: %s", cberr) + + return nil } - log.Debug("Stats: deleted unit %d", id) + + log.Debug("stats: deleted unit %d", nameID) unitDel++ + return nil } - return fmt.Errorf("") + + return errStop + } + + err = tx.ForEach(forEachBkt) + if err != nil && !errors.Is(err, errStop) { + log.Debug("stats: deleting units: %s", err) } - _ = tx.ForEach(forEachBkt) udb = s.loadUnitFromDB(tx, id) if unitDel != 0 { s.commitTxn(tx) } else { - _ = tx.Rollback() + err = tx.Rollback() + if err != nil { + log.Debug("rolling back: %s", err) + } } } @@ -115,8 +137,9 @@ func createObject(conf Config) (*statsCtx, error) { } s.unit = &u - log.Debug("Stats: initialized") - return &s, nil + log.Debug("stats: initialized") + + return s, nil } func (s *statsCtx) Start() { @@ -133,7 +156,7 @@ func (s *statsCtx) dbOpen() bool { log.Tracef("db.Open...") s.db, err = bolt.Open(s.conf.Filename, 0o644, nil) if err != nil { - log.Error("Stats: open DB: %s: %s", s.conf.Filename, err) + log.Error("stats: open DB: %s: %s", s.conf.Filename, err) if err.Error() == "invalid argument" { log.Error("AdGuard Home cannot be initialized due to an incompatible file system.\nPlease read the explanation here: https://github.com/AdguardTeam/AdGuardHome/internal/wiki/Getting-Started#limitations") } @@ -262,10 +285,13 @@ func (s *statsCtx) periodicFlush() { func (s *statsCtx) deleteUnit(tx *bolt.Tx, id uint32) bool { err := tx.DeleteBucket(unitName(id)) if err != nil { - log.Tracef("bolt DeleteBucket: %s", err) + log.Tracef("stats: bolt DeleteBucket: %s", err) + return false } - log.Debug("Stats: deleted unit %d", id) + + log.Debug("stats: deleted unit %d", id) + return true } @@ -390,7 +416,7 @@ func (s *statsCtx) setLimit(limitDays int) { conf := *s.conf conf.limit = uint32(limitDays) * 24 s.conf = &conf - log.Debug("Stats: set limit: %d", limitDays) + log.Debug("stats: set limit: %d", limitDays) } func (s *statsCtx) WriteDiskConfig(dc *DiskConfig) { @@ -415,7 +441,7 @@ func (s *statsCtx) Close() { log.Tracef("db.Close") } - log.Debug("Stats: closed") + log.Debug("stats: closed") } // Reset counters and clear database @@ -443,7 +469,7 @@ func (s *statsCtx) clear() { _ = s.dbOpen() - log.Debug("Stats: cleared") + log.Debug("stats: cleared") } // Get Client IP address From 6b8a46ef3bd39d32866b9938b1ba00fcf0228a8b Mon Sep 17 00:00:00 2001 From: Andrey Meshkov Date: Thu, 11 Feb 2021 18:08:44 +0300 Subject: [PATCH 118/141] Fix install script for darwin --- scripts/install.sh | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/scripts/install.sh b/scripts/install.sh index 551f84fc..7a410cee 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -228,7 +228,13 @@ main() { download "${URL}" "${PKG_NAME}" || error_exit "Cannot download the package" - unpack "${PKG_NAME}" "${OUT_DIR}" "${PKG_EXT}" || error_exit "Cannot unpack the package" + if [ "${OS}" = "darwin" ]; then + # TODO: remove this after v0.106.0 release + mkdir "${AGH_DIR}" + unpack "${PKG_NAME}" "${AGH_DIR}" "${PKG_EXT}" || error_exit "Cannot unpack the package" + else + unpack "${PKG_NAME}" "${OUT_DIR}" "${PKG_EXT}" || error_exit "Cannot unpack the package" + fi # Install AdGuard Home service and run it. ( cd "${AGH_DIR}" && ./AdGuardHome -s install || error_exit "Cannot install AdGuardHome as a service" ) From 7b014082ab5772a588f50708d758500db10fbbb3 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Thu, 11 Feb 2021 18:40:14 +0300 Subject: [PATCH 119/141] Pull request: home: set vary hdr to origin Merge in DNS/adguard-home from 2658-vary-origin to master Updates #2658. Squashed commit of the following: commit b4bf6c16e19f1c0b04cc2e526e2b0968956cf56c Author: Ainar Garipov Date: Thu Feb 11 18:18:32 2021 +0300 all: doc changes commit f2599c5b48759565e2f621c2fcf89440de56e4a4 Merge: 3eb08ac8 6b8a46ef Author: Ainar Garipov Date: Thu Feb 11 18:16:53 2021 +0300 Merge branch 'master' into 2658-vary-origin commit 3eb08ac889163d123b5ca638a83a9289b456d04e Author: Ainar Garipov Date: Thu Feb 11 18:04:04 2021 +0300 home: set vary hdr to origin --- CHANGELOG.md | 4 ++++ internal/home/control.go | 5 ++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bfae45f1..f1e8d9c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,10 +26,14 @@ and this project adheres to ### Fixed +- The `Vary` header is now added along with `Access-Control-Allow-Origin` to + prevent cache-related and other issues in browsers ([#2658]). + domain, but with an HTTP scheme as opposed to `*` ([#2484]). - The request body size limit is now set for HTTPS requests as well. - Incorrect version tag in the Docker release ([#2663]). - DNSCrypt queries weren't marked as such in logs ([#2662]). +[#2658]: https://github.com/AdguardTeam/AdGuardHome/issues/2658 [#2662]: https://github.com/AdguardTeam/AdGuardHome/issues/2662 [#2663]: https://github.com/AdguardTeam/AdGuardHome/issues/2663 [#2664]: https://github.com/AdguardTeam/AdGuardHome/issues/2664 diff --git a/internal/home/control.go b/internal/home/control.go index 71bf52e5..19876b12 100644 --- a/internal/home/control.go +++ b/internal/home/control.go @@ -251,12 +251,15 @@ func handleHTTPSRedirect(w http.ResponseWriter, r *http.Request) (ok bool) { // Allow the frontend from the HTTP origin to send requests to the HTTPS // server. This can happen when the user has just set up HTTPS with - // redirects. + // redirects. Prevent cache-related errors by setting the Vary header. + // + // See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin. originURL := &url.URL{ Scheme: "http", Host: r.Host, } w.Header().Set("Access-Control-Allow-Origin", originURL.String()) + w.Header().Set("Vary", "Origin") return true } From 2eb21ef40901e893069e9e4b6fa902d759d510b5 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Thu, 11 Feb 2021 20:49:03 +0300 Subject: [PATCH 120/141] Pull request: dhcpd: do not override ra-slaac settings Merge in DNS/adguard-home from 2653-ra-slaac to master Updates #2653. Squashed commit of the following: commit f261413a58dc813e37cc848606ed490b8c0ac9f3 Author: Ainar Garipov Date: Thu Feb 11 20:37:13 2021 +0300 all: doc changes, rm debug commit 4a8c6e4897579493c1ca242fb8f0f440c3b51a74 Author: Ainar Garipov Date: Thu Feb 11 20:11:46 2021 +0300 dhcpd: do not override ra-slaac settings --- CHANGELOG.md | 4 +- internal/dhcpd/dhcpd.go | 19 +++--- internal/dhcpd/dhcphttp.go | 106 +++++++++++++++++++------------- internal/dhcpd/nullbool.go | 58 +++++++++++++++++ internal/dhcpd/nullbool_test.go | 69 +++++++++++++++++++++ internal/dhcpd/routeradv.go | 10 +-- internal/dhcpd/server.go | 4 +- internal/dhcpd/v6.go | 6 +- internal/sysutil/net_linux.go | 12 ++-- staticcheck.conf | 2 + 10 files changed, 223 insertions(+), 67 deletions(-) create mode 100644 internal/dhcpd/nullbool.go create mode 100644 internal/dhcpd/nullbool_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index f1e8d9c8..097f5c46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,13 +26,15 @@ and this project adheres to ### Fixed +- DHCPv6 server's `ra_slaac_only` and `ra_allow_slaac` settings aren't reset to + `false` on update any more ([#2653]). - The `Vary` header is now added along with `Access-Control-Allow-Origin` to prevent cache-related and other issues in browsers ([#2658]). - domain, but with an HTTP scheme as opposed to `*` ([#2484]). - The request body size limit is now set for HTTPS requests as well. - Incorrect version tag in the Docker release ([#2663]). - DNSCrypt queries weren't marked as such in logs ([#2662]). +[#2653]: https://github.com/AdguardTeam/AdGuardHome/issues/2653 [#2658]: https://github.com/AdguardTeam/AdGuardHome/issues/2658 [#2662]: https://github.com/AdguardTeam/AdGuardHome/issues/2662 [#2663]: https://github.com/AdguardTeam/AdGuardHome/issues/2663 diff --git a/internal/dhcpd/dhcpd.go b/internal/dhcpd/dhcpd.go index 18509991..bcd35d1c 100644 --- a/internal/dhcpd/dhcpd.go +++ b/internal/dhcpd/dhcpd.go @@ -117,14 +117,14 @@ type ServerInterface interface { } // Create - create object -func Create(config ServerConfig) *Server { +func Create(conf ServerConfig) *Server { s := &Server{} - s.conf.Enabled = config.Enabled - s.conf.InterfaceName = config.InterfaceName - s.conf.HTTPRegister = config.HTTPRegister - s.conf.ConfigModified = config.ConfigModified - s.conf.DBFilePath = filepath.Join(config.WorkDir, dbFilename) + s.conf.Enabled = conf.Enabled + s.conf.InterfaceName = conf.InterfaceName + s.conf.HTTPRegister = conf.HTTPRegister + s.conf.ConfigModified = conf.ConfigModified + s.conf.DBFilePath = filepath.Join(conf.WorkDir, dbFilename) if !webHandlersRegistered && s.conf.HTTPRegister != nil { if runtime.GOOS == "windows" { @@ -145,7 +145,7 @@ func Create(config ServerConfig) *Server { } var err4, err6 error - v4conf := config.Conf4 + v4conf := conf.Conf4 v4conf.Enabled = s.conf.Enabled if len(v4conf.RangeStart) == 0 { v4conf.Enabled = false @@ -154,7 +154,7 @@ func Create(config ServerConfig) *Server { v4conf.notify = s.onNotify s.srv4, err4 = v4Create(v4conf) - v6conf := config.Conf6 + v6conf := conf.Conf6 v6conf.Enabled = s.conf.Enabled if len(v6conf.RangeStart) == 0 { v6conf.Enabled = false @@ -172,6 +172,9 @@ func Create(config ServerConfig) *Server { return nil } + s.conf.Conf4 = conf.Conf4 + s.conf.Conf6 = conf.Conf6 + if s.conf.Enabled && !v4conf.Enabled && !v6conf.Enabled { log.Error("Can't enable DHCP server because neither DHCPv4 nor DHCPv6 servers are configured") return nil diff --git a/internal/dhcpd/dhcphttp.go b/internal/dhcpd/dhcphttp.go index b6b5c729..0a182cf0 100644 --- a/internal/dhcpd/dhcphttp.go +++ b/internal/dhcpd/dhcphttp.go @@ -11,7 +11,6 @@ import ( "github.com/AdguardTeam/AdGuardHome/internal/sysutil" "github.com/AdguardTeam/AdGuardHome/internal/util" - "github.com/AdguardTeam/golibs/jsonutil" "github.com/AdguardTeam/golibs/log" ) @@ -29,7 +28,11 @@ type v4ServerConfJSON struct { LeaseDuration uint32 `json:"lease_duration"` } -func v4JSONToServerConf(j v4ServerConfJSON) V4ServerConf { +func v4JSONToServerConf(j *v4ServerConfJSON) V4ServerConf { + if j == nil { + return V4ServerConf{} + } + return V4ServerConf{ GatewayIP: j.GatewayIP, SubnetMask: j.SubnetMask, @@ -44,7 +47,11 @@ type v6ServerConfJSON struct { LeaseDuration uint32 `json:"lease_duration"` } -func v6JSONToServerConf(j v6ServerConfJSON) V6ServerConf { +func v6JSONToServerConf(j *v6ServerConfJSON) V6ServerConf { + if j == nil { + return V6ServerConf{} + } + return V6ServerConf{ RangeStart: j.RangeStart, LeaseDuration: j.LeaseDuration, @@ -83,13 +90,6 @@ func (s *Server) handleDHCPStatus(w http.ResponseWriter, r *http.Request) { } } -type dhcpServerConfigJSON struct { - Enabled bool `json:"enabled"` - InterfaceName string `json:"interface_name"` - V4 v4ServerConfJSON `json:"v4"` - V6 v6ServerConfJSON `json:"v6"` -} - func (s *Server) enableDHCP(ifaceName string) (code int, err error) { var hasStaticIP bool hasStaticIP, err = sysutil.IfaceHasStaticIP(ifaceName) @@ -112,14 +112,22 @@ func (s *Server) enableDHCP(ifaceName string) (code int, err error) { return 0, nil } -func (s *Server) handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) { - newconfig := dhcpServerConfigJSON{} - newconfig.Enabled = s.conf.Enabled - newconfig.InterfaceName = s.conf.InterfaceName +type dhcpServerConfigJSON struct { + V4 *v4ServerConfJSON `json:"v4"` + V6 *v6ServerConfJSON `json:"v6"` + InterfaceName string `json:"interface_name"` + Enabled nullBool `json:"enabled"` +} - js, err := jsonutil.DecodeObject(&newconfig, r.Body) +func (s *Server) handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) { + conf := dhcpServerConfigJSON{} + conf.Enabled = boolToNullBool(s.conf.Enabled) + conf.InterfaceName = s.conf.InterfaceName + + err := json.NewDecoder(r.Body).Decode(&conf) if err != nil { - httpError(r, w, http.StatusBadRequest, "Failed to parse new DHCP config json: %s", err) + httpError(r, w, http.StatusBadRequest, + "failed to parse new dhcp config json: %s", err) return } @@ -129,62 +137,72 @@ func (s *Server) handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) { v4Enabled := false v6Enabled := false - if js.Exists("v4") { - v4conf := v4JSONToServerConf(newconfig.V4) - v4conf.Enabled = newconfig.Enabled - if len(v4conf.RangeStart) == 0 { - v4conf.Enabled = false + if conf.V4 != nil { + v4Conf := v4JSONToServerConf(conf.V4) + v4Conf.Enabled = conf.Enabled == nbTrue + if len(v4Conf.RangeStart) == 0 { + v4Conf.Enabled = false } - v4Enabled = v4conf.Enabled - v4conf.InterfaceName = newconfig.InterfaceName + v4Enabled = v4Conf.Enabled + v4Conf.InterfaceName = conf.InterfaceName c4 := V4ServerConf{} s.srv4.WriteDiskConfig4(&c4) - v4conf.notify = c4.notify - v4conf.ICMPTimeout = c4.ICMPTimeout + v4Conf.notify = c4.notify + v4Conf.ICMPTimeout = c4.ICMPTimeout - s4, err = v4Create(v4conf) + s4, err = v4Create(v4Conf) if err != nil { - httpError(r, w, http.StatusBadRequest, "invalid dhcpv4 configuration: %s", err) + httpError(r, w, http.StatusBadRequest, + "invalid dhcpv4 configuration: %s", err) return } } - if js.Exists("v6") { - v6conf := v6JSONToServerConf(newconfig.V6) - v6conf.Enabled = newconfig.Enabled - if len(v6conf.RangeStart) == 0 { - v6conf.Enabled = false + if conf.V6 != nil { + v6Conf := v6JSONToServerConf(conf.V6) + v6Conf.Enabled = conf.Enabled == nbTrue + if len(v6Conf.RangeStart) == 0 { + v6Conf.Enabled = false } - v6Enabled = v6conf.Enabled - v6conf.InterfaceName = newconfig.InterfaceName - v6conf.notify = s.onNotify + // Don't overwrite the RA/SLAAC settings from the config file. + // + // TODO(a.garipov): Perhaps include them into the request to + // allow changing them from the HTTP API? + v6Conf.RASLAACOnly = s.conf.Conf6.RASLAACOnly + v6Conf.RAAllowSLAAC = s.conf.Conf6.RAAllowSLAAC - s6, err = v6Create(v6conf) + v6Enabled = v6Conf.Enabled + v6Conf.InterfaceName = conf.InterfaceName + v6Conf.notify = s.onNotify + + s6, err = v6Create(v6Conf) if err != nil { - httpError(r, w, http.StatusBadRequest, "invalid dhcpv6 configuration: %s", err) + httpError(r, w, http.StatusBadRequest, + "invalid dhcpv6 configuration: %s", err) return } } - if newconfig.Enabled && !v4Enabled && !v6Enabled { - httpError(r, w, http.StatusBadRequest, "dhcpv4 or dhcpv6 configuration must be complete") + if conf.Enabled == nbTrue && !v4Enabled && !v6Enabled { + httpError(r, w, http.StatusBadRequest, + "dhcpv4 or dhcpv6 configuration must be complete") return } s.Stop() - if js.Exists("enabled") { - s.conf.Enabled = newconfig.Enabled + if conf.Enabled != nbNull { + s.conf.Enabled = conf.Enabled == nbTrue } - if js.Exists("interface_name") { - s.conf.InterfaceName = newconfig.InterfaceName + if conf.InterfaceName != "" { + s.conf.InterfaceName = conf.InterfaceName } if s4 != nil { @@ -200,7 +218,7 @@ func (s *Server) handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) { if s.conf.Enabled { var code int - code, err = s.enableDHCP(newconfig.InterfaceName) + code, err = s.enableDHCP(conf.InterfaceName) if err != nil { httpError(r, w, code, "enabling dhcp: %s", err) diff --git a/internal/dhcpd/nullbool.go b/internal/dhcpd/nullbool.go new file mode 100644 index 00000000..b07f6768 --- /dev/null +++ b/internal/dhcpd/nullbool.go @@ -0,0 +1,58 @@ +package dhcpd + +import ( + "bytes" + "fmt" +) + +// nullBool is a nullable boolean. Use these in JSON requests and responses +// instead of pointers to bool. +// +// TODO(a.garipov): Inspect uses of *bool, move this type into some new package +// if we need it somewhere else. +type nullBool uint8 + +// nullBool values +const ( + nbNull nullBool = iota + nbTrue + nbFalse +) + +// String implements the fmt.Stringer interface for nullBool. +func (nb nullBool) String() (s string) { + switch nb { + case nbNull: + return "null" + case nbTrue: + return "true" + case nbFalse: + return "false" + } + + return fmt.Sprintf("!invalid nullBool %d", uint8(nb)) +} + +// boolToNullBool converts a bool into a nullBool. +func boolToNullBool(cond bool) (nb nullBool) { + if cond { + return nbTrue + } + + return nbFalse +} + +// UnmarshalJSON implements the json.Unmarshaler interface for *nullBool. +func (nb *nullBool) UnmarshalJSON(b []byte) (err error) { + if len(b) == 0 || bytes.Equal(b, []byte("null")) { + *nb = nbNull + } else if bytes.Equal(b, []byte("true")) { + *nb = nbTrue + } else if bytes.Equal(b, []byte("false")) { + *nb = nbFalse + } else { + return fmt.Errorf("invalid nullBool value %q", b) + } + + return nil +} diff --git a/internal/dhcpd/nullbool_test.go b/internal/dhcpd/nullbool_test.go new file mode 100644 index 00000000..2570dd44 --- /dev/null +++ b/internal/dhcpd/nullbool_test.go @@ -0,0 +1,69 @@ +package dhcpd + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNullBool_UnmarshalText(t *testing.T) { + testCases := []struct { + name string + data []byte + wantErrMsg string + want nullBool + }{{ + name: "empty", + data: []byte{}, + wantErrMsg: "", + want: nbNull, + }, { + name: "null", + data: []byte("null"), + wantErrMsg: "", + want: nbNull, + }, { + name: "true", + data: []byte("true"), + wantErrMsg: "", + want: nbTrue, + }, { + name: "false", + data: []byte("false"), + wantErrMsg: "", + want: nbFalse, + }, { + name: "invalid", + data: []byte("flase"), + wantErrMsg: `invalid nullBool value "flase"`, + want: nbNull, + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + var got nullBool + err := got.UnmarshalJSON(tc.data) + if tc.wantErrMsg == "" { + assert.Nil(t, err) + } else { + require.NotNil(t, err) + assert.Equal(t, tc.wantErrMsg, err.Error()) + } + + assert.Equal(t, tc.want, got) + }) + } + + t.Run("json", func(t *testing.T) { + want := nbTrue + var got struct { + A nullBool + } + + err := json.Unmarshal([]byte(`{"A":true}`), &got) + require.Nil(t, err) + assert.Equal(t, want, got.A) + }) +} diff --git a/internal/dhcpd/routeradv.go b/internal/dhcpd/routeradv.go index f1d63c7d..59aad9d1 100644 --- a/internal/dhcpd/routeradv.go +++ b/internal/dhcpd/routeradv.go @@ -13,8 +13,8 @@ import ( ) type raCtx struct { - raAllowSlaac bool // send RA packets without MO flags - raSlaacOnly bool // send RA packets with MO flags + raAllowSLAAC bool // send RA packets without MO flags + raSLAACOnly bool // send RA packets with MO flags ipAddr net.IP // source IP address (link-local-unicast) dnsIPAddr net.IP // IP address for DNS Server option prefixIPAddr net.IP // IP address for Prefix option @@ -159,7 +159,7 @@ func createICMPv6RAPacket(params icmpv6RA) []byte { func (ra *raCtx) Init() error { ra.stop.Store(0) ra.conn = nil - if !(ra.raAllowSlaac || ra.raSlaacOnly) { + if !(ra.raAllowSLAAC || ra.raSLAACOnly) { return nil } @@ -167,8 +167,8 @@ func (ra *raCtx) Init() error { ra.ipAddr, ra.dnsIPAddr) params := icmpv6RA{ - managedAddressConfiguration: !ra.raSlaacOnly, - otherConfiguration: !ra.raSlaacOnly, + managedAddressConfiguration: !ra.raSLAACOnly, + otherConfiguration: !ra.raSLAACOnly, mtu: uint32(ra.iface.MTU), prefixLen: 64, recursiveDNSServer: ra.dnsIPAddr, diff --git a/internal/dhcpd/server.go b/internal/dhcpd/server.go index 4adbca5a..1d3ce460 100644 --- a/internal/dhcpd/server.go +++ b/internal/dhcpd/server.go @@ -83,8 +83,8 @@ type V6ServerConf struct { LeaseDuration uint32 `yaml:"lease_duration" json:"lease_duration"` // in seconds - RaSlaacOnly bool `yaml:"ra_slaac_only" json:"-"` // send ICMPv6.RA packets without MO flags - RaAllowSlaac bool `yaml:"ra_allow_slaac" json:"-"` // send ICMPv6.RA packets with MO flags + RASLAACOnly bool `yaml:"ra_slaac_only" json:"-"` // send ICMPv6.RA packets without MO flags + RAAllowSLAAC bool `yaml:"ra_allow_slaac" json:"-"` // send ICMPv6.RA packets with MO flags ipStart net.IP // starting IP address for dynamic leases leaseTime time.Duration // the time during which a dynamic lease is considered valid diff --git a/internal/dhcpd/v6.go b/internal/dhcpd/v6.go index 51788551..0b43d6d1 100644 --- a/internal/dhcpd/v6.go +++ b/internal/dhcpd/v6.go @@ -552,8 +552,8 @@ func (s *v6Server) initRA(iface *net.Interface) error { } } - s.ra.raAllowSlaac = s.conf.RaAllowSlaac - s.ra.raSlaacOnly = s.conf.RaSlaacOnly + s.ra.raAllowSLAAC = s.conf.RAAllowSLAAC + s.ra.raSLAACOnly = s.conf.RASLAACOnly s.ra.dnsIPAddr = s.ra.ipAddr s.ra.prefixIPAddr = s.conf.ipStart s.ra.ifaceName = s.conf.InterfaceName @@ -594,7 +594,7 @@ func (s *v6Server) Start() error { } // don't initialize DHCPv6 server if we must force the clients to use SLAAC - if s.conf.RaSlaacOnly { + if s.conf.RASLAACOnly { log.Debug("DHCPv6: not starting DHCPv6 server due to ra_slaac_only=true") return nil } diff --git a/internal/sysutil/net_linux.go b/internal/sysutil/net_linux.go index 8f47cf42..9205fd5c 100644 --- a/internal/sysutil/net_linux.go +++ b/internal/sysutil/net_linux.go @@ -33,15 +33,19 @@ func ifaceHasStaticIP(ifaceName string) (has bool, err error) { filePath: "/etc/network/interfaces", }} { f, err = os.Open(check.filePath) - if errors.Is(err, os.ErrNotExist) { - continue - } if err != nil { + if errors.Is(err, os.ErrNotExist) { + err = nil + + continue + } + return false, err } defer f.Close() - fileReadCloser, err := aghio.LimitReadCloser(f, maxConfigFileSize) + var fileReadCloser io.ReadCloser + fileReadCloser, err = aghio.LimitReadCloser(f, maxConfigFileSize) if err != nil { return false, err } diff --git a/staticcheck.conf b/staticcheck.conf index 146f83cb..4dd93176 100644 --- a/staticcheck.conf +++ b/staticcheck.conf @@ -10,7 +10,9 @@ initialisms = [ , "MX" , "PTR" , "QUIC" +, "RA" , "SDNS" +, "SLAAC" , "SVCB" ] dot_import_whitelist = [] From e83b919dbda3ac7b1835e9b312962ae30dd13e04 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Fri, 12 Feb 2021 12:59:08 +0300 Subject: [PATCH 121/141] Pull request: all: imp issue template Merge in DNS/adguard-home from imp-issue-tmpl to master Squashed commit of the following: commit e6251d4e6db9498d26e555116dbb870625219ffa Author: Ainar Garipov Date: Fri Feb 12 12:46:09 2021 +0300 all: imp issue template more commit f896c6076027c373bc8b62c600a82938ae3a813d Author: Ainar Garipov Date: Fri Feb 12 12:42:25 2021 +0300 all: imp issue template --- .github/ISSUE_TEMPLATE/Bug_report.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/Bug_report.md b/.github/ISSUE_TEMPLATE/Bug_report.md index 5603c7d7..86615ef8 100644 --- a/.github/ISSUE_TEMPLATE/Bug_report.md +++ b/.github/ISSUE_TEMPLATE/Bug_report.md @@ -1,11 +1,9 @@ --- name: Bug report about: Create a bug report to help us improve AdGuard Home - --- - + ### Prerequisites @@ -17,16 +15,18 @@ Please answer the following questions for yourself before submitting an issue. * ### Issue Details - + * **Version of AdGuard Home server:** - * + * * **How did you install AdGuard Home:** - * + * * **How did you setup DNS configuration:** * * **If it's a router or IoT, please write device model:** * +* **CPU architecture:** + * * **Operating system and version:** * From 0d44822c43e1a2fd327689796135acdabfe49ac3 Mon Sep 17 00:00:00 2001 From: Eugene Burkov Date: Fri, 12 Feb 2021 13:27:44 +0300 Subject: [PATCH 122/141] Pull request: 2639 use testify require vol.3 Merge in DNS/adguard-home from 2639-testify-require-3 to master Updates #2639. Squashed commit of the following: commit 83d7afcbb7e5393db5a0242f3eaca063710d36b7 Merge: ef154b6d e83b919d Author: Eugene Burkov Date: Fri Feb 12 13:07:58 2021 +0300 Merge branch 'master' into 2639-testify-require-3 commit ef154b6d3c89f975ce28369372757a1205baa655 Merge: 5b46073a 2eb21ef4 Author: Eugene Burkov Date: Fri Feb 12 12:40:43 2021 +0300 Merge branch 'master' into 2639-testify-require-3 commit 5b46073a09badef44c86a5f48c6bb874c8df2674 Merge: 7dd7b6e0 890f0322 Author: Eugene Burkov Date: Wed Feb 10 21:20:51 2021 +0300 Merge branch 'master' into 2639-testify-require-3 commit 7dd7b6e00ead2bf507af541c801a9ac770106440 Author: Eugene Burkov Date: Wed Feb 10 21:19:36 2021 +0300 dhcpd: fix comment commit 9e74adbcf21dad58409c3dfc8e08b6470bfedc22 Author: Eugene Burkov Date: Wed Feb 10 15:13:40 2021 +0300 all: imp tests drastically --- internal/aghio/limitedreadcloser_test.go | 7 +- internal/dhcpd/dhcpd_test.go | 189 +++++++----- internal/dhcpd/dhcphttp_test.go | 3 +- internal/dhcpd/routeradv_test.go | 8 +- internal/dhcpd/v4.go | 3 +- internal/dhcpd/v46_test.go | 21 +- internal/dhcpd/v4_test.go | 341 ++++++++++++--------- internal/dhcpd/v6_test.go | 371 +++++++++++++---------- internal/home/ipdetector_test.go | 9 +- internal/home/middlewares_test.go | 3 +- 10 files changed, 551 insertions(+), 404 deletions(-) diff --git a/internal/aghio/limitedreadcloser_test.go b/internal/aghio/limitedreadcloser_test.go index 1f10e32b..9cccda17 100644 --- a/internal/aghio/limitedreadcloser_test.go +++ b/internal/aghio/limitedreadcloser_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestLimitReadCloser(t *testing.T) { @@ -78,11 +79,11 @@ func TestLimitedReadCloser_Read(t *testing.T) { buf := make([]byte, tc.limit+1) lreader, err := LimitReadCloser(readCloser, tc.limit) - assert.Nil(t, err) + require.Nil(t, err) n, err := lreader.Read(buf) - assert.Equal(t, n, tc.want) - assert.Equal(t, tc.err, err) + require.Equal(t, tc.err, err) + assert.Equal(t, tc.want, n) }) } } diff --git a/internal/dhcpd/dhcpd_test.go b/internal/dhcpd/dhcpd_test.go index 1aa1b9a6..e9b44518 100644 --- a/internal/dhcpd/dhcpd_test.go +++ b/internal/dhcpd/dhcpd_test.go @@ -11,6 +11,7 @@ import ( "github.com/AdguardTeam/AdGuardHome/internal/aghtest" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestMain(m *testing.M) { @@ -20,116 +21,156 @@ func TestMain(m *testing.M) { func testNotify(flags uint32) { } -// Leases database store/load +// Leases database store/load. func TestDB(t *testing.T) { var err error - s := Server{} - s.conf.DBFilePath = dbFilename + s := Server{ + conf: ServerConfig{ + DBFilePath: dbFilename, + }, + } - conf := V4ServerConf{ + s.srv4, err = v4Create(V4ServerConf{ Enabled: true, RangeStart: net.IP{192, 168, 10, 100}, RangeEnd: net.IP{192, 168, 10, 200}, GatewayIP: net.IP{192, 168, 10, 1}, SubnetMask: net.IP{255, 255, 255, 0}, notify: testNotify, - } - s.srv4, err = v4Create(conf) - assert.Nil(t, err) + }) + require.Nil(t, err) s.srv6, err = v6Create(V6ServerConf{}) - assert.Nil(t, err) + require.Nil(t, err) - l := Lease{} - l.IP = net.IP{192, 168, 10, 100} - l.HWAddr, _ = net.ParseMAC("aa:aa:aa:aa:aa:aa") - exp1 := time.Now().Add(time.Hour) - l.Expiry = exp1 + leases := []Lease{{ + IP: net.IP{192, 168, 10, 100}, + HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, + Expiry: time.Now().Add(time.Hour), + }, { + IP: net.IP{192, 168, 10, 101}, + HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xBB}, + }} srv4, ok := s.srv4.(*v4Server) - assert.True(t, ok) + require.True(t, ok) - srv4.addLease(&l) + srv4.addLease(&leases[0]) + require.Nil(t, s.srv4.AddStaticLease(leases[1])) - l2 := Lease{} - l2.IP = net.IP{192, 168, 10, 101} - l2.HWAddr, _ = net.ParseMAC("aa:aa:aa:aa:aa:bb") - err = s.srv4.AddStaticLease(l2) - assert.Nil(t, err) - - _ = os.Remove("leases.db") s.dbStore() + t.Cleanup(func() { + assert.Nil(t, os.Remove(dbFilename)) + }) s.srv4.ResetLeases(nil) - s.dbLoad() ll := s.srv4.GetLeases(LeasesAll) + require.Len(t, ll, len(leases)) - assert.Equal(t, "aa:aa:aa:aa:aa:bb", ll[0].HWAddr.String()) - assert.True(t, net.IP{192, 168, 10, 101}.Equal(ll[0].IP)) + assert.Equal(t, leases[1].HWAddr, ll[0].HWAddr) + assert.Equal(t, leases[1].IP, ll[0].IP) assert.EqualValues(t, leaseExpireStatic, ll[0].Expiry.Unix()) - assert.Equal(t, "aa:aa:aa:aa:aa:aa", ll[1].HWAddr.String()) - assert.True(t, net.IP{192, 168, 10, 100}.Equal(ll[1].IP)) - assert.Equal(t, exp1.Unix(), ll[1].Expiry.Unix()) - - _ = os.Remove("leases.db") + assert.Equal(t, leases[0].HWAddr, ll[1].HWAddr) + assert.Equal(t, leases[0].IP, ll[1].IP) + assert.Equal(t, leases[0].Expiry.Unix(), ll[1].Expiry.Unix()) } func TestIsValidSubnetMask(t *testing.T) { - assert.True(t, isValidSubnetMask([]byte{255, 255, 255, 0})) - assert.True(t, isValidSubnetMask([]byte{255, 255, 254, 0})) - assert.True(t, isValidSubnetMask([]byte{255, 255, 252, 0})) - assert.False(t, isValidSubnetMask([]byte{255, 255, 253, 0})) - assert.False(t, isValidSubnetMask([]byte{255, 255, 255, 1})) + testCases := []struct { + mask net.IP + want bool + }{{ + mask: net.IP{255, 255, 255, 0}, + want: true, + }, { + mask: net.IP{255, 255, 254, 0}, + want: true, + }, { + mask: net.IP{255, 255, 252, 0}, + want: true, + }, { + mask: net.IP{255, 255, 253, 0}, + }, { + mask: net.IP{255, 255, 255, 1}, + }} + + for _, tc := range testCases { + t.Run(tc.mask.String(), func(t *testing.T) { + assert.Equal(t, tc.want, isValidSubnetMask(tc.mask)) + }) + } } func TestNormalizeLeases(t *testing.T) { - dynLeases := []*Lease{} - staticLeases := []*Lease{} + dynLeases := []*Lease{{ + HWAddr: net.HardwareAddr{1, 2, 3, 4}, + }, { + HWAddr: net.HardwareAddr{1, 2, 3, 5}, + }} - lease := &Lease{} - lease.HWAddr = []byte{1, 2, 3, 4} - dynLeases = append(dynLeases, lease) - lease = new(Lease) - lease.HWAddr = []byte{1, 2, 3, 5} - dynLeases = append(dynLeases, lease) - - lease = new(Lease) - lease.HWAddr = []byte{1, 2, 3, 4} - lease.IP = []byte{0, 2, 3, 4} - staticLeases = append(staticLeases, lease) - lease = new(Lease) - lease.HWAddr = []byte{2, 2, 3, 4} - staticLeases = append(staticLeases, lease) + staticLeases := []*Lease{{ + HWAddr: net.HardwareAddr{1, 2, 3, 4}, + IP: net.IP{0, 2, 3, 4}, + }, { + HWAddr: net.HardwareAddr{2, 2, 3, 4}, + }} leases := normalizeLeases(staticLeases, dynLeases) + require.Len(t, leases, 3) - assert.Len(t, leases, 3) - assert.True(t, bytes.Equal(leases[0].HWAddr, []byte{1, 2, 3, 4})) - assert.True(t, bytes.Equal(leases[0].IP, []byte{0, 2, 3, 4})) - assert.True(t, bytes.Equal(leases[1].HWAddr, []byte{2, 2, 3, 4})) - assert.True(t, bytes.Equal(leases[2].HWAddr, []byte{1, 2, 3, 5})) + assert.Equal(t, leases[0].HWAddr, dynLeases[0].HWAddr) + assert.Equal(t, leases[0].IP, staticLeases[0].IP) + assert.Equal(t, leases[1].HWAddr, staticLeases[1].HWAddr) + assert.Equal(t, leases[2].HWAddr, dynLeases[1].HWAddr) } func TestOptions(t *testing.T) { - code, val := parseOptionString(" 12 hex abcdef ") - assert.EqualValues(t, 12, code) - assert.True(t, bytes.Equal([]byte{0xab, 0xcd, 0xef}, val)) + testCases := []struct { + name string + optStr string + wantCode uint8 + wantVal []byte + }{{ + name: "all_right_hex", + optStr: " 12 hex abcdef ", + wantCode: 12, + wantVal: []byte{0xab, 0xcd, 0xef}, + }, { + name: "bad_hex", + optStr: " 12 hex abcdef1 ", + wantCode: 0, + }, { + name: "all_right_ip", + optStr: "123 ip 1.2.3.4", + wantCode: 123, + wantVal: net.IPv4(1, 2, 3, 4), + }, { + name: "bad_code", + optStr: "256 ip 1.1.1.1", + wantCode: 0, + }, { + name: "negative_code", + optStr: "-1 ip 1.1.1.1", + wantCode: 0, + }, { + name: "bad_ip", + optStr: "12 ip 1.1.1.1x", + wantCode: 0, + }, { + name: "bad_mode", + optStr: "12 x 1.1.1.1", + wantCode: 0, + }} - code, _ = parseOptionString(" 12 hex abcdef1 ") - assert.EqualValues(t, 0, code) - - code, val = parseOptionString("123 ip 1.2.3.4") - assert.EqualValues(t, 123, code) - assert.True(t, net.IP{1, 2, 3, 4}.Equal(net.IP(val))) - - code, _ = parseOptionString("256 ip 1.1.1.1") - assert.EqualValues(t, 0, code) - code, _ = parseOptionString("-1 ip 1.1.1.1") - assert.EqualValues(t, 0, code) - code, _ = parseOptionString("12 ip 1.1.1.1x") - assert.EqualValues(t, 0, code) - code, _ = parseOptionString("12 x 1.1.1.1") - assert.EqualValues(t, 0, code) + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + code, val := parseOptionString(tc.optStr) + require.EqualValues(t, tc.wantCode, code) + if tc.wantVal != nil { + assert.True(t, bytes.Equal(tc.wantVal, val)) + } + }) + } } diff --git a/internal/dhcpd/dhcphttp_test.go b/internal/dhcpd/dhcphttp_test.go index 47b926dc..36a89a6e 100644 --- a/internal/dhcpd/dhcphttp_test.go +++ b/internal/dhcpd/dhcphttp_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestServer_notImplemented(t *testing.T) { @@ -14,7 +15,7 @@ func TestServer_notImplemented(t *testing.T) { w := httptest.NewRecorder() r, err := http.NewRequest(http.MethodGet, "/unsupported", nil) - assert.Nil(t, err) + require.Nil(t, err) h(w, r) assert.Equal(t, http.StatusNotImplemented, w.Code) diff --git a/internal/dhcpd/routeradv_test.go b/internal/dhcpd/routeradv_test.go index 95f3d4fa..4a0f4c5b 100644 --- a/internal/dhcpd/routeradv_test.go +++ b/internal/dhcpd/routeradv_test.go @@ -1,7 +1,6 @@ package dhcpd import ( - "bytes" "net" "testing" @@ -9,7 +8,7 @@ import ( ) func TestRA(t *testing.T) { - ra := icmpv6RA{ + data := createICMPv6RAPacket(icmpv6RA{ managedAddressConfiguration: false, otherConfiguration: true, mtu: 1500, @@ -17,8 +16,7 @@ func TestRA(t *testing.T) { prefixLen: 64, recursiveDNSServer: net.ParseIP("fe80::800:27ff:fe00:0"), sourceLinkLayerAddress: []byte{0x0a, 0x00, 0x27, 0x00, 0x00, 0x00}, - } - data := createICMPv6RAPacket(ra) + }) dataCorrect := []byte{ 0x86, 0x00, 0x00, 0x00, 0x40, 0x40, 0x07, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x04, 0x40, 0xc0, 0x00, 0x00, 0x0e, 0x10, 0x00, 0x00, 0x0e, 0x10, 0x00, 0x00, 0x00, 0x00, @@ -27,5 +25,5 @@ func TestRA(t *testing.T) { 0x19, 0x03, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x10, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x27, 0xff, 0xfe, 0x00, 0x00, 0x00, } - assert.True(t, bytes.Equal(data, dataCorrect)) + assert.Equal(t, dataCorrect, data) } diff --git a/internal/dhcpd/v4.go b/internal/dhcpd/v4.go index 2f5484a2..7d24699e 100644 --- a/internal/dhcpd/v4.go +++ b/internal/dhcpd/v4.go @@ -23,7 +23,8 @@ type v4Server struct { srv *server4.Server leasesLock sync.Mutex leases []*Lease - ipAddrs [256]byte + // TODO(e.burkov): This field type should be a normal bitmap. + ipAddrs [256]byte conf V4ServerConf } diff --git a/internal/dhcpd/v46_test.go b/internal/dhcpd/v46_test.go index 6007205d..6495eeee 100644 --- a/internal/dhcpd/v46_test.go +++ b/internal/dhcpd/v46_test.go @@ -7,6 +7,7 @@ import ( "github.com/AdguardTeam/AdGuardHome/internal/agherr" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) type fakeIface struct { @@ -79,8 +80,8 @@ func TestIfaceIPAddrs(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { got, gotErr := ifaceIPAddrs(tc.iface, tc.ipv) + require.True(t, errors.Is(gotErr, tc.wantErr)) assert.Equal(t, tc.want, got) - assert.True(t, errors.Is(gotErr, tc.wantErr)) }) } } @@ -140,12 +141,8 @@ func TestIfaceDNSIPAddrs(t *testing.T) { want: nil, wantErr: errTest, }, { - name: "ipv4_wait", - iface: &waitingFakeIface{ - addrs: []net.Addr{addr4}, - err: nil, - n: 1, - }, + name: "ipv4_wait", + iface: &waitingFakeIface{addrs: []net.Addr{addr4}, err: nil, n: 1}, ipv: ipVersion4, want: []net.IP{ip4, ip4}, wantErr: nil, @@ -168,12 +165,8 @@ func TestIfaceDNSIPAddrs(t *testing.T) { want: nil, wantErr: errTest, }, { - name: "ipv6_wait", - iface: &waitingFakeIface{ - addrs: []net.Addr{addr6}, - err: nil, - n: 1, - }, + name: "ipv6_wait", + iface: &waitingFakeIface{addrs: []net.Addr{addr6}, err: nil, n: 1}, ipv: ipVersion6, want: []net.IP{ip6, ip6}, wantErr: nil, @@ -182,8 +175,8 @@ func TestIfaceDNSIPAddrs(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { got, gotErr := ifaceDNSIPAddrs(tc.iface, tc.ipv, 2, 0) + require.True(t, errors.Is(gotErr, tc.wantErr)) assert.Equal(t, tc.want, got) - assert.True(t, errors.Is(gotErr, tc.wantErr)) }) } } diff --git a/internal/dhcpd/v4_test.go b/internal/dhcpd/v4_test.go index 8edb3113..d204a200 100644 --- a/internal/dhcpd/v4_test.go +++ b/internal/dhcpd/v4_test.go @@ -8,172 +8,182 @@ import ( "github.com/insomniacslk/dhcp/dhcpv4" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func notify4(flags uint32) { } -func TestV4StaticLeaseAddRemove(t *testing.T) { - conf := V4ServerConf{ +func TestV4_AddRemove_static(t *testing.T) { + s, err := v4Create(V4ServerConf{ Enabled: true, RangeStart: net.IP{192, 168, 10, 100}, RangeEnd: net.IP{192, 168, 10, 200}, GatewayIP: net.IP{192, 168, 10, 1}, SubnetMask: net.IP{255, 255, 255, 0}, notify: notify4, - } - s, err := v4Create(conf) - assert.Nil(t, err) + }) + require.Nil(t, err) ls := s.GetLeases(LeasesStatic) assert.Empty(t, ls) - // add static lease - l := Lease{} - l.IP = net.IP{192, 168, 10, 150} - l.HWAddr, _ = net.ParseMAC("aa:aa:aa:aa:aa:aa") - assert.Nil(t, s.AddStaticLease(l)) - - // try to add the same static lease - fail + // Add static lease. + l := Lease{ + IP: net.IP{192, 168, 10, 150}, + HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, + } + require.Nil(t, s.AddStaticLease(l)) assert.NotNil(t, s.AddStaticLease(l)) - // check ls = s.GetLeases(LeasesStatic) - assert.Len(t, ls, 1) - assert.True(t, net.IP{192, 168, 10, 150}.Equal(ls[0].IP)) - assert.Equal(t, "aa:aa:aa:aa:aa:aa", ls[0].HWAddr.String()) + require.Len(t, ls, 1) + assert.True(t, l.IP.Equal(ls[0].IP)) + assert.Equal(t, l.HWAddr, ls[0].HWAddr) assert.EqualValues(t, leaseExpireStatic, ls[0].Expiry.Unix()) - // try to remove static lease - fail - l.IP = net.IP{192, 168, 10, 110} - l.HWAddr, _ = net.ParseMAC("aa:aa:aa:aa:aa:aa") - assert.NotNil(t, s.RemoveStaticLease(l)) + // Try to remove static lease. + assert.NotNil(t, s.RemoveStaticLease(Lease{ + IP: net.IP{192, 168, 10, 110}, + HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, + })) - // remove static lease - l.IP = net.IP{192, 168, 10, 150} - l.HWAddr, _ = net.ParseMAC("aa:aa:aa:aa:aa:aa") - assert.Nil(t, s.RemoveStaticLease(l)) - - // check + // Remove static lease. + require.Nil(t, s.RemoveStaticLease(l)) ls = s.GetLeases(LeasesStatic) assert.Empty(t, ls) } -func TestV4StaticLeaseAddReplaceDynamic(t *testing.T) { - conf := V4ServerConf{ +func TestV4_AddReplace(t *testing.T) { + sIface, err := v4Create(V4ServerConf{ Enabled: true, RangeStart: net.IP{192, 168, 10, 100}, RangeEnd: net.IP{192, 168, 10, 200}, GatewayIP: net.IP{192, 168, 10, 1}, SubnetMask: net.IP{255, 255, 255, 0}, notify: notify4, - } - sIface, err := v4Create(conf) + }) + require.Nil(t, err) + s, ok := sIface.(*v4Server) - assert.True(t, ok) - assert.Nil(t, err) + require.True(t, ok) - // add dynamic lease - ld := Lease{} - ld.IP = net.IP{192, 168, 10, 150} - ld.HWAddr, _ = net.ParseMAC("11:aa:aa:aa:aa:aa") - s.addLease(&ld) + dynLeases := []Lease{{ + IP: net.IP{192, 168, 10, 150}, + HWAddr: net.HardwareAddr{0x11, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, + }, { + IP: net.IP{192, 168, 10, 151}, + HWAddr: net.HardwareAddr{0x22, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, + }} - // add dynamic lease - { - ld := Lease{} - ld.IP = net.IP{192, 168, 10, 151} - ld.HWAddr, _ = net.ParseMAC("22:aa:aa:aa:aa:aa") - s.addLease(&ld) + for i := range dynLeases { + s.addLease(&dynLeases[i]) } - // add static lease with the same IP - l := Lease{} - l.IP = net.IP{192, 168, 10, 150} - l.HWAddr, _ = net.ParseMAC("33:aa:aa:aa:aa:aa") - assert.Nil(t, s.AddStaticLease(l)) + stLeases := []Lease{{ + IP: net.IP{192, 168, 10, 150}, + HWAddr: net.HardwareAddr{0x33, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, + }, { + IP: net.IP{192, 168, 10, 152}, + HWAddr: net.HardwareAddr{0x22, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, + }} - // add static lease with the same MAC - l = Lease{} - l.IP = net.IP{192, 168, 10, 152} - l.HWAddr, _ = net.ParseMAC("22:aa:aa:aa:aa:aa") - assert.Nil(t, s.AddStaticLease(l)) + for _, l := range stLeases { + require.Nil(t, s.AddStaticLease(l)) + } - // check ls := s.GetLeases(LeasesStatic) - assert.Len(t, ls, 2) + require.Len(t, ls, 2) - assert.True(t, net.IP{192, 168, 10, 150}.Equal(ls[0].IP)) - assert.Equal(t, "33:aa:aa:aa:aa:aa", ls[0].HWAddr.String()) - assert.EqualValues(t, leaseExpireStatic, ls[0].Expiry.Unix()) - - assert.True(t, net.IP{192, 168, 10, 152}.Equal(ls[1].IP)) - assert.Equal(t, "22:aa:aa:aa:aa:aa", ls[1].HWAddr.String()) - assert.EqualValues(t, leaseExpireStatic, ls[1].Expiry.Unix()) + for i, l := range ls { + assert.True(t, stLeases[i].IP.Equal(l.IP)) + assert.Equal(t, stLeases[i].HWAddr, l.HWAddr) + assert.EqualValues(t, leaseExpireStatic, l.Expiry.Unix()) + } } -func TestV4StaticLeaseGet(t *testing.T) { - conf := V4ServerConf{ +func TestV4StaticLease_Get(t *testing.T) { + var err error + sIface, err := v4Create(V4ServerConf{ Enabled: true, RangeStart: net.IP{192, 168, 10, 100}, RangeEnd: net.IP{192, 168, 10, 200}, GatewayIP: net.IP{192, 168, 10, 1}, SubnetMask: net.IP{255, 255, 255, 0}, notify: notify4, - } - sIface, err := v4Create(conf) + }) + require.Nil(t, err) + s, ok := sIface.(*v4Server) - assert.True(t, ok) - assert.Nil(t, err) + require.True(t, ok) s.conf.dnsIPAddrs = []net.IP{{192, 168, 10, 1}} - l := Lease{} - l.IP = net.IP{192, 168, 10, 150} - l.HWAddr, _ = net.ParseMAC("aa:aa:aa:aa:aa:aa") - assert.Nil(t, s.AddStaticLease(l)) + l := Lease{ + IP: net.IP{192, 168, 10, 150}, + HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, + } + require.Nil(t, s.AddStaticLease(l)) - // "Discover" - mac, _ := net.ParseMAC("aa:aa:aa:aa:aa:aa") - req, _ := dhcpv4.NewDiscovery(mac) - resp, _ := dhcpv4.NewReplyFromRequest(req) - assert.Equal(t, 1, s.process(req, resp)) + var req, resp *dhcpv4.DHCPv4 + mac := net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA} - // check "Offer" - assert.Equal(t, dhcpv4.MessageTypeOffer, resp.MessageType()) - assert.Equal(t, "aa:aa:aa:aa:aa:aa", resp.ClientHWAddr.String()) - assert.True(t, net.IP{192, 168, 10, 150}.Equal(resp.YourIPAddr)) - assert.True(t, net.IP{192, 168, 10, 1}.Equal(resp.Router()[0])) - assert.True(t, net.IP{192, 168, 10, 1}.Equal(resp.ServerIdentifier())) - assert.True(t, net.IP{255, 255, 255, 0}.Equal(net.IP(resp.SubnetMask()))) - assert.Equal(t, s.conf.leaseTime.Seconds(), resp.IPAddressLeaseTime(-1).Seconds()) + t.Run("discover", func(t *testing.T) { + var err error - // "Request" - req, _ = dhcpv4.NewRequestFromOffer(resp) - resp, _ = dhcpv4.NewReplyFromRequest(req) - assert.Equal(t, 1, s.process(req, resp)) + req, err = dhcpv4.NewDiscovery(mac) + require.Nil(t, err) - // check "Ack" - assert.Equal(t, dhcpv4.MessageTypeAck, resp.MessageType()) - assert.Equal(t, "aa:aa:aa:aa:aa:aa", resp.ClientHWAddr.String()) - assert.True(t, net.IP{192, 168, 10, 150}.Equal(resp.YourIPAddr)) - assert.True(t, net.IP{192, 168, 10, 1}.Equal(resp.Router()[0])) - assert.True(t, net.IP{192, 168, 10, 1}.Equal(resp.ServerIdentifier())) - assert.True(t, net.IP{255, 255, 255, 0}.Equal(net.IP(resp.SubnetMask()))) - assert.Equal(t, s.conf.leaseTime.Seconds(), resp.IPAddressLeaseTime(-1).Seconds()) + resp, err = dhcpv4.NewReplyFromRequest(req) + require.Nil(t, err) + assert.Equal(t, 1, s.process(req, resp)) + }) + require.Nil(t, err) + + t.Run("offer", func(t *testing.T) { + assert.Equal(t, dhcpv4.MessageTypeOffer, resp.MessageType()) + assert.Equal(t, mac, resp.ClientHWAddr) + assert.True(t, l.IP.Equal(resp.YourIPAddr)) + assert.True(t, s.conf.GatewayIP.Equal(resp.Router()[0])) + assert.True(t, s.conf.GatewayIP.Equal(resp.ServerIdentifier())) + assert.Equal(t, s.conf.subnetMask, resp.SubnetMask()) + assert.Equal(t, s.conf.leaseTime.Seconds(), resp.IPAddressLeaseTime(-1).Seconds()) + }) + + t.Run("request", func(t *testing.T) { + req, err = dhcpv4.NewRequestFromOffer(resp) + require.Nil(t, err) + + resp, err = dhcpv4.NewReplyFromRequest(req) + require.Nil(t, err) + assert.Equal(t, 1, s.process(req, resp)) + }) + require.Nil(t, err) + + t.Run("ack", func(t *testing.T) { + assert.Equal(t, dhcpv4.MessageTypeAck, resp.MessageType()) + assert.Equal(t, mac, resp.ClientHWAddr) + assert.True(t, l.IP.Equal(resp.YourIPAddr)) + assert.True(t, s.conf.GatewayIP.Equal(resp.Router()[0])) + assert.True(t, s.conf.GatewayIP.Equal(resp.ServerIdentifier())) + assert.Equal(t, s.conf.subnetMask, resp.SubnetMask()) + assert.Equal(t, s.conf.leaseTime.Seconds(), resp.IPAddressLeaseTime(-1).Seconds()) + }) dnsAddrs := resp.DNS() - assert.Len(t, dnsAddrs, 1) - assert.True(t, net.IP{192, 168, 10, 1}.Equal(dnsAddrs[0])) + require.Len(t, dnsAddrs, 1) + assert.True(t, s.conf.GatewayIP.Equal(dnsAddrs[0])) - // check lease - ls := s.GetLeases(LeasesStatic) - assert.Len(t, ls, 1) - assert.True(t, net.IP{192, 168, 10, 150}.Equal(ls[0].IP)) - assert.Equal(t, "aa:aa:aa:aa:aa:aa", ls[0].HWAddr.String()) + t.Run("check_lease", func(t *testing.T) { + ls := s.GetLeases(LeasesStatic) + require.Len(t, ls, 1) + assert.True(t, l.IP.Equal(ls[0].IP)) + assert.Equal(t, mac, ls[0].HWAddr) + }) } -func TestV4DynamicLeaseGet(t *testing.T) { - conf := V4ServerConf{ +func TestV4DynamicLease_Get(t *testing.T) { + var err error + sIface, err := v4Create(V4ServerConf{ Enabled: true, RangeStart: net.IP{192, 168, 10, 100}, RangeEnd: net.IP{192, 168, 10, 200}, @@ -184,58 +194,97 @@ func TestV4DynamicLeaseGet(t *testing.T) { "81 hex 303132", "82 ip 1.2.3.4", }, - } - sIface, err := v4Create(conf) + }) + require.Nil(t, err) + s, ok := sIface.(*v4Server) - assert.True(t, ok) - assert.Nil(t, err) + require.True(t, ok) s.conf.dnsIPAddrs = []net.IP{{192, 168, 10, 1}} - // "Discover" - mac, _ := net.ParseMAC("aa:aa:aa:aa:aa:aa") - req, _ := dhcpv4.NewDiscovery(mac) - resp, _ := dhcpv4.NewReplyFromRequest(req) - assert.Equal(t, 1, s.process(req, resp)) + var req, resp *dhcpv4.DHCPv4 + mac := net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA} - // check "Offer" - assert.Equal(t, dhcpv4.MessageTypeOffer, resp.MessageType()) - assert.Equal(t, "aa:aa:aa:aa:aa:aa", resp.ClientHWAddr.String()) - assert.True(t, net.IP{192, 168, 10, 100}.Equal(resp.YourIPAddr)) - assert.True(t, net.IP{192, 168, 10, 1}.Equal(resp.Router()[0])) - assert.True(t, net.IP{192, 168, 10, 1}.Equal(resp.ServerIdentifier())) - assert.True(t, net.IP{255, 255, 255, 0}.Equal(net.IP(resp.SubnetMask()))) - assert.Equal(t, s.conf.leaseTime.Seconds(), resp.IPAddressLeaseTime(-1).Seconds()) - assert.Equal(t, []byte("012"), resp.Options[uint8(dhcpv4.OptionFQDN)]) - assert.True(t, net.IP{1, 2, 3, 4}.Equal(net.IP(resp.Options[uint8(dhcpv4.OptionRelayAgentInformation)]))) + t.Run("discover", func(t *testing.T) { + req, err = dhcpv4.NewDiscovery(mac) + require.Nil(t, err) - // "Request" - req, _ = dhcpv4.NewRequestFromOffer(resp) - resp, _ = dhcpv4.NewReplyFromRequest(req) - assert.Equal(t, 1, s.process(req, resp)) + resp, err = dhcpv4.NewReplyFromRequest(req) + require.Nil(t, err) + assert.Equal(t, 1, s.process(req, resp)) + }) + require.Nil(t, err) - // check "Ack" - assert.Equal(t, dhcpv4.MessageTypeAck, resp.MessageType()) - assert.Equal(t, "aa:aa:aa:aa:aa:aa", resp.ClientHWAddr.String()) - assert.True(t, net.IP{192, 168, 10, 100}.Equal(resp.YourIPAddr)) - assert.True(t, net.IP{192, 168, 10, 1}.Equal(resp.Router()[0])) - assert.True(t, net.IP{192, 168, 10, 1}.Equal(resp.ServerIdentifier())) - assert.True(t, net.IP{255, 255, 255, 0}.Equal(net.IP(resp.SubnetMask()))) - assert.Equal(t, s.conf.leaseTime.Seconds(), resp.IPAddressLeaseTime(-1).Seconds()) + t.Run("offer", func(t *testing.T) { + assert.Equal(t, dhcpv4.MessageTypeOffer, resp.MessageType()) + assert.Equal(t, mac, resp.ClientHWAddr) + assert.True(t, s.conf.RangeStart.Equal(resp.YourIPAddr)) + assert.True(t, s.conf.GatewayIP.Equal(resp.Router()[0])) + assert.True(t, s.conf.GatewayIP.Equal(resp.ServerIdentifier())) + assert.Equal(t, s.conf.subnetMask, resp.SubnetMask()) + assert.Equal(t, s.conf.leaseTime.Seconds(), resp.IPAddressLeaseTime(-1).Seconds()) + assert.Equal(t, []byte("012"), resp.Options[uint8(dhcpv4.OptionFQDN)]) + assert.True(t, net.IP{1, 2, 3, 4}.Equal(net.IP(resp.Options[uint8(dhcpv4.OptionRelayAgentInformation)]))) + }) + + t.Run("request", func(t *testing.T) { + var err error + + req, err = dhcpv4.NewRequestFromOffer(resp) + require.Nil(t, err) + + resp, err = dhcpv4.NewReplyFromRequest(req) + require.Nil(t, err) + assert.Equal(t, 1, s.process(req, resp)) + }) + require.Nil(t, err) + + t.Run("ack", func(t *testing.T) { + assert.Equal(t, dhcpv4.MessageTypeAck, resp.MessageType()) + assert.Equal(t, mac, resp.ClientHWAddr) + assert.True(t, s.conf.RangeStart.Equal(resp.YourIPAddr)) + assert.True(t, s.conf.GatewayIP.Equal(resp.Router()[0])) + assert.True(t, s.conf.GatewayIP.Equal(resp.ServerIdentifier())) + assert.Equal(t, s.conf.subnetMask, resp.SubnetMask()) + assert.Equal(t, s.conf.leaseTime.Seconds(), resp.IPAddressLeaseTime(-1).Seconds()) + }) dnsAddrs := resp.DNS() - assert.Len(t, dnsAddrs, 1) + require.Len(t, dnsAddrs, 1) assert.True(t, net.IP{192, 168, 10, 1}.Equal(dnsAddrs[0])) // check lease - ls := s.GetLeases(LeasesDynamic) - assert.Len(t, ls, 1) - assert.True(t, net.IP{192, 168, 10, 100}.Equal(ls[0].IP)) - assert.Equal(t, "aa:aa:aa:aa:aa:aa", ls[0].HWAddr.String()) + t.Run("check_lease", func(t *testing.T) { + ls := s.GetLeases(LeasesDynamic) + assert.Len(t, ls, 1) + assert.True(t, net.IP{192, 168, 10, 100}.Equal(ls[0].IP)) + assert.Equal(t, mac, ls[0].HWAddr) + }) +} +func TestIP4InRange(t *testing.T) { start := net.IP{192, 168, 10, 100} stop := net.IP{192, 168, 10, 200} - assert.False(t, ip4InRange(start, stop, net.IP{192, 168, 10, 99})) - assert.False(t, ip4InRange(start, stop, net.IP{192, 168, 11, 100})) - assert.False(t, ip4InRange(start, stop, net.IP{192, 168, 11, 201})) - assert.True(t, ip4InRange(start, stop, net.IP{192, 168, 10, 100})) + + testCases := []struct { + ip net.IP + want bool + }{{ + ip: net.IP{192, 168, 10, 99}, + want: false, + }, { + ip: net.IP{192, 168, 11, 100}, + want: false, + }, { + ip: net.IP{192, 168, 11, 201}, + want: false, + }, { + ip: start, + want: true, + }} + + for _, tc := range testCases { + t.Run(tc.ip.String(), func(t *testing.T) { + assert.Equal(t, tc.want, ip4InRange(start, stop, tc.ip)) + }) + } } diff --git a/internal/dhcpd/v6_test.go b/internal/dhcpd/v6_test.go index 9cdf3ee4..3eb06a89 100644 --- a/internal/dhcpd/v6_test.go +++ b/internal/dhcpd/v6_test.go @@ -9,220 +9,283 @@ import ( "github.com/insomniacslk/dhcp/dhcpv6" "github.com/insomniacslk/dhcp/iana" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func notify6(flags uint32) { } -func TestV6StaticLeaseAddRemove(t *testing.T) { - conf := V6ServerConf{ +func TestV6_AddRemove_static(t *testing.T) { + s, err := v6Create(V6ServerConf{ Enabled: true, RangeStart: net.ParseIP("2001::1"), notify: notify6, + }) + require.Nil(t, err) + + require.Empty(t, s.GetLeases(LeasesStatic)) + + // Add static lease. + l := Lease{ + IP: net.ParseIP("2001::1"), + HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, } - s, err := v6Create(conf) - assert.Nil(t, err) + require.Nil(t, s.AddStaticLease(l)) + + // Try to add the same static lease. + require.NotNil(t, s.AddStaticLease(l)) ls := s.GetLeases(LeasesStatic) - assert.Empty(t, ls) - - // add static lease - l := Lease{} - l.IP = net.ParseIP("2001::1") - l.HWAddr, _ = net.ParseMAC("aa:aa:aa:aa:aa:aa") - assert.Nil(t, s.AddStaticLease(l)) - - // try to add static lease - fail - assert.NotNil(t, s.AddStaticLease(l)) - - // check - ls = s.GetLeases(LeasesStatic) - assert.Len(t, ls, 1) - assert.Equal(t, "2001::1", ls[0].IP.String()) - assert.Equal(t, "aa:aa:aa:aa:aa:aa", ls[0].HWAddr.String()) + require.Len(t, ls, 1) + assert.Equal(t, l.IP, ls[0].IP) + assert.Equal(t, l.HWAddr, ls[0].HWAddr) assert.EqualValues(t, leaseExpireStatic, ls[0].Expiry.Unix()) - // try to remove static lease - fail - l.IP = net.ParseIP("2001::2") - l.HWAddr, _ = net.ParseMAC("aa:aa:aa:aa:aa:aa") - assert.NotNil(t, s.RemoveStaticLease(l)) + // Try to remove non-existent static lease. + require.NotNil(t, s.RemoveStaticLease(Lease{ + IP: net.ParseIP("2001::2"), + HWAddr: l.HWAddr, + })) - // remove static lease - l.IP = net.ParseIP("2001::1") - l.HWAddr, _ = net.ParseMAC("aa:aa:aa:aa:aa:aa") - assert.Nil(t, s.RemoveStaticLease(l)) + // Remove static lease. + require.Nil(t, s.RemoveStaticLease(l)) - // check - ls = s.GetLeases(LeasesStatic) - assert.Empty(t, ls) + assert.Empty(t, s.GetLeases(LeasesStatic)) } -func TestV6StaticLeaseAddReplaceDynamic(t *testing.T) { - conf := V6ServerConf{ +func TestV6_AddReplace(t *testing.T) { + sIface, err := v6Create(V6ServerConf{ Enabled: true, RangeStart: net.ParseIP("2001::1"), notify: notify6, - } - sIface, err := v6Create(conf) + }) + require.Nil(t, err) s, ok := sIface.(*v6Server) - assert.True(t, ok) - assert.Nil(t, err) + require.True(t, ok) - // add dynamic lease - ld := Lease{} - ld.IP = net.ParseIP("2001::1") - ld.HWAddr, _ = net.ParseMAC("11:aa:aa:aa:aa:aa") - s.addLease(&ld) + // Add dynamic leases. + dynLeases := []*Lease{{ + IP: net.ParseIP("2001::1"), + HWAddr: net.HardwareAddr{0x11, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, + }, { + IP: net.ParseIP("2001::2"), + HWAddr: net.HardwareAddr{0x22, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, + }} - // add dynamic lease - { - ld := Lease{} - ld.IP = net.ParseIP("2001::2") - ld.HWAddr, _ = net.ParseMAC("22:aa:aa:aa:aa:aa") - s.addLease(&ld) + for _, l := range dynLeases { + s.addLease(l) } - // add static lease with the same IP - l := Lease{} - l.IP = net.ParseIP("2001::1") - l.HWAddr, _ = net.ParseMAC("33:aa:aa:aa:aa:aa") - assert.Nil(t, s.AddStaticLease(l)) + stLeases := []Lease{{ + IP: net.ParseIP("2001::1"), + HWAddr: net.HardwareAddr{0x33, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, + }, { + IP: net.ParseIP("2001::3"), + HWAddr: net.HardwareAddr{0x22, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, + }} - // add static lease with the same MAC - l = Lease{} - l.IP = net.ParseIP("2001::3") - l.HWAddr, _ = net.ParseMAC("22:aa:aa:aa:aa:aa") - assert.Nil(t, s.AddStaticLease(l)) + for _, l := range stLeases { + require.Nil(t, s.AddStaticLease(l)) + } - // check ls := s.GetLeases(LeasesStatic) - assert.Len(t, ls, 2) + require.Len(t, ls, 2) - assert.Equal(t, "2001::1", ls[0].IP.String()) - assert.Equal(t, "33:aa:aa:aa:aa:aa", ls[0].HWAddr.String()) - assert.EqualValues(t, leaseExpireStatic, ls[0].Expiry.Unix()) - - assert.Equal(t, "2001::3", ls[1].IP.String()) - assert.Equal(t, "22:aa:aa:aa:aa:aa", ls[1].HWAddr.String()) - assert.EqualValues(t, leaseExpireStatic, ls[1].Expiry.Unix()) + for i, l := range ls { + assert.True(t, stLeases[i].IP.Equal(l.IP)) + assert.Equal(t, stLeases[i].HWAddr, l.HWAddr) + assert.EqualValues(t, leaseExpireStatic, l.Expiry.Unix()) + } } func TestV6GetLease(t *testing.T) { - conf := V6ServerConf{ + var err error + sIface, err := v6Create(V6ServerConf{ Enabled: true, RangeStart: net.ParseIP("2001::1"), notify: notify6, - } - sIface, err := v6Create(conf) + }) + require.Nil(t, err) s, ok := sIface.(*v6Server) - assert.True(t, ok) - assert.Nil(t, err) - s.conf.dnsIPAddrs = []net.IP{net.ParseIP("2000::1")} + require.True(t, ok) + + dnsAddr := net.ParseIP("2000::1") + s.conf.dnsIPAddrs = []net.IP{dnsAddr} s.sid = dhcpv6.Duid{ - Type: dhcpv6.DUID_LLT, - HwType: iana.HWTypeEthernet, + Type: dhcpv6.DUID_LLT, + HwType: iana.HWTypeEthernet, + LinkLayerAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, } - s.sid.LinkLayerAddr, _ = net.ParseMAC("aa:aa:aa:aa:aa:aa") - l := Lease{} - l.IP = net.ParseIP("2001::1") - l.HWAddr, _ = net.ParseMAC("aa:aa:aa:aa:aa:aa") - assert.Nil(t, s.AddStaticLease(l)) + l := Lease{ + IP: net.ParseIP("2001::1"), + HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, + } + require.Nil(t, s.AddStaticLease(l)) - // "Solicit" - mac, _ := net.ParseMAC("aa:aa:aa:aa:aa:aa") - req, _ := dhcpv6.NewSolicit(mac) - msg, _ := req.GetInnerMessage() - resp, _ := dhcpv6.NewAdvertiseFromSolicit(msg) - assert.True(t, s.process(msg, req, resp)) + var req, resp, msg *dhcpv6.Message + mac := net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA} + t.Run("solicit", func(t *testing.T) { + req, err = dhcpv6.NewSolicit(mac) + require.Nil(t, err) + + msg, err = req.GetInnerMessage() + require.Nil(t, err) + + resp, err = dhcpv6.NewAdvertiseFromSolicit(msg) + require.Nil(t, err) + + assert.True(t, s.process(msg, req, resp)) + }) + require.Nil(t, err) resp.AddOption(dhcpv6.OptServerID(s.sid)) - // check "Advertise" - assert.Equal(t, dhcpv6.MessageTypeAdvertise, resp.Type()) - oia := resp.Options.OneIANA() - oiaAddr := oia.Options.OneAddress() - assert.Equal(t, "2001::1", oiaAddr.IPv6Addr.String()) - assert.Equal(t, s.conf.leaseTime.Seconds(), oiaAddr.ValidLifetime.Seconds()) + var oia *dhcpv6.OptIANA + var oiaAddr *dhcpv6.OptIAAddress + t.Run("advertise", func(t *testing.T) { + require.Equal(t, dhcpv6.MessageTypeAdvertise, resp.Type()) + oia = resp.Options.OneIANA() + oiaAddr = oia.Options.OneAddress() - // "Request" - req, _ = dhcpv6.NewRequestFromAdvertise(resp) - msg, _ = req.GetInnerMessage() - resp, _ = dhcpv6.NewReplyFromMessage(msg) - assert.True(t, s.process(msg, req, resp)) + assert.Equal(t, l.IP, oiaAddr.IPv6Addr) + assert.Equal(t, s.conf.leaseTime.Seconds(), oiaAddr.ValidLifetime.Seconds()) + }) - // check "Reply" - assert.Equal(t, dhcpv6.MessageTypeReply, resp.Type()) - oia = resp.Options.OneIANA() - oiaAddr = oia.Options.OneAddress() - assert.Equal(t, "2001::1", oiaAddr.IPv6Addr.String()) - assert.Equal(t, s.conf.leaseTime.Seconds(), oiaAddr.ValidLifetime.Seconds()) + t.Run("request", func(t *testing.T) { + req, err = dhcpv6.NewRequestFromAdvertise(resp) + require.Nil(t, err) + + msg, err = req.GetInnerMessage() + require.Nil(t, err) + + resp, err = dhcpv6.NewReplyFromMessage(msg) + require.Nil(t, err) + + assert.True(t, s.process(msg, req, resp)) + }) + require.Nil(t, err) + + t.Run("reply", func(t *testing.T) { + require.Equal(t, dhcpv6.MessageTypeReply, resp.Type()) + oia = resp.Options.OneIANA() + oiaAddr = oia.Options.OneAddress() + + assert.Equal(t, l.IP, oiaAddr.IPv6Addr) + assert.Equal(t, s.conf.leaseTime.Seconds(), oiaAddr.ValidLifetime.Seconds()) + }) dnsAddrs := resp.Options.DNS() - assert.Len(t, dnsAddrs, 1) - assert.Equal(t, "2000::1", dnsAddrs[0].String()) + require.Len(t, dnsAddrs, 1) + assert.Equal(t, dnsAddr, dnsAddrs[0]) - // check lease - ls := s.GetLeases(LeasesStatic) - assert.Len(t, ls, 1) - assert.Equal(t, "2001::1", ls[0].IP.String()) - assert.Equal(t, "aa:aa:aa:aa:aa:aa", ls[0].HWAddr.String()) + t.Run("lease", func(t *testing.T) { + ls := s.GetLeases(LeasesStatic) + require.Len(t, ls, 1) + assert.Equal(t, l.IP, ls[0].IP) + assert.Equal(t, l.HWAddr, ls[0].HWAddr) + }) } func TestV6GetDynamicLease(t *testing.T) { - conf := V6ServerConf{ + sIface, err := v6Create(V6ServerConf{ Enabled: true, RangeStart: net.ParseIP("2001::2"), notify: notify6, - } - sIface, err := v6Create(conf) + }) + require.Nil(t, err) s, ok := sIface.(*v6Server) - assert.True(t, ok) - assert.Nil(t, err) - s.conf.dnsIPAddrs = []net.IP{net.ParseIP("2000::1")} - s.sid = dhcpv6.Duid{ - Type: dhcpv6.DUID_LLT, - HwType: iana.HWTypeEthernet, - } - s.sid.LinkLayerAddr, _ = net.ParseMAC("aa:aa:aa:aa:aa:aa") + require.True(t, ok) - // "Solicit" - mac, _ := net.ParseMAC("aa:aa:aa:aa:aa:aa") - req, _ := dhcpv6.NewSolicit(mac) - msg, _ := req.GetInnerMessage() - resp, _ := dhcpv6.NewAdvertiseFromSolicit(msg) - assert.True(t, s.process(msg, req, resp)) + dnsAddr := net.ParseIP("2000::1") + s.conf.dnsIPAddrs = []net.IP{dnsAddr} + s.sid = dhcpv6.Duid{ + Type: dhcpv6.DUID_LLT, + HwType: iana.HWTypeEthernet, + LinkLayerAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, + } + + var req, resp, msg *dhcpv6.Message + mac := net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA} + t.Run("solicit", func(t *testing.T) { + req, err = dhcpv6.NewSolicit(mac) + require.Nil(t, err) + + msg, err = req.GetInnerMessage() + require.Nil(t, err) + + resp, err = dhcpv6.NewAdvertiseFromSolicit(msg) + require.Nil(t, err) + + assert.True(t, s.process(msg, req, resp)) + }) + require.Nil(t, err) resp.AddOption(dhcpv6.OptServerID(s.sid)) - // check "Advertise" - assert.Equal(t, dhcpv6.MessageTypeAdvertise, resp.Type()) - oia := resp.Options.OneIANA() - oiaAddr := oia.Options.OneAddress() - assert.Equal(t, "2001::2", oiaAddr.IPv6Addr.String()) + var oia *dhcpv6.OptIANA + var oiaAddr *dhcpv6.OptIAAddress + t.Run("advertise", func(t *testing.T) { + require.Equal(t, dhcpv6.MessageTypeAdvertise, resp.Type()) + oia = resp.Options.OneIANA() + oiaAddr = oia.Options.OneAddress() + assert.Equal(t, "2001::2", oiaAddr.IPv6Addr.String()) + }) - // "Request" - req, _ = dhcpv6.NewRequestFromAdvertise(resp) - msg, _ = req.GetInnerMessage() - resp, _ = dhcpv6.NewReplyFromMessage(msg) - assert.True(t, s.process(msg, req, resp)) + t.Run("request", func(t *testing.T) { + req, err = dhcpv6.NewRequestFromAdvertise(resp) + require.Nil(t, err) - // check "Reply" - assert.Equal(t, dhcpv6.MessageTypeReply, resp.Type()) - oia = resp.Options.OneIANA() - oiaAddr = oia.Options.OneAddress() - assert.Equal(t, "2001::2", oiaAddr.IPv6Addr.String()) + msg, err = req.GetInnerMessage() + require.Nil(t, err) + + resp, err = dhcpv6.NewReplyFromMessage(msg) + require.Nil(t, err) + + assert.True(t, s.process(msg, req, resp)) + }) + require.Nil(t, err) + + t.Run("reply", func(t *testing.T) { + require.Equal(t, dhcpv6.MessageTypeReply, resp.Type()) + oia = resp.Options.OneIANA() + oiaAddr = oia.Options.OneAddress() + assert.Equal(t, "2001::2", oiaAddr.IPv6Addr.String()) + }) dnsAddrs := resp.Options.DNS() - assert.Len(t, dnsAddrs, 1) - assert.Equal(t, "2000::1", dnsAddrs[0].String()) + require.Len(t, dnsAddrs, 1) + assert.Equal(t, dnsAddr, dnsAddrs[0]) - // check lease - ls := s.GetLeases(LeasesDynamic) - assert.Len(t, ls, 1) - assert.Equal(t, "2001::2", ls[0].IP.String()) - assert.Equal(t, "aa:aa:aa:aa:aa:aa", ls[0].HWAddr.String()) - - assert.False(t, ip6InRange(net.ParseIP("2001::2"), net.ParseIP("2001::1"))) - assert.False(t, ip6InRange(net.ParseIP("2001::2"), net.ParseIP("2002::2"))) - assert.True(t, ip6InRange(net.ParseIP("2001::2"), net.ParseIP("2001::2"))) - assert.True(t, ip6InRange(net.ParseIP("2001::2"), net.ParseIP("2001::3"))) + t.Run("lease", func(t *testing.T) { + ls := s.GetLeases(LeasesDynamic) + require.Len(t, ls, 1) + assert.Equal(t, "2001::2", ls[0].IP.String()) + assert.Equal(t, mac, ls[0].HWAddr) + }) +} + +func TestIP6InRange(t *testing.T) { + start := net.ParseIP("2001::2") + + testCases := []struct { + ip net.IP + want bool + }{{ + ip: net.ParseIP("2001::1"), + want: false, + }, { + ip: net.ParseIP("2002::2"), + want: false, + }, { + ip: start, + want: true, + }, { + ip: net.ParseIP("2001::3"), + want: true, + }} + + for _, tc := range testCases { + t.Run(tc.ip.String(), func(t *testing.T) { + assert.Equal(t, tc.want, ip6InRange(start, tc.ip)) + }) + } } diff --git a/internal/home/ipdetector_test.go b/internal/home/ipdetector_test.go index ee20612f..6609ba08 100644 --- a/internal/home/ipdetector_test.go +++ b/internal/home/ipdetector_test.go @@ -5,16 +5,15 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestIPDetector_detectSpecialNetwork(t *testing.T) { var ipd *ipDetector + var err error - t.Run("newIPDetector", func(t *testing.T) { - var err error - ipd, err = newIPDetector() - assert.Nil(t, err) - }) + ipd, err = newIPDetector() + require.Nil(t, err) testCases := []struct { name string diff --git a/internal/home/middlewares_test.go b/internal/home/middlewares_test.go index 53b7a933..fbd7a214 100644 --- a/internal/home/middlewares_test.go +++ b/internal/home/middlewares_test.go @@ -9,6 +9,7 @@ import ( "github.com/AdguardTeam/AdGuardHome/internal/aghio" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestLimitRequestBody(t *testing.T) { @@ -60,8 +61,8 @@ func TestLimitRequestBody(t *testing.T) { lim.ServeHTTP(res, req) + require.Equal(t, tc.wantErr, err) assert.Equal(t, tc.want, res.Body.Bytes()) - assert.Equal(t, tc.wantErr, err) }) } } From 10f03b7527e0974a085aba8aeb4192e24b8ea19b Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Fri, 12 Feb 2021 16:40:34 +0300 Subject: [PATCH 123/141] Pull request: dhcpd: assume static ip on eperm Merge in DNS/adguard-home from 2667-eperm-dhcp to master Updates #2667. Squashed commit of the following: commit 7fad607ae0ae75419005707ee58312bc64fe78c5 Author: Ainar Garipov Date: Fri Feb 12 16:27:59 2021 +0300 dhcpd: assume static ip on eperm --- CHANGELOG.md | 3 +++ internal/dhcpd/{dhcphttp.go => http.go} | 16 +++++++++++++++- .../dhcpd/{dhcphttp_test.go => http_test.go} | 0 3 files changed, 18 insertions(+), 1 deletion(-) rename internal/dhcpd/{dhcphttp.go => http.go} (95%) rename internal/dhcpd/{dhcphttp_test.go => http_test.go} (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 097f5c46..23edf804 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,8 @@ and this project adheres to ### Changed +- "Permission denied" errors when checking if the machine has a static IP no + longer prevent the DHCP server from starting ([#2667]). - The server name sent by clients of TLS APIs is not only checked when `strict_sni_check` is enabled ([#2664]). - HTTP API request body size limit for the `/control/access/set` API is @@ -40,6 +42,7 @@ and this project adheres to [#2663]: https://github.com/AdguardTeam/AdGuardHome/issues/2663 [#2664]: https://github.com/AdguardTeam/AdGuardHome/issues/2664 [#2666]: https://github.com/AdguardTeam/AdGuardHome/issues/2666 +[#2667]: https://github.com/AdguardTeam/AdGuardHome/issues/2667 diff --git a/internal/dhcpd/dhcphttp.go b/internal/dhcpd/http.go similarity index 95% rename from internal/dhcpd/dhcphttp.go rename to internal/dhcpd/http.go index 0a182cf0..de155c5c 100644 --- a/internal/dhcpd/dhcphttp.go +++ b/internal/dhcpd/http.go @@ -2,6 +2,7 @@ package dhcpd import ( "encoding/json" + "errors" "fmt" "io/ioutil" "net" @@ -94,7 +95,20 @@ func (s *Server) enableDHCP(ifaceName string) (code int, err error) { var hasStaticIP bool hasStaticIP, err = sysutil.IfaceHasStaticIP(ifaceName) if err != nil { - return http.StatusInternalServerError, fmt.Errorf("checking static ip: %w", err) + // ErrPermission may happen here on Linux systems where AdGuard + // Home is installed using Snap. That doesn't necessarily mean + // that the machine doesn't have a static IP, so we can assume + // that it has and go on. If the machine doesn't, we'll get an + // error later. + // + // See https://github.com/AdguardTeam/AdGuardHome/issues/2667. + if errors.Is(err, os.ErrPermission) { + log.Info("error while checking static ip: %s; "+ + "assuming machine has static ip and going on", err) + hasStaticIP = true + } else { + return http.StatusInternalServerError, fmt.Errorf("checking static ip: %w", err) + } } if !hasStaticIP { diff --git a/internal/dhcpd/dhcphttp_test.go b/internal/dhcpd/http_test.go similarity index 100% rename from internal/dhcpd/dhcphttp_test.go rename to internal/dhcpd/http_test.go From e272e19516bff782c27bda5e15c6efb115a72d99 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Fri, 12 Feb 2021 18:56:18 +0300 Subject: [PATCH 124/141] Pull request: home: beta http server errorss are not fatal Merge in DNS/adguard-home from http-beta-no-fatal to master Squashed commit of the following: commit 13dcf7bca6d156f387639906c778fd6b07f491f3 Author: Ainar Garipov Date: Fri Feb 12 17:58:01 2021 +0300 home: beta http server errorss are not fatal --- internal/home/web.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/internal/home/web.go b/internal/home/web.go index cdd19aba..9cea9c7d 100644 --- a/internal/home/web.go +++ b/internal/home/web.go @@ -191,7 +191,10 @@ func (web *Web) Start() { WriteTimeout: web.conf.WriteTimeout, } go func() { - errs <- web.httpServerBeta.ListenAndServe() + betaErr := web.httpServerBeta.ListenAndServe() + if betaErr != nil { + log.Error("starting beta http server: %s", betaErr) + } }() } From 8a0bc5468b5173b27ab856ec636fe299a00737ad Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Mon, 15 Feb 2021 13:32:16 +0300 Subject: [PATCH 125/141] Pull request #1004: dhcpd: fix dhcpv6 status json Merge in DNS/adguard-home from 2678-json-tag to master Updates #2678. Squashed commit of the following: commit 7c272ae8830ac10b0e0154656cf472b003315349 Author: Ainar Garipov Date: Mon Feb 15 13:14:16 2021 +0300 all: doc changes commit 3c964f814f2bc92e807aad3f5bad342eb455fe28 Author: Ainar Garipov Date: Mon Feb 15 12:54:27 2021 +0300 dhcpd: fix dhcpv6 status json --- CHANGELOG.md | 7 +++++-- internal/dhcpd/server.go | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 23edf804..4a7407c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,11 +23,13 @@ and this project adheres to longer prevent the DHCP server from starting ([#2667]). - The server name sent by clients of TLS APIs is not only checked when `strict_sni_check` is enabled ([#2664]). -- HTTP API request body size limit for the `/control/access/set` API is - increased ([#2666]). +- HTTP API request body size limit for the `POST /control/access/set` HTTP API + is increased ([#2666]). ### Fixed +- The field `"range_start"` in the `GET /control/dhcp/status` HTTP API response + is now correctly named again ([#2678]). - DHCPv6 server's `ra_slaac_only` and `ra_allow_slaac` settings aren't reset to `false` on update any more ([#2653]). - The `Vary` header is now added along with `Access-Control-Allow-Origin` to @@ -43,6 +45,7 @@ and this project adheres to [#2664]: https://github.com/AdguardTeam/AdGuardHome/issues/2664 [#2666]: https://github.com/AdguardTeam/AdGuardHome/issues/2666 [#2667]: https://github.com/AdguardTeam/AdGuardHome/issues/2667 +[#2678]: https://github.com/AdguardTeam/AdGuardHome/issues/2678 diff --git a/internal/dhcpd/server.go b/internal/dhcpd/server.go index 1d3ce460..2fac533e 100644 --- a/internal/dhcpd/server.go +++ b/internal/dhcpd/server.go @@ -79,7 +79,7 @@ type V6ServerConf struct { // The first IP address for dynamic leases // The last allowed IP address ends with 0xff byte - RangeStart net.IP `yaml:"range_start"` + RangeStart net.IP `yaml:"range_start" json:"range_start"` LeaseDuration uint32 `yaml:"lease_duration" json:"lease_duration"` // in seconds From 7d1ca48ae49689766dd294e32ad6db3b2deaa52f Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Mon, 15 Feb 2021 13:56:41 +0300 Subject: [PATCH 126/141] Pull request #1005: home: imp large req handling Merge in DNS/adguard-home from 2675-larger-requests to master Updates #2675. Squashed commit of the following: commit 2b45c9bfdc817980204b11de768b425fb72a6488 Author: Ainar Garipov Date: Mon Feb 15 13:38:44 2021 +0300 home: imp names commit dad39ae7ee35346ea91f15665acc93ba0b5653df Author: Ainar Garipov Date: Mon Feb 15 13:31:53 2021 +0300 home: imp large req handling --- CHANGELOG.md | 5 ++-- internal/home/middlewares.go | 38 +++++++++++++++++++++++-------- internal/home/middlewares_test.go | 6 ++--- 3 files changed, 34 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a7407c1..775ebcc8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,8 +23,8 @@ and this project adheres to longer prevent the DHCP server from starting ([#2667]). - The server name sent by clients of TLS APIs is not only checked when `strict_sni_check` is enabled ([#2664]). -- HTTP API request body size limit for the `POST /control/access/set` HTTP API - is increased ([#2666]). +- HTTP API request body size limit for the `POST /control/access/set` and `POST + /control/filtering/set_rules` HTTP APIs is increased ([#2666], [#2675]). ### Fixed @@ -45,6 +45,7 @@ and this project adheres to [#2664]: https://github.com/AdguardTeam/AdGuardHome/issues/2664 [#2666]: https://github.com/AdguardTeam/AdGuardHome/issues/2666 [#2667]: https://github.com/AdguardTeam/AdGuardHome/issues/2667 +[#2675]: https://github.com/AdguardTeam/AdGuardHome/issues/2675 [#2678]: https://github.com/AdguardTeam/AdGuardHome/issues/2678 diff --git a/internal/home/middlewares.go b/internal/home/middlewares.go index 7d34f73e..de38c3fa 100644 --- a/internal/home/middlewares.go +++ b/internal/home/middlewares.go @@ -22,8 +22,30 @@ func withMiddlewares(h http.Handler, middlewares ...middleware) (wrapped http.Ha return wrapped } -// RequestBodySizeLimit is maximum request body length in bytes. -const RequestBodySizeLimit = 64 * 1024 +// defaultReqBodySzLim is the default maximum request body size. +const defaultReqBodySzLim = 64 * 1024 + +// largerReqBodySzLim is the maximum request body size for APIs expecting larger +// requests. +const largerReqBodySzLim = 4 * 1024 * 1024 + +// expectsLargerRequests shows if this request should use a larger body size +// limit. These are exceptions for poorly designed current APIs as well as APIs +// that are designed to expect large files and requests. Remove once the new, +// better APIs are up. +// +// See https://github.com/AdguardTeam/AdGuardHome/issues/2666 and +// https://github.com/AdguardTeam/AdGuardHome/issues/2675. +func expectsLargerRequests(r *http.Request) (ok bool) { + m := r.Method + if m != http.MethodPost { + return false + } + + p := r.URL.Path + return p == "/control/access/set" || + p == "/control/filtering/set_rules" +} // limitRequestBody wraps underlying handler h, making it's request's body Read // method limited. @@ -31,16 +53,12 @@ func limitRequestBody(h http.Handler) (limited http.Handler) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var err error - var bodySizeLimit int64 = RequestBodySizeLimit - if u := r.URL; u.Path == "/control/access/set" { - // An exception for a poorly designed API. Remove once - // the new, better API is up. - // - // See https://github.com/AdguardTeam/AdGuardHome/issues/2666. - bodySizeLimit *= 4 + var szLim int64 = defaultReqBodySzLim + if expectsLargerRequests(r) { + szLim = largerReqBodySzLim } - r.Body, err = aghio.LimitReadCloser(r.Body, bodySizeLimit) + r.Body, err = aghio.LimitReadCloser(r.Body, szLim) if err != nil { log.Error("limitRequestBody: %s", err) diff --git a/internal/home/middlewares_test.go b/internal/home/middlewares_test.go index fbd7a214..8397302b 100644 --- a/internal/home/middlewares_test.go +++ b/internal/home/middlewares_test.go @@ -14,7 +14,7 @@ import ( func TestLimitRequestBody(t *testing.T) { errReqLimitReached := &aghio.LimitReachedError{ - Limit: RequestBodySizeLimit, + Limit: defaultReqBodySzLim, } testCases := []struct { @@ -29,8 +29,8 @@ func TestLimitRequestBody(t *testing.T) { wantErr: nil, }, { name: "so_big", - body: string(make([]byte, RequestBodySizeLimit+1)), - want: make([]byte, RequestBodySizeLimit), + body: string(make([]byte, defaultReqBodySzLim+1)), + want: make([]byte, defaultReqBodySzLim), wantErr: errReqLimitReached, }, { name: "empty", From aebcd74efe5fedfcf4db7229d171471c26dd8a69 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Mon, 15 Feb 2021 14:20:23 +0300 Subject: [PATCH 127/141] Pull request: home: imp init Merge in DNS/adguard-home from fix-init to master Squashed commit of the following: commit 551c143f6c3846f061b3118a06e1c756bc3e2ba1 Author: Ainar Garipov Date: Mon Feb 15 13:45:06 2021 +0300 home: imp init --- CHANGELOG.md | 1 + internal/home/home.go | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 775ebcc8..f84a1b6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ and this project adheres to ### Fixed +- Occasional crashes during startup. - The field `"range_start"` in the `GET /control/dhcp/status` HTTP API response is now correctly named again ([#2678]). - DHCPv6 server's `ra_slaac_only` and `ra_allow_slaac` settings aren't reset to diff --git a/internal/home/home.go b/internal/home/home.go index 1b6312c8..8db077ed 100644 --- a/internal/home/home.go +++ b/internal/home/home.go @@ -304,6 +304,11 @@ func run(args options) { log.Fatalf("Can't initialize Web module") } + Context.ipDetector, err = newIPDetector() + if err != nil { + log.Fatal(err) + } + if !Context.firstRun { err := initDNSServer() if err != nil { @@ -324,11 +329,6 @@ func run(args options) { } } - Context.ipDetector, err = newIPDetector() - if err != nil { - log.Fatal(err) - } - Context.web.Start() // wait indefinitely for other go-routines to complete their job From d295621352f7dde326529a5d58bc6c9ed7db39bf Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Mon, 15 Feb 2021 15:36:38 +0300 Subject: [PATCH 128/141] Pull request: home: inc http timeouts Merge in DNS/adguard-home from 2671-timeout to master Updates #2671. Updates #2682. Squashed commit of the following: commit 79b1a36a79e3c7c26fc1a4b171feb050690f8c83 Author: Ainar Garipov Date: Mon Feb 15 15:25:26 2021 +0300 all: doc changes commit 84229b782bde433faa4ed8b71dd092965787d30e Author: Ainar Garipov Date: Mon Feb 15 15:12:33 2021 +0300 home: imp names commit b18d7b08473c99ddd37ecfa14be8d48838c2afab Author: Ainar Garipov Date: Mon Feb 15 15:04:27 2021 +0300 home: inc http timeouts --- CHANGELOG.md | 3 +++ internal/home/home.go | 6 +++--- internal/home/web.go | 15 ++++++--------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f84a1b6a..bb24fb1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ and this project adheres to ### Changed +- Increased HTTP API timeouts ([#2671], [#2682]). - "Permission denied" errors when checking if the machine has a static IP no longer prevent the DHCP server from starting ([#2667]). - The server name sent by clients of TLS APIs is not only checked when @@ -46,8 +47,10 @@ and this project adheres to [#2664]: https://github.com/AdguardTeam/AdGuardHome/issues/2664 [#2666]: https://github.com/AdguardTeam/AdGuardHome/issues/2666 [#2667]: https://github.com/AdguardTeam/AdGuardHome/issues/2667 +[#2671]: https://github.com/AdguardTeam/AdGuardHome/issues/2671 [#2675]: https://github.com/AdguardTeam/AdGuardHome/issues/2675 [#2678]: https://github.com/AdguardTeam/AdGuardHome/issues/2678 +[#2682]: https://github.com/AdguardTeam/AdGuardHome/issues/2682 diff --git a/internal/home/home.go b/internal/home/home.go index 8db077ed..422312a6 100644 --- a/internal/home/home.go +++ b/internal/home/home.go @@ -295,9 +295,9 @@ func run(args options) { BindPort: config.BindPort, BetaBindPort: config.BetaBindPort, - ReadTimeout: ReadTimeout, - ReadHeaderTimeout: ReadHeaderTimeout, - WriteTimeout: WriteTimeout, + ReadTimeout: readTimeout, + ReadHeaderTimeout: readHdrTimeout, + WriteTimeout: writeTimeout, } Context.web = CreateWeb(&webConf) if Context.web == nil { diff --git a/internal/home/web.go b/internal/home/web.go index 9cea9c7d..fff2aa07 100644 --- a/internal/home/web.go +++ b/internal/home/web.go @@ -16,17 +16,14 @@ import ( ) const ( - // ReadTimeout is the maximum duration for reading the entire request, + // readTimeout is the maximum duration for reading the entire request, // including the body. - ReadTimeout = 10 * time.Second - - // ReadHeaderTimeout is the amount of time allowed to read request - // headers. - ReadHeaderTimeout = 10 * time.Second - - // WriteTimeout is the maximum duration before timing out writes of the + readTimeout = 30 * time.Second + // readHdrTimeout is the amount of time allowed to read request headers. + readHdrTimeout = 30 * time.Second + // writeTimeout is the maximum duration before timing out writes of the // response. - WriteTimeout = 10 * time.Second + writeTimeout = 30 * time.Second ) type webConfig struct { From ab81ff03f6b1f624f5b70e2f750c502daa4efefb Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Mon, 15 Feb 2021 15:53:24 +0300 Subject: [PATCH 129/141] Pull request: all: upd translations Merge in DNS/adguard-home from upd-locales to master Updates #2643. Squashed commit of the following: commit d7f26aaa63bfc9307887301abfff4779be7eba41 Author: Ainar Garipov Date: Thu Feb 11 16:18:54 2021 +0300 all: upd translations --- client/src/__locales/be.json | 11 + client/src/__locales/cs.json | 1 + client/src/__locales/da.json | 1 + client/src/__locales/de.json | 1 + client/src/__locales/en.json | 2 +- client/src/__locales/es.json | 17 +- client/src/__locales/fr.json | 1 + client/src/__locales/hr.json | 11 + client/src/__locales/it.json | 1 + client/src/__locales/ja.json | 1 + client/src/__locales/ko.json | 1 + client/src/__locales/nl.json | 1 + client/src/__locales/no.json | 4 + client/src/__locales/pl.json | 1 + client/src/__locales/pt-br.json | 1 + client/src/__locales/pt-pt.json | 1 + client/src/__locales/ro.json | 1 + client/src/__locales/ru.json | 1 + client/src/__locales/si-lk.json | 12 +- client/src/__locales/sk.json | 1 + client/src/__locales/sl.json | 1 + client/src/__locales/tr.json | 1 + client/src/__locales/vi.json | 1 + client/src/__locales/zh-cn.json | 1 + client/src/__locales/zh-tw.json | 11 +- scripts/translations/package-lock.json | 468 ++++++++++++++++++++++++- 26 files changed, 533 insertions(+), 21 deletions(-) diff --git a/client/src/__locales/be.json b/client/src/__locales/be.json index e2d35a79..b89af602 100644 --- a/client/src/__locales/be.json +++ b/client/src/__locales/be.json @@ -32,6 +32,7 @@ "form_error_ip_format": "Няслушны фармат IP-адраса", "form_error_mac_format": "Некарэктны фармат MAC", "form_error_client_id_format": "Няслушны фармат ID кліента", + "form_error_server_name": "Няслушнае імя сервера", "form_error_positive": "Павінна быць больш 0", "form_error_negative": "Павінна быць не менш 0", "range_end_error": "Павінен перавышаць пачатак дыяпазону", @@ -247,10 +248,16 @@ "custom_ip": "Свой IP", "blocking_ipv4": "Блакаванне IPv4", "blocking_ipv6": "Блакаванне IPv6", + "dnscrypt": "DNSCrypt", "dns_over_https": "DNS-over-HTTPS", "dns_over_tls": "DNS-over-TLS", + "dns_over_quic": "DNS-over-QUIC", + "client_id": "Ідэнтыфікатар кліента", + "client_id_placeholder": "Увядзіце ідэнтыфікатар кліента", + "client_id_desc": "Розныя кліенты могуць ідэнтыфікавацца па адмысловым ідэнтыфікатары кліента. Тут вы можаце даведацца больш пра ідэнтыфікацыю кліентаў.", "download_mobileconfig_doh": "Спампаваць .mobileconfig для DNS-over-HTTPS", "download_mobileconfig_dot": "Спампаваць .mobileconfig для DNS-over-TLS", + "download_mobileconfig": "Загрузіць файл канфігурацыі", "plain_dns": "Нешыфраваны DNS", "form_enter_rate_limit": "Увядзіце rate limit", "rate_limit": "Ограничение скорости", @@ -269,6 +276,7 @@ "source_label": "Крыніца", "found_in_known_domain_db": "Знойдзены ў базе вядомых даменаў.", "category_label": "Катэгорыя", + "rule_label": "Правіла(ы)", "list_label": "Спіс", "unknown_filter": "Невядомы фільтр {{filterId}}", "known_tracker": "Вядомы трэкер", @@ -329,6 +337,7 @@ "encryption_config_saved": "Налады шыфравання захаваны", "encryption_server": "Імя сервера", "encryption_server_enter": "Увядзіце ваша даменавае імя", + "encryption_server_desc": "Для выкарыстання HTTPS вам трэба ўвесці імя сервера, якое падыходзіць вашаму SSL-сертыфікату.", "encryption_redirect": "Аўтаматычна перанакіроўваць на HTTPS", "encryption_redirect_desc": "Калі ўлучана, AdGuard Home будзе аўтаматычна перанакіроўваць вас з HTTP на HTTPS адрас.", "encryption_https": "Порт HTTPS", @@ -384,6 +393,7 @@ "client_edit": "Рэдагаваць кліента", "client_identifier": "Ідэнтыфікатар", "ip_address": "IP-адрас", + "client_identifier_desc": "Кліенты могуць быць ідэнтыфікаваны па IP-адрасе, CIDR ці MAC-адрасу. Звярніце ўвагу, што выкарыстанне MAC як ідэнтыфікатара магчыма, толькі калі AdGuard Home таксама з'яўляецца і <0>DHCP-серверам", "form_enter_ip": "Увядзіце IP", "form_enter_mac": "Увядзіце MAC", "form_enter_id": "Увядзіце ідэнтыфікатар", @@ -427,6 +437,7 @@ "setup_dns_privacy_other_3": "<0>dnscrypt-proxy падтрымвае <1>DNS-over-HTTPS.", "setup_dns_privacy_other_4": "<0>Mozilla Firefox падтрымвае <1>DNS-over-HTTPS.", "setup_dns_privacy_other_5": "Вы можаце знайсці яшчэ варыянты <0>тут і <1>тут.", + "setup_dns_privacy_ioc_mac": "Канфігурацыя для iOS і macOS", "setup_dns_notice": "Каб выкарыстоўваць <1>DNS-over-HTTPS ці <1>DNS-over-TLS, вам патрэбна <0>наладзіць шыфраванне у наладах AdGuard Home.", "rewrite_added": "Правіла перанакіравання DNS для \"{{key}}\" паспяхова дададзена", "rewrite_deleted": "Правіла перанакіравання DNS для \"{{key}}\" паспяхова выдалена", diff --git a/client/src/__locales/cs.json b/client/src/__locales/cs.json index ca5d8a5c..84cf3e32 100644 --- a/client/src/__locales/cs.json +++ b/client/src/__locales/cs.json @@ -248,6 +248,7 @@ "custom_ip": "Vlastní IP", "blocking_ipv4": "Blokování IPv4", "blocking_ipv6": "Blokování IPv6", + "dnscrypt": "DNSCrypt", "dns_over_https": "DNS přes HTTPS", "dns_over_tls": "DNS přes TLS", "dns_over_quic": "DNS skrze QUIC", diff --git a/client/src/__locales/da.json b/client/src/__locales/da.json index 97105ad3..3ba7f9b4 100644 --- a/client/src/__locales/da.json +++ b/client/src/__locales/da.json @@ -248,6 +248,7 @@ "custom_ip": "Tilpasset IP", "blocking_ipv4": "IPv4-blokering", "blocking_ipv6": "IPv6-blokering", + "dnscrypt": "DNSCrypt", "dns_over_https": "DNS-over-HTTPS", "dns_over_tls": "DNS-over-TLS", "dns_over_quic": "DNS-over-Quic", diff --git a/client/src/__locales/de.json b/client/src/__locales/de.json index 7e35661e..17d5832e 100644 --- a/client/src/__locales/de.json +++ b/client/src/__locales/de.json @@ -248,6 +248,7 @@ "custom_ip": "Benutzerdefinierte IP", "blocking_ipv4": "IPv4-Sperren", "blocking_ipv6": "IPv6-Sperren", + "dnscrypt": "DNSCrypt", "dns_over_https": "DNS-over-HTTPS (DNS-Abrage über HTTPS)", "dns_over_tls": "DNS-over-TLS (DNS-Abrage über TLS)", "dns_over_quic": "DNS-over-QUIC", diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json index b558dd60..3b0aa663 100644 --- a/client/src/__locales/en.json +++ b/client/src/__locales/en.json @@ -594,4 +594,4 @@ "adg_will_drop_dns_queries": "AdGuard Home will be dropping all DNS queries from this client.", "client_not_in_allowed_clients": "The client is not allowed because it is not in the \"Allowed clients\" list.", "experimental": "Experimental" -} +} \ No newline at end of file diff --git a/client/src/__locales/es.json b/client/src/__locales/es.json index 3be25fac..cf0d7d02 100644 --- a/client/src/__locales/es.json +++ b/client/src/__locales/es.json @@ -248,15 +248,16 @@ "custom_ip": "IP personalizada", "blocking_ipv4": "Bloqueo de IPv4", "blocking_ipv6": "Bloqueo de IPv6", + "dnscrypt": "DNSCrypt", "dns_over_https": "DNS mediante HTTPS", "dns_over_tls": "DNS mediante TLS", - "dns_over_quic": "DNS-over-QUIC", + "dns_over_quic": "DNS mediante QUIC", "client_id": "ID de cliente", - "client_id_placeholder": "Ingresa tu ID de cliente", - "client_id_desc": "Varios clientes se pueden identificar mediante un ID de cliente especial. Aquí puede aprender más sobre cómo identificar clientes.", + "client_id_placeholder": "Ingresa el ID del cliente", + "client_id_desc": "Diferentes clientes pueden ser identificados por un ID de cliente especial. Aquí puedes obtener más información sobre cómo identificar clientes.", "download_mobileconfig_doh": "Descargar .mobileconfig para DNS mediante HTTPS", "download_mobileconfig_dot": "Descargar .mobileconfig para DNS mediante TLS", - "download_mobileconfig": "Descargar el archivo de configuración", + "download_mobileconfig": "Descargar archivo de configuración", "plain_dns": "DNS simple", "form_enter_rate_limit": "Ingresa el límite de cantidad", "rate_limit": "Límite de cantidad", @@ -275,7 +276,7 @@ "source_label": "Fuente", "found_in_known_domain_db": "Encontrado en la base de datos de dominios conocidos.", "category_label": "Categoría", - "rule_label": "Regla(s)", + "rule_label": "Regla", "list_label": "Lista", "unknown_filter": "Filtro desconocido {{filterId}}", "known_tracker": "Rastreador conocido", @@ -336,7 +337,7 @@ "encryption_config_saved": "Configuración de cifrado guardado", "encryption_server": "Nombre del servidor", "encryption_server_enter": "Ingresa el nombre del dominio", - "encryption_server_desc": "Para utilizar HTTPS, debes ingresar el nombre del servidor que coincida con tu certificado SSL o certificado Wildcard. Si el campo no está establecido, el servidor aceptará conexiones TLS para cualquier dominio.", + "encryption_server_desc": "Para utilizar HTTPS, debes ingresar el nombre del servidor que coincida con tu certificado SSL o certificado comodín. Si el campo no está establecido, el servidor aceptará conexiones TLS para cualquier dominio.", "encryption_redirect": "Redireccionar a HTTPS automáticamente", "encryption_redirect_desc": "Si está marcado, AdGuard Home redireccionará automáticamente de HTTP a las direcciones HTTPS.", "encryption_https": "Puerto HTTPS", @@ -392,7 +393,7 @@ "client_edit": "Editar cliente", "client_identifier": "Identificador", "ip_address": "Dirección IP", - "client_identifier_desc": "Los clientes pueden ser identificados por la dirección IP, MAC, CIDR o un especial ID de cliente (puede ser utilizado para DoT/DoH/DoQ). <0>Aquí puede obtener más información sobre cómo identificar clientes.", + "client_identifier_desc": "Los clientes pueden ser identificados por la dirección IP, MAC, CIDR o un ID de cliente especial (puede ser utilizado para DoT/DoH/DoQ). <0>Aquí puedes obtener más información sobre cómo identificar clientes.", "form_enter_ip": "Ingresa la IP", "form_enter_mac": "Ingresa la MAC", "form_enter_id": "Ingresa el identificador", @@ -436,7 +437,7 @@ "setup_dns_privacy_other_3": "<0>dnscrypt-proxy soporta <1>DNS mediante HTTPS.", "setup_dns_privacy_other_4": "<0>Mozilla Firefox soporta <1>DNS mediante HTTPS.", "setup_dns_privacy_other_5": "Encontrarás más implementaciones <0>aquí y <1>aquí.", - "setup_dns_privacy_ioc_mac": "La configuración de iOS y macOS ", + "setup_dns_privacy_ioc_mac": "Configuración de iOS y macOS", "setup_dns_notice": "Para utilizar <1>DNS mediante HTTPS o <1>DNS mediante TLS, debes <0>configurar el cifrado en la configuración de AdGuard Home.", "rewrite_added": "Reescritura DNS para \"{{key}}\" añadido correctamente", "rewrite_deleted": "Reescritura DNS para \"{{key}}\" eliminado correctamente", diff --git a/client/src/__locales/fr.json b/client/src/__locales/fr.json index 36ec94e7..064f4ccc 100644 --- a/client/src/__locales/fr.json +++ b/client/src/__locales/fr.json @@ -248,6 +248,7 @@ "custom_ip": "IP personnalisée", "blocking_ipv4": "Blocage IPv4", "blocking_ipv6": "Blocage IPv6", + "dnscrypt": "DNSCrypt", "dns_over_https": "DNS-over-HTTPS", "dns_over_tls": "DNS-over-TLS", "dns_over_quic": "DNS-over-QUIC", diff --git a/client/src/__locales/hr.json b/client/src/__locales/hr.json index 7b02b4d0..ab5567f0 100644 --- a/client/src/__locales/hr.json +++ b/client/src/__locales/hr.json @@ -32,6 +32,7 @@ "form_error_ip_format": "Nevažeći format IP adrese", "form_error_mac_format": "Nevažeći MAC format", "form_error_client_id_format": "Nevažeći format ID-a klijenta", + "form_error_server_name": "Nevažeće ime poslužitelja", "form_error_positive": "Mora biti veće od 0", "form_error_negative": "Mora biti jednako ili veće od 0", "range_end_error": "Mora biti veće od početne vrijednosti raspona", @@ -247,10 +248,16 @@ "custom_ip": "Prilagođen IP", "blocking_ipv4": "Blokiranje IPv4", "blocking_ipv6": "Blokiranje IPv6", + "dnscrypt": "DNSCrypt", "dns_over_https": "DNS-over-HTTPS", "dns_over_tls": "DNS-over-TLS", + "dns_over_quic": "DNS-over-Quic", + "client_id": "ID klijenta", + "client_id_placeholder": "Unesite ID klijenta", + "client_id_desc": "Razni klijenti mogu biti prepoznati po specijalnom identifikatoru. Ovdje možete saznati više kako možete identificirati klijente.", "download_mobileconfig_doh": "Preuzmi .mobileconfig za DNS-over-HTTPS", "download_mobileconfig_dot": "Preuzmi .mobileconfig za DNS-over-TLS", + "download_mobileconfig": "Preuzmite konfiguracijsku datoteku", "plain_dns": "Obični DNS", "form_enter_rate_limit": "Unesite ograničenje", "rate_limit": "Ograničenje", @@ -269,6 +276,7 @@ "source_label": "Izvor", "found_in_known_domain_db": "Pronađeno u bazi poznatih domena.", "category_label": "Kategorija", + "rule_label": "Pravilo", "list_label": "Popis", "unknown_filter": "Nepoznati filtar {{filterId}}", "known_tracker": "Poznati pratitelj", @@ -329,6 +337,7 @@ "encryption_config_saved": "Spremljene postavke šifriranja", "encryption_server": "Naziv poslužitelja", "encryption_server_enter": "Unesite naziv domene", + "encryption_server_desc": "Kako biste koristili HTTPS, morate unijeti naziv poslužitelja koji odgovara vašem SSL certifikatu.", "encryption_redirect": "Automatski preusmjeri na HTTPS", "encryption_redirect_desc": "Ako je omogućeno, AdGuard Home će vas automatski preusmjeravati s HTTP na HTTPS adrese.", "encryption_https": "HTTPS port", @@ -384,6 +393,7 @@ "client_edit": "Uredi klijenta", "client_identifier": "Identifikator", "ip_address": "IP adresa", + "client_identifier_desc": "Klijenti se mogu prepoznati po IP adresi, CIDR-u ili MAC adresi. Imajte na umu da je upotreba MAC-a kao identifikatora, moguća samo ako je AdGuard Home također i <0>DHCP poslužitelj", "form_enter_ip": "Unesite IP adresu", "form_enter_mac": "Unesite MAC adresu", "form_enter_id": "Unesi identifikator", @@ -427,6 +437,7 @@ "setup_dns_privacy_other_3": "<0>dnscrypt-proxy podržava <1>DNS-over-HTTPS.", "setup_dns_privacy_other_4": "<0>Mozilla Firefox podržava <1>DNS-over-HTTPS.", "setup_dns_privacy_other_5": "Možete pronaći više implementacija <0>ovdje i <1>ovdje.", + "setup_dns_privacy_ioc_mac": "konfiguracija za iOS i macOS", "setup_dns_notice": "Da biste koristili <1>DNS-over-HTTPS ili <1>DNS-over-TLS, morate <0>postaviti šifriranje u AdGuard Home postavkama.", "rewrite_added": "DNS prijepis za \"{{key}}\" je uspješno dodan", "rewrite_deleted": "DNS prijepis za \"{{key}}\" je uspješno uklonjen", diff --git a/client/src/__locales/it.json b/client/src/__locales/it.json index 5079165b..1f3a0207 100644 --- a/client/src/__locales/it.json +++ b/client/src/__locales/it.json @@ -248,6 +248,7 @@ "custom_ip": "IP personalizzato", "blocking_ipv4": "Blocca IPv4", "blocking_ipv6": "Blocca IPv6", + "dnscrypt": "DNSCrypt", "dns_over_https": "DNS su HTTPS", "dns_over_tls": "DNS su TLS", "dns_over_quic": "DNS su Quic", diff --git a/client/src/__locales/ja.json b/client/src/__locales/ja.json index 6c4ef890..7eb45326 100644 --- a/client/src/__locales/ja.json +++ b/client/src/__locales/ja.json @@ -248,6 +248,7 @@ "custom_ip": "カスタムIP", "blocking_ipv4": "ブロック中のIPv4", "blocking_ipv6": "ブロック中のIPv6", + "dnscrypt": "DNSCrypt", "dns_over_https": "DNS-over-HTTPS", "dns_over_tls": "DNS-over-TLS", "dns_over_quic": "DNS-over-QUIC", diff --git a/client/src/__locales/ko.json b/client/src/__locales/ko.json index c266c8c4..2f25cbc6 100644 --- a/client/src/__locales/ko.json +++ b/client/src/__locales/ko.json @@ -248,6 +248,7 @@ "custom_ip": "사용자 지정 IP", "blocking_ipv4": "IPv4 차단", "blocking_ipv6": "IPv6 차단", + "dnscrypt": "DNSCrypt", "dns_over_https": "DNS-over-HTTPS", "dns_over_tls": "DNS-over-TLS", "dns_over_quic": "DNS-over-QUIC", diff --git a/client/src/__locales/nl.json b/client/src/__locales/nl.json index d5be0f99..b903ced6 100644 --- a/client/src/__locales/nl.json +++ b/client/src/__locales/nl.json @@ -248,6 +248,7 @@ "custom_ip": "Aangepast IP", "blocking_ipv4": "Blokkeren IP4", "blocking_ipv6": "Blokkeren IP6", + "dnscrypt": "DNSCrypt", "dns_over_https": "DNS-via-HTTPS", "dns_over_tls": "DNS-via-TLS", "dns_over_quic": "DNS-via-QUIC", diff --git a/client/src/__locales/no.json b/client/src/__locales/no.json index 58637b4a..dde12de0 100644 --- a/client/src/__locales/no.json +++ b/client/src/__locales/no.json @@ -32,6 +32,7 @@ "form_error_ip_format": "Ugyldig IPv4-format", "form_error_mac_format": "Ugyldig MAC-format", "form_error_client_id_format": "Ugyldig ID-klientformat", + "form_error_server_name": "Ugyldig tjenernavn", "form_error_positive": "Må være høyere enn 0", "form_error_negative": "Må være ≥0", "range_end_error": "Må være høyere enn rekkeviddens start", @@ -247,8 +248,11 @@ "custom_ip": "Tilpasset IP", "blocking_ipv4": "IPv4-blokkering", "blocking_ipv6": "IPv6-blokkering", + "dnscrypt": "DNSCrypt", "dns_over_https": "DNS-over-HTTPS", "dns_over_tls": "DNS-over-TLS", + "dns_over_quic": "DNS-over-QUIC", + "client_id": "Klient-ID", "download_mobileconfig_doh": "Last ned .mobileconfig for DNS-over-HTTPS", "download_mobileconfig_dot": "Last ned .mobileconfig for DNS-over-TLS", "plain_dns": "Ordinær DNS", diff --git a/client/src/__locales/pl.json b/client/src/__locales/pl.json index 55f172b3..e44ef6d4 100644 --- a/client/src/__locales/pl.json +++ b/client/src/__locales/pl.json @@ -248,6 +248,7 @@ "custom_ip": "Niestandardowy adres IP", "blocking_ipv4": "Blokowanie IPv4", "blocking_ipv6": "Blokowanie IPv6", + "dnscrypt": "DNSCrypt", "dns_over_https": "DNS-over-HTTPS", "dns_over_tls": "DNS-over-TLS", "dns_over_quic": "DNS-over-QUIC", diff --git a/client/src/__locales/pt-br.json b/client/src/__locales/pt-br.json index 01a3a933..844aa1b6 100644 --- a/client/src/__locales/pt-br.json +++ b/client/src/__locales/pt-br.json @@ -248,6 +248,7 @@ "custom_ip": "IP personalizado", "blocking_ipv4": "Bloqueando IPv4", "blocking_ipv6": "Bloqueando IPv6", + "dnscrypt": "DNSCrypt", "dns_over_https": "DNS-sobre-HTTPS", "dns_over_tls": "DNS-sobre-TLS", "dns_over_quic": "DNS-sobre-QUIC", diff --git a/client/src/__locales/pt-pt.json b/client/src/__locales/pt-pt.json index e8c70cc9..8e08db07 100644 --- a/client/src/__locales/pt-pt.json +++ b/client/src/__locales/pt-pt.json @@ -248,6 +248,7 @@ "custom_ip": "IP Personalizado", "blocking_ipv4": "A bloquear IPv4", "blocking_ipv6": "A bloquear IPv6", + "dnscrypt": "DNSCrypt", "dns_over_https": "DNS-sobre-HTTPS", "dns_over_tls": "DNS-sobre-TLS", "dns_over_quic": "DNS-sobre-QUIC", diff --git a/client/src/__locales/ro.json b/client/src/__locales/ro.json index 6523bd30..80929301 100644 --- a/client/src/__locales/ro.json +++ b/client/src/__locales/ro.json @@ -248,6 +248,7 @@ "custom_ip": "IP personalizat", "blocking_ipv4": "Blocarea IPv4", "blocking_ipv6": "Blocarea IPv6", + "dnscrypt": "DNSCrypt", "dns_over_https": "DNS-over-HTTPS", "dns_over_tls": "DNS-over-TLS", "dns_over_quic": "DNS-over-QUIC", diff --git a/client/src/__locales/ru.json b/client/src/__locales/ru.json index ea58c7f3..1b9f0a39 100644 --- a/client/src/__locales/ru.json +++ b/client/src/__locales/ru.json @@ -248,6 +248,7 @@ "custom_ip": "Свой IP", "blocking_ipv4": "Блокировка IPv4", "blocking_ipv6": "Блокировка IPv6", + "dnscrypt": "DNSCrypt", "dns_over_https": "DNS-over-HTTPS", "dns_over_tls": "DNS-over-TLS", "dns_over_quic": "DNS-over-QUIC", diff --git a/client/src/__locales/si-lk.json b/client/src/__locales/si-lk.json index 015c477c..42909355 100644 --- a/client/src/__locales/si-lk.json +++ b/client/src/__locales/si-lk.json @@ -119,7 +119,7 @@ "enabled_save_search_toast": "ආරක්ෂිත සෙවීම සබල කර ඇත", "enabled_table_header": "සබල කර ඇත", "name_table_header": "නම", - "list_url_table_header": "URL ලැයිස්තුව", + "list_url_table_header": "ඒ.ස.නි.(URL) ලැයිස්තුව", "rules_count_table_header": "නීති ගණන", "last_time_updated_table_header": "අවසන් වරට යාවත්කාලීන කරන ලද", "actions_table_header": "ක්‍රියාමාර්ග", @@ -134,7 +134,7 @@ "add_allowlist": "අවසර දීමේ ලැයිස්තුවක් එකතු කරන්න", "cancel_btn": "අහෝසි කරන්න", "enter_name_hint": "නම ඇතුළත් කරන්න", - "enter_url_or_path_hint": "ලැයිස්තුවක URL හෝ ස්ථීර මාර්ගයක් ඇතුළත් කරන්න", + "enter_url_or_path_hint": "ලැයිස්තුවක ඒ.ස.නි.(URL) හෝ ස්ථීර මාර්ගයක් ඇතුළත් කරන්න", "check_updates_btn": "යාවත්කාල පරීක්ෂා කරන්න", "new_blocklist": "නව අවහිර කිරී‌‌‌‌‌මේ ලැයිස්තුව", "new_allowlist": "නව අවසර දීමේ ලැයිස්තුව", @@ -142,10 +142,10 @@ "edit_allowlist": "අවසර දීමේ ලැයිස්තුව සංස්කරණය කරන්න", "choose_blocklist": "අවහිර කීරීමේ ලැයිස්තුවක් තෝරන්න", "choose_allowlist": "අනවහිර කීරීමේ ලැයිස්තුවක් තෝරන්න", - "enter_valid_blocklist": "අවහිර කිරී‌‌‌‌‌මේ ලැයිස්තුවට වලංගු URL ලිපිනයක් ඇතුළත් කරන්න.", - "enter_valid_allowlist": "අවසර දීමේ ලැයිස්තුවට වලංගු URL ලිපිනයක් ඇතුළත් කරන්න.", - "form_error_url_format": "වලංගු නොවන URL ආකෘතියකි", - "form_error_url_or_path_format": "ලැයිස්තුවක වලංගු නොවන URL හෝ ස්ථීර මාර්ගයකි", + "enter_valid_blocklist": "අවහිර කිරී‌‌‌‌‌මේ ලැයිස්තුවට වලංගු ඒ.ස.නි.(URL) ලිපිනයක් ඇතුළත් කරන්න.", + "enter_valid_allowlist": "අවසර දීමේ ලැයිස්තුවට වලංගු ඒ.ස.නි.(URL) ලිපිනයක් ඇතුළත් කරන්න.", + "form_error_url_format": "වලංගු නොවන ඒ.ස.නි.(URL) ආකෘතියකි", + "form_error_url_or_path_format": "ලැයිස්තුවක වලංගු නොවන ඒ.ස.නි.(URL) හෝ ස්ථීර මාර්ගයකි", "custom_filter_rules": "අභිරුචි පෙරීමේ නීති", "custom_filter_rules_hint": "පේළියකට එක් නීතියක් බැගින් ඇතුළත් කරන්න. ඔබට දැන්වීම් අවහිර කිරීමේ නීති හෝ ධාරක ගොනු පද ගැලපුම් භාවිතා කළ හැකිය.", "examples_title": "උදාහරණ", diff --git a/client/src/__locales/sk.json b/client/src/__locales/sk.json index af88853a..7c0d4fa9 100644 --- a/client/src/__locales/sk.json +++ b/client/src/__locales/sk.json @@ -248,6 +248,7 @@ "custom_ip": "Vlastná IP adresa", "blocking_ipv4": "Blokovanie IPv4", "blocking_ipv6": "Blokovanie IPv6", + "dnscrypt": "DNSCrypt", "dns_over_https": "DNS-over-HTTPS", "dns_over_tls": "DNS-over-TLS", "dns_over_quic": "DNS-over-QUIC", diff --git a/client/src/__locales/sl.json b/client/src/__locales/sl.json index 4a35e53a..007283bb 100644 --- a/client/src/__locales/sl.json +++ b/client/src/__locales/sl.json @@ -248,6 +248,7 @@ "custom_ip": "IP po meri", "blocking_ipv4": "Onemogočanje IPv4", "blocking_ipv6": "Onemogočanje IPv6", + "dnscrypt": "DNSCrypt", "dns_over_https": "DNS-prek-HTTPS", "dns_over_tls": "DNS-prek-TLS", "dns_over_quic": "DNS-prek-QIUC", diff --git a/client/src/__locales/tr.json b/client/src/__locales/tr.json index 0276740d..04b06729 100644 --- a/client/src/__locales/tr.json +++ b/client/src/__locales/tr.json @@ -248,6 +248,7 @@ "custom_ip": "Özel IP", "blocking_ipv4": "IPv4 engelleme", "blocking_ipv6": "IPv6 engelleme", + "dnscrypt": "DNSCrypt", "dns_over_https": "DNS-over-HTTPS", "dns_over_tls": "DNS-over-TLS", "dns_over_quic": "DNS-over-QUIC", diff --git a/client/src/__locales/vi.json b/client/src/__locales/vi.json index 85a5df2f..368b0f9a 100644 --- a/client/src/__locales/vi.json +++ b/client/src/__locales/vi.json @@ -248,6 +248,7 @@ "custom_ip": "IP tuỳ chỉnh", "blocking_ipv4": "Chặn IPv4", "blocking_ipv6": "Chặn IPv6", + "dnscrypt": "DNSCrypt", "dns_over_https": "DNS-over-HTTPS", "dns_over_tls": "DNS-over-TLS", "dns_over_quic": "DNS-over-QUIC", diff --git a/client/src/__locales/zh-cn.json b/client/src/__locales/zh-cn.json index 54b72644..edf26ede 100644 --- a/client/src/__locales/zh-cn.json +++ b/client/src/__locales/zh-cn.json @@ -248,6 +248,7 @@ "custom_ip": "自定义 IP", "blocking_ipv4": "拦截 IPv4", "blocking_ipv6": "拦截 IPv6", + "dnscrypt": "DNSCrypt", "dns_over_https": "DNS-over-HTTPS", "dns_over_tls": "DNS-over-TLS", "dns_over_quic": "DNS-over-QUIC", diff --git a/client/src/__locales/zh-tw.json b/client/src/__locales/zh-tw.json index 258c661c..84df5c3e 100644 --- a/client/src/__locales/zh-tw.json +++ b/client/src/__locales/zh-tw.json @@ -1,6 +1,6 @@ { "client_settings": "用戶端設定", - "example_upstream_reserved": "您可以<0>為特定網域指定上游 DNS", + "example_upstream_reserved": "您可<0>對於特定的網域明確指定 DNS 上游", "example_upstream_comment": "您可明確指定註解", "upstream_parallel": "透過同時地查詢所有上游的伺服器,使用並行的查詢以加速解析網域", "parallel_requests": "並行的請求", @@ -32,6 +32,7 @@ "form_error_ip_format": "無效的 IP 格式", "form_error_mac_format": "無效的媒體存取控制(MAC)格式", "form_error_client_id_format": "無效的用戶端 ID 格式", + "form_error_server_name": "無效的伺服器名稱", "form_error_positive": "必須大於 0", "form_error_negative": "必須等於或大於 0", "range_end_error": "必須大於起始範圍", @@ -247,10 +248,16 @@ "custom_ip": "自訂的 IP", "blocking_ipv4": "封鎖 IPv4", "blocking_ipv6": "封鎖 IPv6", + "dnscrypt": "DNSCrypt", "dns_over_https": "DNS-over-HTTPS", "dns_over_tls": "DNS-over-TLS", + "dns_over_quic": "DNS-over-QUIC", + "client_id": "用戶端 ID", + "client_id_placeholder": "輸入用戶端 ID", + "client_id_desc": "不同的用戶端可根據特殊的用戶端 ID 被識別。<0>於此,您可了解更多關於如何識別用戶端。", "download_mobileconfig_doh": "下載用於 DNS-over-HTTPS 的 .mobileconfig", "download_mobileconfig_dot": "下載用於 DNS-over-TLS 的 .mobileconfig", + "download_mobileconfig": "下載配置檔案", "plain_dns": "一般的 DNS", "form_enter_rate_limit": "輸入速率限制", "rate_limit": "速率限制", @@ -386,6 +393,7 @@ "client_edit": "編輯用戶端", "client_identifier": "識別碼", "ip_address": "IP 位址", + "client_identifier_desc": "用戶端可根據 IP 位址、無類別網域間路由(CIDR)、媒體存取控制(MAC)位址或特殊的用戶端 ID(可被用於 DoT/DoH/DoQ)被識別。<0>於此,您可了解更多關於如何識別用戶端。", "form_enter_ip": "輸入 IP", "form_enter_mac": "輸入媒體存取控制(MAC)", "form_enter_id": "輸入識別碼", @@ -429,6 +437,7 @@ "setup_dns_privacy_other_3": "<0>dnscrypt-proxy 支援 <1>DNS-over-HTTPS。", "setup_dns_privacy_other_4": "<0>Mozilla Firefox 支援 <1>DNS-over-HTTPS。", "setup_dns_privacy_other_5": "在<0>這裡和<1>這裡,您將發現更多的執行。", + "setup_dns_privacy_ioc_mac": "iOS 和 macOS 配置", "setup_dns_notice": "為了使用 <1>DNS-over-HTTPS 或 <1>DNS-over-TLS,您需要在 AdGuard Home 設定裡<0>配置加密。", "rewrite_added": "對於 \"{{key}}\" 之 DNS 改寫被成功地加入", "rewrite_deleted": "對於 \"{{key}}\" 之 DNS 改寫被成功地刪除", diff --git a/scripts/translations/package-lock.json b/scripts/translations/package-lock.json index 5361d859..42f5d23a 100644 --- a/scripts/translations/package-lock.json +++ b/scripts/translations/package-lock.json @@ -1,8 +1,466 @@ { "name": "translations", - "version": "0.1.0", - "lockfileVersion": 1, + "version": "0.2.0", + "lockfileVersion": 2, "requires": true, + "packages": { + "": { + "version": "0.2.0", + "dependencies": { + "request": "^2.88.0", + "request-promise": "^4.2.2" + } + }, + "node_modules/ajv": { + "version": "6.5.5", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.5.tgz", + "integrity": "sha512-7q7gtRQDJSyuEHjuVgHoUa2VuemFiCMrfQc9Tc08XTAc4Zj/5U1buQJ0HU6i7fKjXU09SVgSmxa4sLvuvS8Iyg==", + "dependencies": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "node_modules/asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "node_modules/aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "engines": { + "node": "*" + } + }, + "node_modules/aws4": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" + }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, + "node_modules/bluebird": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.3.tgz", + "integrity": "sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw==" + }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "node_modules/combined-stream": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", + "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "node_modules/extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "engines": [ + "node >=0.6.0" + ] + }, + "node_modules/fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + }, + "node_modules/forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "engines": { + "node": "*" + } + }, + "node_modules/form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dependencies": { + "assert-plus": "^1.0.0" + } + }, + "node_modules/har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "engines": { + "node": ">=4" + } + }, + "node_modules/har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "dependencies": { + "ajv": "^6.5.5", + "har-schema": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.3.7" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + }, + "node_modules/json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "node_modules/jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "node_modules/lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" + }, + "node_modules/mime-db": { + "version": "1.37.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz", + "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.21", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz", + "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==", + "dependencies": { + "mime-db": "~1.37.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "engines": { + "node": "*" + } + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "node_modules/psl": { + "version": "1.1.29", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.29.tgz", + "integrity": "sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ==" + }, + "node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + }, + "node_modules/qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/request": { + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/request-promise": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.2.2.tgz", + "integrity": "sha1-0epG1lSm7k+O5qT+oQGMIpEZBLQ=", + "dependencies": { + "bluebird": "^3.5.0", + "request-promise-core": "1.1.1", + "stealthy-require": "^1.1.0", + "tough-cookie": ">=2.3.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/request-promise-core": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.1.tgz", + "integrity": "sha1-Pu4AssWqgyOc+wTFcA2jb4HNCLY=", + "dependencies": { + "lodash": "^4.13.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/sshpk": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.15.2.tgz", + "integrity": "sha512-Ra/OXQtuh0/enyl4ETZAfTaeksa6BXks5ZcjpSUNrjBr0DvrJKX+1fsKDPpT9TBXgHAFsa4510aNVgI8g/+SzA==", + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stealthy-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", + "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "dependencies": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + }, + "node_modules/uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/uri-js/node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "engines": { + "node": ">=6" + } + }, + "node_modules/uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + } + }, "dependencies": { "ajv": { "version": "6.5.5", @@ -205,9 +663,9 @@ } }, "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" }, "mime-db": { "version": "1.37.0", From a1c9e9d36b62a5ac05f2976ddb10127c0e01e8d6 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Mon, 15 Feb 2021 16:25:44 +0300 Subject: [PATCH 130/141] Pull request: all: prep release Merge in DNS/adguard-home from prep-release to master Squashed commit of the following: commit 32d83fe663dfd7a585ed9d89d09d47fd7b4cb653 Author: Ainar Garipov Date: Mon Feb 15 16:02:56 2021 +0300 all: prep release --- CHANGELOG.md | 10 +++++++--- internal/home/web.go | 6 +++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bb24fb1f..4f00990e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,9 +14,11 @@ and this project adheres to --> +## [v0.105.1] - 2021-02-15 + ### Changed - Increased HTTP API timeouts ([#2671], [#2682]). @@ -192,10 +194,12 @@ and this project adheres to + [Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.105.1...HEAD [v0.105.1]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.105.0...v0.105.1 ---> -[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.105.0...HEAD [v0.105.0]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.104.3...v0.105.0 [v0.104.3]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.104.2...v0.104.3 [v0.104.2]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.104.1...v0.104.2 diff --git a/internal/home/web.go b/internal/home/web.go index fff2aa07..e016e341 100644 --- a/internal/home/web.go +++ b/internal/home/web.go @@ -18,12 +18,12 @@ import ( const ( // readTimeout is the maximum duration for reading the entire request, // including the body. - readTimeout = 30 * time.Second + readTimeout = 60 * time.Second // readHdrTimeout is the amount of time allowed to read request headers. - readHdrTimeout = 30 * time.Second + readHdrTimeout = 60 * time.Second // writeTimeout is the maximum duration before timing out writes of the // response. - writeTimeout = 30 * time.Second + writeTimeout = 60 * time.Second ) type webConfig struct { From 66b549a565aba86d52d5e137a280954050521de9 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Mon, 15 Feb 2021 16:52:16 +0300 Subject: [PATCH 131/141] Pull request: scripts: fix shameful error Merge in DNS/adguard-home from fix-edge to master Squashed commit of the following: commit fdb7bc5ff772aabf86063f18f3b7c4ec83e63028 Author: Ainar Garipov Date: Mon Feb 15 16:26:39 2021 +0300 scripts: fix shameful error --- scripts/make/build-docker.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/make/build-docker.sh b/scripts/make/build-docker.sh index 29b86ba6..92adb4fb 100644 --- a/scripts/make/build-docker.sh +++ b/scripts/make/build-docker.sh @@ -26,7 +26,6 @@ else fi echo $version -exit 0 # Allow users to use sudo. readonly sudo_cmd="${SUDO:-}" From dbcc55f528d59b962fa1fb608e80ab373ec30cbf Mon Sep 17 00:00:00 2001 From: Artem Baskal Date: Mon, 15 Feb 2021 18:50:58 +0300 Subject: [PATCH 132/141] 2641: Fix optical issue on custom rules Close #2641 Squashed commit of the following: commit 3d7280418e42c1607dd644fdbf5faab870470c93 Author: Artem Baskal Date: Mon Feb 15 18:37:11 2021 +0300 Update changelog order commit 98e46fe3285b294de5f0b5525611cfb18afb63f3 Author: Artem Baskal Date: Mon Feb 15 18:30:53 2021 +0300 Update changelog commit 5342d7c7bc5ca40888a4daeef1526464b861ef29 Author: Artem Baskal Date: Mon Feb 15 18:03:14 2021 +0300 2641: Fix optical issue on custom rules --- CHANGELOG.md | 2 ++ client/src/components/App/index.css | 4 +++- client/src/components/ui/texareaCommentsHighlight.css | 2 +- client/src/install/Setup/Setup.css | 3 ++- client/src/login/Login/Login.css | 3 ++- 5 files changed, 10 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f00990e..127971a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ and this project adheres to ### Fixed +- Optical issue on custom rules ([#2641]). - Occasional crashes during startup. - The field `"range_start"` in the `GET /control/dhcp/status` HTTP API response is now correctly named again ([#2678]). @@ -42,6 +43,7 @@ and this project adheres to - Incorrect version tag in the Docker release ([#2663]). - DNSCrypt queries weren't marked as such in logs ([#2662]). +[#2641]: https://github.com/AdguardTeam/AdGuardHome/issues/2641 [#2653]: https://github.com/AdguardTeam/AdGuardHome/issues/2653 [#2658]: https://github.com/AdguardTeam/AdGuardHome/issues/2658 [#2662]: https://github.com/AdguardTeam/AdGuardHome/issues/2662 diff --git a/client/src/components/App/index.css b/client/src/components/App/index.css index 2b1ee76d..990f13e4 100644 --- a/client/src/components/App/index.css +++ b/client/src/components/App/index.css @@ -5,6 +5,7 @@ --gray-d8: #d8d8d8; --gray-f3: #f3f3f3; --font-family-monospace: Monaco, Menlo, "Ubuntu Mono", Consolas, source-code-pro, monospace; + --font-size-disable-autozoom: 1rem; } body { @@ -13,9 +14,10 @@ body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Arial, sans-serif; } +/* Disable Auto Zoom in Input - Safari on iPhone https://stackoverflow.com/a/6394497 */ @media screen and (max-width: 767px) { input, select, textarea { - font-size: 16px !important; + font-size: var(--font-size-disable-autozoom); } } diff --git a/client/src/components/ui/texareaCommentsHighlight.css b/client/src/components/ui/texareaCommentsHighlight.css index 8551966d..19c84fac 100644 --- a/client/src/components/ui/texareaCommentsHighlight.css +++ b/client/src/components/ui/texareaCommentsHighlight.css @@ -15,7 +15,7 @@ white-space: pre-wrap; line-height: 1.5rem; word-wrap: break-word; - font-size: 0.9375rem; + font-size: var(--font-size-disable-autozoom); margin: 0; } diff --git a/client/src/install/Setup/Setup.css b/client/src/install/Setup/Setup.css index 522d57c1..b08f4cb8 100644 --- a/client/src/install/Setup/Setup.css +++ b/client/src/install/Setup/Setup.css @@ -1,6 +1,7 @@ +/* Disable Auto Zoom in Input - Safari on iPhone https://stackoverflow.com/a/6394497 */ @media screen and (max-width: 767px) { input, select, textarea { - font-size: 16px !important; + font-size: 1rem; } } diff --git a/client/src/login/Login/Login.css b/client/src/login/Login/Login.css index a6b84e96..0d3dbfc1 100644 --- a/client/src/login/Login/Login.css +++ b/client/src/login/Login/Login.css @@ -1,6 +1,7 @@ +/* Disable Auto Zoom in Input - Safari on iPhone https://stackoverflow.com/a/6394497 */ @media screen and (max-width: 767px) { input, select, textarea { - font-size: 16px !important; + font-size: 1rem; } } From f68f6c9b37cea1f33cb6fa791e6678b9df3a237b Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Mon, 15 Feb 2021 19:07:08 +0300 Subject: [PATCH 133/141] Pull request: all: fix statip ip ck Merge in DNS/adguard-home from fix-static-ip-check to master Squashed commit of the following: commit af365c106f3d620afc77492a06f5368611328f5f Author: Ainar Garipov Date: Mon Feb 15 18:55:35 2021 +0300 all: doc changes commit 922afb262458fc488e03cad232430d90c504f2f3 Merge: 43fec5fb dbcc55f5 Author: Ainar Garipov Date: Mon Feb 15 18:53:31 2021 +0300 Merge branch 'master' into fix-static-ip-check commit 43fec5fb79f5c67b375da00aa73d11b3ed9ba3a4 Author: Ainar Garipov Date: Mon Feb 15 18:37:16 2021 +0300 all: fix statip ip ck --- CHANGELOG.md | 2 ++ internal/dhcpd/http.go | 32 +++++++++++++++++++++++--------- internal/sysutil/net.go | 7 +++++++ internal/sysutil/net_linux.go | 17 +++++++++++++---- 4 files changed, 45 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 127971a1..39217416 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,8 @@ and this project adheres to ### Fixed +- Error when enabling the DHCP server when AdGuard Home couldn't determine if + the machine has a static IP. - Optical issue on custom rules ([#2641]). - Occasional crashes during startup. - The field `"range_start"` in the `GET /control/dhcp/status` HTTP API response diff --git a/internal/dhcpd/http.go b/internal/dhcpd/http.go index de155c5c..2dab1132 100644 --- a/internal/dhcpd/http.go +++ b/internal/dhcpd/http.go @@ -95,26 +95,40 @@ func (s *Server) enableDHCP(ifaceName string) (code int, err error) { var hasStaticIP bool hasStaticIP, err = sysutil.IfaceHasStaticIP(ifaceName) if err != nil { - // ErrPermission may happen here on Linux systems where AdGuard - // Home is installed using Snap. That doesn't necessarily mean - // that the machine doesn't have a static IP, so we can assume - // that it has and go on. If the machine doesn't, we'll get an - // error later. - // - // See https://github.com/AdguardTeam/AdGuardHome/issues/2667. if errors.Is(err, os.ErrPermission) { + // ErrPermission may happen here on Linux systems where + // AdGuard Home is installed using Snap. That doesn't + // necessarily mean that the machine doesn't have + // a static IP, so we can assume that it has and go on. + // If the machine doesn't, we'll get an error later. + // + // See https://github.com/AdguardTeam/AdGuardHome/issues/2667. + // + // TODO(a.garipov): I was thinking about moving this + // into IfaceHasStaticIP, but then we wouldn't be able + // to log it. Think about it more. log.Info("error while checking static ip: %s; "+ "assuming machine has static ip and going on", err) hasStaticIP = true + } else if errors.Is(err, sysutil.ErrNoStaticIPInfo) { + // Couldn't obtain a definitive answer. Assume static + // IP an go on. + log.Info("can't check for static ip; " + + "assuming machine has static ip and going on") + hasStaticIP = true } else { - return http.StatusInternalServerError, fmt.Errorf("checking static ip: %w", err) + err = fmt.Errorf("checking static ip: %w", err) + + return http.StatusInternalServerError, err } } if !hasStaticIP { err = sysutil.IfaceSetStaticIP(ifaceName) if err != nil { - return http.StatusInternalServerError, fmt.Errorf("setting static ip: %w", err) + err = fmt.Errorf("setting static ip: %w", err) + + return http.StatusInternalServerError, err } } diff --git a/internal/sysutil/net.go b/internal/sysutil/net.go index 0e3b448e..9ba41704 100644 --- a/internal/sysutil/net.go +++ b/internal/sysutil/net.go @@ -5,10 +5,17 @@ import ( "os/exec" "strings" + "github.com/AdguardTeam/AdGuardHome/internal/agherr" "github.com/AdguardTeam/golibs/log" ) +// ErrNoStaticIPInfo is returned by IfaceHasStaticIP when no information about +// the IP being static is available. +const ErrNoStaticIPInfo agherr.Error = "no information about static ip" + // IfaceHasStaticIP checks if interface is configured to have static IP address. +// If it can't give a definitive answer, it returns false and an error for which +// errors.Is(err, ErrNoStaticIPInfo) is true. func IfaceHasStaticIP(ifaceName string) (has bool, err error) { return ifaceHasStaticIP(ifaceName) } diff --git a/internal/sysutil/net_linux.go b/internal/sysutil/net_linux.go index 9205fd5c..1964f9dd 100644 --- a/internal/sysutil/net_linux.go +++ b/internal/sysutil/net_linux.go @@ -21,7 +21,11 @@ import ( const maxConfigFileSize = 1024 * 1024 func ifaceHasStaticIP(ifaceName string) (has bool, err error) { - var f *os.File + // TODO(a.garipov): Currently, this function returns the first + // definitive result. So if /etc/dhcpcd.conf has a static IP while + // /etc/network/interfaces doesn't, it will return true. Perhaps this + // is not the most desirable behavior. + for _, check := range []struct { checker func(io.Reader, string) (bool, error) filePath string @@ -32,8 +36,11 @@ func ifaceHasStaticIP(ifaceName string) (has bool, err error) { checker: ifacesStaticConfig, filePath: "/etc/network/interfaces", }} { + var f *os.File f, err = os.Open(check.filePath) if err != nil { + // ErrNotExist can happen here if there is no such file. + // This is normal, as not every system uses those files. if errors.Is(err, os.ErrNotExist) { err = nil @@ -52,12 +59,14 @@ func ifaceHasStaticIP(ifaceName string) (has bool, err error) { defer fileReadCloser.Close() has, err = check.checker(fileReadCloser, ifaceName) - if has || err != nil { - break + if err != nil { + return false, err } + + return has, nil } - return has, err + return false, ErrNoStaticIPInfo } // dhcpcdStaticConfig checks if interface is configured by /etc/dhcpcd.conf to From e32c18faab81fb08cafe88324c7049cad5b47cf7 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Tue, 16 Feb 2021 17:58:42 +0300 Subject: [PATCH 134/141] Pull request: dhcpd: fix ip option parsing Merge in DNS/adguard-home from 2688-dhcp-opt-ip to master Updates #2688. Squashed commit of the following: commit e17e003a3a61c1f4ed55617bb61df721cbca12c1 Author: Ainar Garipov Date: Tue Feb 16 17:10:32 2021 +0300 dhcpd: fix ip option parsing --- CHANGELOG.md | 8 ++++++++ internal/dhcpd/dhcpd.go | 12 ++++++++++-- internal/dhcpd/dhcpd_test.go | 34 ++++++++++++++++++++++++---------- 3 files changed, 42 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 39217416..6d11db20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,14 @@ and this project adheres to ## [v0.105.2] - 2021-02-24 --> +### Fixed + +- Wrong parsing of DHCP options of the `ip` type ([#2688]). + +[#2688]: https://github.com/AdguardTeam/AdGuardHome/issues/2688 + + + ## [v0.105.1] - 2021-02-15 ### Changed diff --git a/internal/dhcpd/dhcpd.go b/internal/dhcpd/dhcpd.go index bcd35d1c..e1d5ab1b 100644 --- a/internal/dhcpd/dhcpd.go +++ b/internal/dhcpd/dhcpd.go @@ -293,14 +293,22 @@ func parseOptionString(s string) (uint8, []byte) { if err != nil { return 0, nil } - case "ip": ip := net.ParseIP(sval) if ip == nil { return 0, nil } - val = ip + // Most DHCP options require IPv4, so do not put the 16-byte + // version if we can. Otherwise, the clients will receive weird + // data that looks like four IPv4 addresses. + // + // See https://github.com/AdguardTeam/AdGuardHome/issues/2688. + if ip4 := ip.To4(); ip4 != nil { + val = ip4 + } else { + val = ip + } default: return 0, nil } diff --git a/internal/dhcpd/dhcpd_test.go b/internal/dhcpd/dhcpd_test.go index e9b44518..cca733d9 100644 --- a/internal/dhcpd/dhcpd_test.go +++ b/internal/dhcpd/dhcpd_test.go @@ -3,7 +3,6 @@ package dhcpd import ( - "bytes" "net" "os" "testing" @@ -130,36 +129,51 @@ func TestOptions(t *testing.T) { testCases := []struct { name string optStr string - wantCode uint8 wantVal []byte + wantCode uint8 }{{ - name: "all_right_hex", - optStr: " 12 hex abcdef ", - wantCode: 12, + name: "success_hex", + optStr: "12 hex abcdef", wantVal: []byte{0xab, 0xcd, 0xef}, + wantCode: 12, }, { name: "bad_hex", - optStr: " 12 hex abcdef1 ", + optStr: "12 hex abcdefx", + wantVal: nil, wantCode: 0, }, { - name: "all_right_ip", + name: "success_ip", optStr: "123 ip 1.2.3.4", + wantVal: net.IP{1, 2, 3, 4}, + wantCode: 123, + }, { + name: "success_ipv6", + optStr: "123 ip ::1234", + wantVal: net.IP{ + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0x12, 0x34, + }, wantCode: 123, - wantVal: net.IPv4(1, 2, 3, 4), }, { name: "bad_code", optStr: "256 ip 1.1.1.1", + wantVal: nil, wantCode: 0, }, { name: "negative_code", optStr: "-1 ip 1.1.1.1", + wantVal: nil, wantCode: 0, }, { name: "bad_ip", optStr: "12 ip 1.1.1.1x", + wantVal: nil, wantCode: 0, }, { name: "bad_mode", + wantVal: nil, optStr: "12 x 1.1.1.1", wantCode: 0, }} @@ -167,9 +181,9 @@ func TestOptions(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { code, val := parseOptionString(tc.optStr) - require.EqualValues(t, tc.wantCode, code) + require.Equal(t, tc.wantCode, code) if tc.wantVal != nil { - assert.True(t, bytes.Equal(tc.wantVal, val)) + assert.Equal(t, tc.wantVal, val) } }) } From 1122e71cf34a14cfd3c4e98ea43f80050ca6eebb Mon Sep 17 00:00:00 2001 From: Eugene Burkov Date: Tue, 16 Feb 2021 18:46:49 +0300 Subject: [PATCH 135/141] Pull request: 2674 fix upstreams validation Merge in DNS/adguard-home from 2674-quic-upstream to master Updates #2674. Squashed commit of the following: commit ce7643580cc9e19881689e7fd4933bc953f2884e Author: Eugene Burkov Date: Tue Feb 16 18:23:39 2021 +0300 all: fix log commit b53e4368dd9ca61965e73b1f1274388422142830 Author: Eugene Burkov Date: Tue Feb 16 18:21:59 2021 +0300 all: log changes commit 1cdca48e31c272ccfbde955c2a3e560ca6ca6bcf Merge: bc441dac e32c18fa Author: Eugene Burkov Date: Tue Feb 16 18:15:47 2021 +0300 Merge branch 'master' into 2674-quic-upstream commit bc441dac3cbe8070c8c1d672925b14d309f05b9f Author: Eugene Burkov Date: Tue Feb 16 17:06:47 2021 +0300 dnsforward: fix error wrapping commit 281c1b43233c2bb51e3a933588087a207b7eef3d Author: Eugene Burkov Date: Tue Feb 16 17:00:35 2021 +0300 all: fix validation, imp shutdown commit 31a5ea7a081de4bcc3913bd04d62334fec1b59e1 Author: Eugene Burkov Date: Tue Feb 16 14:53:10 2021 +0300 dnsforward: fix upstreams validation --- CHANGELOG.md | 2 ++ internal/dnsforward/config.go | 2 +- internal/dnsforward/http.go | 6 ++++++ internal/home/home.go | 1 + 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d11db20..33a36ee4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,8 +19,10 @@ and this project adheres to ### Fixed +- Incomplete DNS upstreams validation ([#2674]). - Wrong parsing of DHCP options of the `ip` type ([#2688]). +[#2674]: https://github.com/AdguardTeam/AdGuardHome/issues/2674 [#2688]: https://github.com/AdguardTeam/AdGuardHome/issues/2688 diff --git a/internal/dnsforward/config.go b/internal/dnsforward/config.go index a80e7ad5..81864899 100644 --- a/internal/dnsforward/config.go +++ b/internal/dnsforward/config.go @@ -282,7 +282,7 @@ func (s *Server) prepareUpstreamSettings() error { } if len(upstreamConfig.Upstreams) == 0 { - log.Info("Warning: no default upstream servers specified, using %v", defaultDNS) + log.Info("warning: no default upstream servers specified, using %v", defaultDNS) uc, err := proxy.ParseUpstreamsConfig(defaultDNS, s.conf.BootstrapDNS, DefaultTimeout) if err != nil { return fmt.Errorf("dns: failed to parse default upstreams: %v", err) diff --git a/internal/dnsforward/http.go b/internal/dnsforward/http.go index e7508bc8..527d3b38 100644 --- a/internal/dnsforward/http.go +++ b/internal/dnsforward/http.go @@ -8,6 +8,7 @@ import ( "strconv" "strings" + "github.com/AdguardTeam/dnsproxy/proxy" "github.com/AdguardTeam/dnsproxy/upstream" "github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/utils" @@ -314,6 +315,11 @@ func ValidateUpstreams(upstreams []string) error { return nil } + _, err := proxy.ParseUpstreamsConfig(upstreams, []string{}, DefaultTimeout) + if err != nil { + return err + } + var defaultUpstreamFound bool for _, u := range upstreams { d, err := validateUpstream(u) diff --git a/internal/home/home.go b/internal/home/home.go index 422312a6..bfc17750 100644 --- a/internal/home/home.go +++ b/internal/home/home.go @@ -320,6 +320,7 @@ func run(args options) { go func() { err := startDNSServer() if err != nil { + closeDNSServer() log.Fatal(err) } }() From a0abad6644d1cbe80d9293b6742e38c4c6ddc282 Mon Sep 17 00:00:00 2001 From: Eugene Burkov Date: Thu, 18 Feb 2021 14:11:54 +0300 Subject: [PATCH 136/141] Pull request: 2692 time format Merge in DNS/adguard-home from 2692-time-format to master Closes #2692. Squashed commit of the following: commit a77e3ffc1e2ca7ad1eb4b56641eee787595268e9 Merge: 5262c0b9 1122e71c Author: Eugene Burkov Date: Thu Feb 18 13:58:29 2021 +0300 Merge branch 'master' into 2692-time-format commit 5262c0b95979a6c7a01bdd56e9476c4cdf217119 Author: Eugene Burkov Date: Thu Feb 18 13:50:38 2021 +0300 dhcpd: imp docs commit 3744338d51dd003a0052672ceffa260c70f5738d Author: Eugene Burkov Date: Wed Feb 17 21:10:37 2021 +0300 dhcpd: fix lease time format --- CHANGELOG.md | 2 ++ internal/dhcpd/dhcpd.go | 31 ++++++++++++++++++++++--------- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 33a36ee4..0d47edd7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,11 +19,13 @@ and this project adheres to ### Fixed +- DHCP lease's `expired` field incorrect time format ([#2692]). - Incomplete DNS upstreams validation ([#2674]). - Wrong parsing of DHCP options of the `ip` type ([#2688]). [#2674]: https://github.com/AdguardTeam/AdGuardHome/issues/2674 [#2688]: https://github.com/AdguardTeam/AdGuardHome/issues/2688 +[#2692]: https://github.com/AdguardTeam/AdGuardHome/issues/2692 diff --git a/internal/dhcpd/dhcpd.go b/internal/dhcpd/dhcpd.go index e1d5ab1b..7a48d239 100644 --- a/internal/dhcpd/dhcpd.go +++ b/internal/dhcpd/dhcpd.go @@ -19,7 +19,12 @@ import ( const ( defaultDiscoverTime = time.Second * 3 - leaseExpireStatic = 1 + // leaseExpireStatic is used to define the Expiry field for static + // leases. + // + // TODO(e.burkov): Remove it when static leases determining mechanism + // will be improved. + leaseExpireStatic = 1 ) var webHandlersRegistered = false @@ -37,12 +42,24 @@ type Lease struct { // MarshalJSON implements the json.Marshaler interface for *Lease. func (l *Lease) MarshalJSON() ([]byte, error) { + var expiryStr string + if expiry := l.Expiry; expiry.Unix() != leaseExpireStatic { + // The front-end is waiting for RFC 3999 format of the time + // value. It also shouldn't got an Expiry field for static + // leases. + // + // See https://github.com/AdguardTeam/AdGuardHome/issues/2692. + expiryStr = expiry.Format(time.RFC3339) + } + type lease Lease return json.Marshal(&struct { HWAddr string `json:"mac"` + Expiry string `json:"expires,omitempty"` *lease }{ HWAddr: l.HWAddr.String(), + Expiry: expiryStr, lease: (*lease)(l), }) } @@ -248,14 +265,10 @@ const ( LeasesAll = LeasesDynamic | LeasesStatic ) -// Leases returns the list of current DHCP leases (thread-safe) -func (s *Server) Leases(flags int) []Lease { - result := s.srv4.GetLeases(flags) - - v6leases := s.srv6.GetLeases(flags) - result = append(result, v6leases...) - - return result +// Leases returns the list of active IPv4 and IPv6 DHCP leases. It's safe for +// concurrent use. +func (s *Server) Leases(flags int) (leases []Lease) { + return append(s.srv4.GetLeases(flags), s.srv6.GetLeases(flags)...) } // FindMACbyIP - find a MAC address by IP address in the currently active DHCP leases From 836d0db563cd83b5c1d686caced2e164a5886f5e Mon Sep 17 00:00:00 2001 From: Andrey Meshkov Date: Tue, 23 Feb 2021 14:21:08 +0300 Subject: [PATCH 137/141] Add links to discussions --- .github/ISSUE_TEMPLATE/Bug_report.md | 2 +- .github/ISSUE_TEMPLATE/Feature_request.md | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/Bug_report.md b/.github/ISSUE_TEMPLATE/Bug_report.md index 86615ef8..9aaebca4 100644 --- a/.github/ISSUE_TEMPLATE/Bug_report.md +++ b/.github/ISSUE_TEMPLATE/Bug_report.md @@ -3,7 +3,7 @@ name: Bug report about: Create a bug report to help us improve AdGuard Home --- - +Have a question or an idea? Please search it [on our forum](https://github.com/AdguardTeam/AdGuardHome/discussions) to make sure it was not yet asked. If you cannot find what you had in mind, please [submit it here](https://github.com/AdguardTeam/AdGuardHome/discussions/new). ### Prerequisites diff --git a/.github/ISSUE_TEMPLATE/Feature_request.md b/.github/ISSUE_TEMPLATE/Feature_request.md index 4937f5b6..094531b3 100644 --- a/.github/ISSUE_TEMPLATE/Feature_request.md +++ b/.github/ISSUE_TEMPLATE/Feature_request.md @@ -1,11 +1,9 @@ --- name: Feature request -about: Suggest an idea for AdGuard Home - +about: Suggest a feature request for AdGuard Home --- - +Have a question or an idea? Please search it [on our forum](https://github.com/AdguardTeam/AdGuardHome/discussions) to make sure it was not yet asked. If you cannot find what you had in mind, please [submit it here](https://github.com/AdguardTeam/AdGuardHome/discussions/new). ### Prerequisites From e31c0c456a7b32f75d13065dfad9e6167986b0c8 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Wed, 24 Feb 2021 14:13:29 +0300 Subject: [PATCH 138/141] Pull request: all: imp stalebot Merge in DNS/adguard-home from imp-stalebot to master Squashed commit of the following: commit 8b72c1328030e2299e296a7ab6adb0148dcb03ad Author: Ainar Garipov Date: Wed Feb 24 13:43:41 2021 +0300 all: imp stalebot --- .github/stale.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/stale.yml b/.github/stale.yml index 5c7c4caa..518db8b9 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -1,20 +1,22 @@ # Number of days of inactivity before an issue becomes stale. -'daysUntilStale': 60 +'daysUntilStale': 90 # Number of days of inactivity before a stale issue is closed. -'daysUntilClose': 7 +'daysUntilClose': 15 # Issues with these labels will never be considered stale. 'exemptLabels': - 'bug' - 'enhancement' - 'feature request' - 'localization' +- 'needs investigation' - 'recurrent' +- 'research' # Label to use when marking an issue as stale. 'staleLabel': 'wontfix' # Comment to post when marking an issue as stale. Set to `false` to disable. 'markComment': > This issue has been automatically marked as stale because it has not had - recent activity. It will be closed if no further activity occurs. Thank you + recent activity. It will be closed if no further activity occurs. Thank you for your contributions. # Comment to post when closing a stale issue. Set to `false` to disable. 'closeComment': false From d6a059e395a3998421649014c7b1175a4075ef61 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Fri, 26 Feb 2021 15:52:52 +0300 Subject: [PATCH 139/141] Pull request: openapi: add missing property Merge in DNS/adguard-home from fix-openapi to master Squashed commit of the following: commit 24efa5732b327d179f07cbf67fbe8df6ee1b9477 Author: Ainar Garipov Date: Fri Feb 26 15:42:56 2021 +0300 openapi: add missing property --- openapi/openapi.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index 5de5a607..5892d704 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -1279,6 +1279,8 @@ 'type': 'string' 'edns_cs_enabled': 'type': 'boolean' + 'disable_ipv6': + 'type': 'boolean' 'dnssec_enabled': 'type': 'boolean' 'cache_size': From 91403d0b95e30e071bdf425a95e0f3746c76d359 Mon Sep 17 00:00:00 2001 From: Eugene Burkov Date: Mon, 1 Mar 2021 17:20:16 +0300 Subject: [PATCH 140/141] Pull request: 2757 fix OpenWRT detection Merge in DNS/adguard-home from 2757-openwrt to master Updates #2757. Squashed commit of the following: commit 8e94e6a67ae702bd1b281b306555a4ce9ecc6391 Author: Eugene Burkov Date: Mon Mar 1 17:02:24 2021 +0300 util: convert only once commit f1c74f4d18898f286d70c58f93b2fa21de6b5780 Author: Eugene Burkov Date: Mon Mar 1 16:22:51 2021 +0300 util: log changes, imp docs commit 0a4558d044602058255db71f825a730642cc9b07 Author: Eugene Burkov Date: Mon Mar 1 15:53:26 2021 +0300 util: imp os detection --- CHANGELOG.md | 4 +++- internal/util/helpers.go | 35 ++++++++++++++++++++++++++++++++--- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d47edd7..7d078d4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,11 +14,12 @@ and this project adheres to --> ### Fixed +- Incomplete OpenWRT detection ([#2757]). - DHCP lease's `expired` field incorrect time format ([#2692]). - Incomplete DNS upstreams validation ([#2674]). - Wrong parsing of DHCP options of the `ip` type ([#2688]). @@ -26,6 +27,7 @@ and this project adheres to [#2674]: https://github.com/AdguardTeam/AdGuardHome/issues/2674 [#2688]: https://github.com/AdguardTeam/AdGuardHome/issues/2688 [#2692]: https://github.com/AdguardTeam/AdGuardHome/issues/2692 +[#2757]: https://github.com/AdguardTeam/AdGuardHome/issues/2757 diff --git a/internal/util/helpers.go b/internal/util/helpers.go index b575b269..1b759f45 100644 --- a/internal/util/helpers.go +++ b/internal/util/helpers.go @@ -5,10 +5,12 @@ package util import ( + "bytes" "fmt" "io/ioutil" "os" "os/exec" + "path/filepath" "runtime" "strings" ) @@ -64,16 +66,43 @@ func SplitNext(str *string, splitBy byte) string { return strings.TrimSpace(s) } -// IsOpenWRT checks if OS is OpenWRT. +// IsOpenWRT returns true if host OS is OpenWRT. func IsOpenWRT() bool { if runtime.GOOS != "linux" { return false } - body, err := ioutil.ReadFile("/etc/os-release") + const etcDir = "/etc" + + // TODO(e.burkov): Take care of dealing with fs package after updating + // Go version to 1.16. + fileInfos, err := ioutil.ReadDir(etcDir) if err != nil { return false } - return strings.Contains(string(body), "OpenWrt") + // fNameSubstr is a part of a name of the desired file. + const fNameSubstr = "release" + osNameData := []byte("OpenWrt") + + for _, fileInfo := range fileInfos { + if fileInfo.IsDir() { + continue + } + + if !strings.Contains(fileInfo.Name(), fNameSubstr) { + continue + } + + body, err := ioutil.ReadFile(filepath.Join(etcDir, fileInfo.Name())) + if err != nil { + continue + } + + if bytes.Contains(body, osNameData) { + return true + } + } + + return false } From 94e783d5725163d8163a106a1a1bb30cbdcadbf9 Mon Sep 17 00:00:00 2001 From: Eugene Burkov Date: Mon, 1 Mar 2021 20:37:28 +0300 Subject: [PATCH 141/141] Pull request: 2470 session token Merge in DNS/adguard-home from 2470-session-token to master Updates #2470. Squashed commit of the following: commit 02e874404808ee23000c49b4b2980b049dc4d0e6 Author: Eugene Burkov Date: Mon Mar 1 20:11:35 2021 +0300 home: imp time formating commit 6f4a6c9b190b2672cecd3e3e31413b03d19f8771 Author: Eugene Burkov Date: Mon Mar 1 18:48:15 2021 +0300 home: rm user's data from session token --- CHANGELOG.md | 6 +++++ internal/home/auth.go | 55 +++++++++++++++++++++++--------------- internal/home/auth_test.go | 33 +++++++++++++++++++---- 3 files changed, 68 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d078d4e..cd5293c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,12 @@ and this project adheres to [#2692]: https://github.com/AdguardTeam/AdGuardHome/issues/2692 [#2757]: https://github.com/AdguardTeam/AdGuardHome/issues/2757 +### Security + +- Session token doesn't contain user's information anymore ([#2470]). + +[#2470]: https://github.com/AdguardTeam/AdGuardHome/issues/2470 + ## [v0.105.1] - 2021-02-15 diff --git a/internal/home/auth.go b/internal/home/auth.go index 26b57787..76e7e7b6 100644 --- a/internal/home/auth.go +++ b/internal/home/auth.go @@ -2,13 +2,10 @@ package home import ( "crypto/rand" - "crypto/sha256" "encoding/binary" "encoding/hex" "encoding/json" "fmt" - "math" - "math/big" "net/http" "strings" "sync" @@ -20,8 +17,12 @@ import ( ) const ( - cookieTTL = 365 * 24 // in hours + // cookieTTL is given in hours. + cookieTTL = 365 * 24 sessionCookieName = "agh_session" + + // sessionTokenSize is the length of session token in bytes. + sessionTokenSize = 16 ) type session struct { @@ -285,16 +286,29 @@ type loginJSON struct { Password string `json:"password"` } -func getSession(u *User) ([]byte, error) { - maxSalt := big.NewInt(math.MaxUint32) - salt, err := rand.Int(rand.Reader, maxSalt) +// newSessionToken returns cryptographically secure randomly generated slice of +// bytes of sessionTokenSize length. +// +// TODO(e.burkov): Think about using byte array instead of byte slice. +func newSessionToken() (data []byte, err error) { + randData := make([]byte, sessionTokenSize) + + _, err = rand.Read(randData) if err != nil { return nil, err } - d := []byte(fmt.Sprintf("%s%s%s", salt, u.Name, u.PasswordHash)) - hash := sha256.Sum256(d) - return hash[:], nil + return randData, nil +} + +// cookieTimeFormat is the format to be used in (time.Time).Format for cookie's +// expiry field. +const cookieTimeFormat = "Mon, 02 Jan 2006 15:04:05 GMT" + +// cookieExpiryFormat returns the formatted exp to be used in cookie string. +// It's quite simple for now, but probably will be expanded in the future. +func cookieExpiryFormat(exp time.Time) (formatted string) { + return exp.Format(cookieTimeFormat) } func (a *Auth) httpCookie(req loginJSON) (string, error) { @@ -303,24 +317,23 @@ func (a *Auth) httpCookie(req loginJSON) (string, error) { return "", nil } - sess, err := getSession(&u) + sess, err := newSessionToken() if err != nil { return "", err } now := time.Now().UTC() - expire := now.Add(cookieTTL * time.Hour) - expstr := expire.Format(time.RFC1123) - expstr = expstr[:len(expstr)-len("UTC")] // "UTC" -> "GMT" - expstr += "GMT" - s := session{} - s.userName = u.Name - s.expire = uint32(now.Unix()) + a.sessionTTL - a.addSession(sess, &s) + a.addSession(sess, &session{ + userName: u.Name, + expire: uint32(now.Unix()) + a.sessionTTL, + }) - return fmt.Sprintf("%s=%s; Path=/; HttpOnly; Expires=%s", - sessionCookieName, hex.EncodeToString(sess), expstr), nil + return fmt.Sprintf( + "%s=%s; Path=/; HttpOnly; Expires=%s", + sessionCookieName, hex.EncodeToString(sess), + cookieExpiryFormat(now.Add(cookieTTL*time.Hour)), + ), nil } func handleLogin(w http.ResponseWriter, r *http.Request) { diff --git a/internal/home/auth_test.go b/internal/home/auth_test.go index 4dbd07b6..7dbbf3c6 100644 --- a/internal/home/auth_test.go +++ b/internal/home/auth_test.go @@ -1,6 +1,8 @@ package home import ( + "bytes" + "crypto/rand" "encoding/hex" "net/http" "net/url" @@ -11,6 +13,7 @@ import ( "github.com/AdguardTeam/AdGuardHome/internal/aghtest" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestMain(m *testing.M) { @@ -24,14 +27,34 @@ func prepareTestDir() string { return dir } +func TestNewSessionToken(t *testing.T) { + // Successful case. + token, err := newSessionToken() + require.Nil(t, err) + assert.Len(t, token, sessionTokenSize) + + // Break the rand.Reader. + prevReader := rand.Reader + t.Cleanup(func() { + rand.Reader = prevReader + }) + rand.Reader = &bytes.Buffer{} + + // Unsuccessful case. + token, err = newSessionToken() + require.NotNil(t, err) + assert.Empty(t, token) +} + func TestAuth(t *testing.T) { dir := prepareTestDir() - defer func() { _ = os.RemoveAll(dir) }() + t.Cleanup(func() { _ = os.RemoveAll(dir) }) fn := filepath.Join(dir, "sessions.db") - users := []User{ - {Name: "name", PasswordHash: "$2y$05$..vyzAECIhJPfaQiOK17IukcQnqEgKJHy0iETyYqxn3YXJl8yZuo2"}, - } + users := []User{{ + Name: "name", + PasswordHash: "$2y$05$..vyzAECIhJPfaQiOK17IukcQnqEgKJHy0iETyYqxn3YXJl8yZuo2", + }} a := InitAuth(fn, nil, 60) s := session{} @@ -41,7 +64,7 @@ func TestAuth(t *testing.T) { assert.Equal(t, checkSessionNotFound, a.checkSession("notfound")) a.RemoveSession("notfound") - sess, err := getSession(&users[0]) + sess, err := newSessionToken() assert.Nil(t, err) sessStr := hex.EncodeToString(sess)