Pull request #730: + client: Add Hot Module Replacement

Merge in DNS/adguard-home from feature/hmr to master

Squashed commit of the following:

commit 952ed1955c2a7a32446d99489f137f02eb47c99e
Merge: 83484931 de92c852
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Thu Aug 13 11:02:10 2020 +0300

    Merge branch 'master' into feature/hmr

commit 8348493105d7d63d8b0836a5c272df2b17a6b142
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Wed Aug 5 15:07:31 2020 +0300

    Remove empty prop types, remove Services empty container

commit b2fe4a30b79d91e482318ee5deea8e49c7038f7e
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Wed Aug 5 13:56:35 2020 +0300

    Move constants

commit f8be4c18c35193ad77bf5e25f311ad834c1d6870
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Wed Aug 5 13:19:02 2020 +0300

    Fix Setup bug, update webpack.dev

commit 1d9cc4ddf8af2c979eb707a7f0fc06744eec186c
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Wed Aug 5 12:10:38 2020 +0300

    Review changes

commit a1edb21358def21ed1808b081ffc2f0b6755e3da
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Wed Aug 5 11:46:58 2020 +0300

    Remove lazy loading, fix updated components

commit 0aa2cf55f8d4206ac9e2f99fc1b990ed8a9c7825
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Tue Aug 4 20:32:19 2020 +0300

    Refactor App component, add lazy loading

commit 3c2ba4772a91ff7b06641dba6c6bf3fdcd2fdf7f
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Tue Aug 4 17:12:41 2020 +0300

    Simplify App hot loading boilerplate, setup lazy loading, update Header

commit 8df3221f315372b066f2ac0c9a1687f1677b8415
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Tue Aug 4 15:16:06 2020 +0300

    + client: Add Hot Module Replacement
This commit is contained in:
Artem Baskal 2020-08-13 11:15:45 +03:00
parent de92c85256
commit 97df19898f
38 changed files with 1098 additions and 822 deletions

View File

@ -44,4 +44,4 @@ jobs:
fields: repo,message,commit,author fields: repo,message,commit,author
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}

View File

@ -11,6 +11,7 @@ module.exports = (api) => {
'@babel/plugin-proposal-object-rest-spread', '@babel/plugin-proposal-object-rest-spread',
'@babel/plugin-proposal-nullish-coalescing-operator', '@babel/plugin-proposal-nullish-coalescing-operator',
'@babel/plugin-proposal-optional-chaining', '@babel/plugin-proposal-optional-chaining',
'react-hot-loader/babel',
], ],
}; };
}; };

11
client/constants.js vendored Normal file
View File

@ -0,0 +1,11 @@
const BUILD_ENVS = {
dev: 'development',
prod: 'production',
};
const BASE_URL = '/control';
module.exports = {
BUILD_ENVS,
BASE_URL,
};

143
client/package-lock.json generated vendored
View File

@ -1356,6 +1356,17 @@
"resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz", "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz",
"integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==" "integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA=="
}, },
"@hot-loader/react-dom": {
"version": "16.13.0",
"resolved": "https://registry.npmjs.org/@hot-loader/react-dom/-/react-dom-16.13.0.tgz",
"integrity": "sha512-lJZrmkucz2MrQJTQtJobx5MICXcfQvKihszqv655p557HPi0hMOWxrNpiHv3DWD8ugNWjtWcVWqRnFvwsHq1mQ==",
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1",
"prop-types": "^15.6.2",
"scheduler": "^0.19.0"
}
},
"@istanbuljs/load-nyc-config": { "@istanbuljs/load-nyc-config": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
@ -2257,6 +2268,12 @@
"@types/istanbul-lib-report": "*" "@types/istanbul-lib-report": "*"
} }
}, },
"@types/json-schema": {
"version": "7.0.5",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.5.tgz",
"integrity": "sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ==",
"dev": true
},
"@types/minimatch": { "@types/minimatch": {
"version": "3.0.3", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
@ -2728,7 +2745,6 @@
"version": "1.0.10", "version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"dev": true,
"requires": { "requires": {
"sprintf-js": "~1.0.2" "sprintf-js": "~1.0.2"
} }
@ -5108,6 +5124,12 @@
} }
} }
}, },
"dom-walk": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz",
"integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==",
"dev": true
},
"domain-browser": { "domain-browser": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz",
@ -5201,9 +5223,9 @@
"dev": true "dev": true
}, },
"elliptic": { "elliptic": {
"version": "6.5.2", "version": "6.5.3",
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz",
"integrity": "sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw==", "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==",
"dev": true, "dev": true,
"requires": { "requires": {
"bn.js": "^4.4.0", "bn.js": "^4.4.0",
@ -5216,9 +5238,9 @@
}, },
"dependencies": { "dependencies": {
"bn.js": { "bn.js": {
"version": "4.11.8", "version": "4.11.9",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
"integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==",
"dev": true "dev": true
} }
} }
@ -5887,8 +5909,7 @@
"esprima": { "esprima": {
"version": "4.0.1", "version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
"dev": true
}, },
"esquery": { "esquery": {
"version": "1.3.1", "version": "1.3.1",
@ -6844,6 +6865,16 @@
} }
} }
}, },
"global": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz",
"integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==",
"dev": true,
"requires": {
"min-document": "^2.19.0",
"process": "^0.11.10"
}
},
"global-modules": { "global-modules": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz",
@ -7357,18 +7388,6 @@
"requires-port": "^1.0.0" "requires-port": "^1.0.0"
} }
}, },
"http-proxy-middleware": {
"version": "0.19.1",
"resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz",
"integrity": "sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q==",
"dev": true,
"requires": {
"http-proxy": "^1.17.0",
"is-glob": "^4.0.0",
"lodash": "^4.17.11",
"micromatch": "^3.1.10"
}
},
"http-signature": { "http-signature": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
@ -9898,10 +9917,9 @@
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
}, },
"js-yaml": { "js-yaml": {
"version": "3.13.1", "version": "3.14.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz",
"integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==",
"dev": true,
"requires": { "requires": {
"argparse": "^1.0.7", "argparse": "^1.0.7",
"esprima": "^4.0.0" "esprima": "^4.0.0"
@ -10628,6 +10646,15 @@
"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
"dev": true "dev": true
}, },
"min-document": {
"version": "2.19.0",
"resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz",
"integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=",
"dev": true,
"requires": {
"dom-walk": "^0.1.0"
}
},
"min-indent": { "min-indent": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.0.tgz", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.0.tgz",
@ -12287,6 +12314,39 @@
"scheduler": "^0.19.1" "scheduler": "^0.19.1"
} }
}, },
"react-hot-loader": {
"version": "4.12.21",
"resolved": "https://registry.npmjs.org/react-hot-loader/-/react-hot-loader-4.12.21.tgz",
"integrity": "sha512-Ynxa6ROfWUeKWsTHxsrL2KMzujxJVPjs385lmB2t5cHUxdoRPGind9F00tOkdc1l5WBleOF4XEAMILY1KPIIDA==",
"dev": true,
"requires": {
"fast-levenshtein": "^2.0.6",
"global": "^4.3.0",
"hoist-non-react-statics": "^3.3.0",
"loader-utils": "^1.1.0",
"prop-types": "^15.6.1",
"react-lifecycles-compat": "^3.0.4",
"shallowequal": "^1.1.0",
"source-map": "^0.7.3"
},
"dependencies": {
"hoist-non-react-statics": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
"dev": true,
"requires": {
"react-is": "^16.7.0"
}
},
"source-map": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
"integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
"dev": true
}
}
},
"react-i18next": { "react-i18next": {
"version": "11.4.0", "version": "11.4.0",
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-11.4.0.tgz", "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-11.4.0.tgz",
@ -13328,6 +13388,12 @@
"safe-buffer": "^5.0.1" "safe-buffer": "^5.0.1"
} }
}, },
"shallowequal": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
"integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==",
"dev": true
},
"shebang-command": { "shebang-command": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@ -13491,7 +13557,8 @@
}, },
"kind-of": { "kind-of": {
"version": "6.0.2", "version": "6.0.2",
"resolved": "", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
"integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==",
"dev": true "dev": true
} }
} }
@ -13751,8 +13818,7 @@
"sprintf-js": { "sprintf-js": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
"dev": true
}, },
"sshpk": { "sshpk": {
"version": "1.16.1", "version": "1.16.1",
@ -14079,12 +14145,13 @@
} }
}, },
"schema-utils": { "schema-utils": {
"version": "2.6.6", "version": "2.7.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.6.tgz", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz",
"integrity": "sha512-wHutF/WPSbIi9x6ctjGGk2Hvl0VOz5l3EKEuKbjPlB30mKZUzb9A5k9yEXRX3pwyqVLPvpfZZEllaFq/M718hA==", "integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==",
"dev": true, "dev": true,
"requires": { "requires": {
"ajv": "^6.12.0", "@types/json-schema": "^7.0.4",
"ajv": "^6.12.2",
"ajv-keywords": "^3.4.1" "ajv-keywords": "^3.4.1"
} }
} }
@ -15930,6 +15997,18 @@
"ms": "^2.1.1" "ms": "^2.1.1"
} }
}, },
"http-proxy-middleware": {
"version": "0.19.1",
"resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz",
"integrity": "sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q==",
"dev": true,
"requires": {
"http-proxy": "^1.17.0",
"is-glob": "^4.0.0",
"lodash": "^4.17.11",
"micromatch": "^3.1.10"
}
},
"ms": { "ms": {
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",

6
client/package.json vendored
View File

@ -4,14 +4,16 @@
"private": true, "private": true,
"scripts": { "scripts": {
"build-dev": "cross-env BUILD_ENV=dev webpack --config webpack.dev.js", "build-dev": "cross-env BUILD_ENV=dev webpack --config webpack.dev.js",
"watch": "cross-env BUILD_ENV=dev webpack --config webpack.dev.js --watch",
"build-prod": "cross-env BUILD_ENV=prod webpack --config webpack.prod.js", "build-prod": "cross-env BUILD_ENV=prod webpack --config webpack.prod.js",
"watch": "cross-env BUILD_ENV=dev webpack --config webpack.dev.js --watch",
"watch:hot": "cross-env BUILD_ENV=dev webpack-dev-server --config webpack.dev.js",
"lint": "eslint src", "lint": "eslint src",
"lint:fix": "eslint src --fix", "lint:fix": "eslint src --fix",
"test": "jest", "test": "jest",
"test:watch": "jest --watch" "test:watch": "jest --watch"
}, },
"dependencies": { "dependencies": {
"@hot-loader/react-dom": "^16.13.0",
"@nivo/line": "^0.49.1", "@nivo/line": "^0.49.1",
"axios": "^0.19.2", "axios": "^0.19.2",
"classnames": "^2.2.6", "classnames": "^2.2.6",
@ -19,6 +21,7 @@
"i18next": "^19.4.4", "i18next": "^19.4.4",
"i18next-browser-languagedetector": "^4.2.0", "i18next-browser-languagedetector": "^4.2.0",
"ipaddr.js": "^1.9.1", "ipaddr.js": "^1.9.1",
"js-yaml": "^3.14.0",
"lodash": "^4.17.19", "lodash": "^4.17.19",
"nanoid": "^3.1.9", "nanoid": "^3.1.9",
"prop-types": "^15.7.2", "prop-types": "^15.7.2",
@ -73,6 +76,7 @@
"path": "^0.12.7", "path": "^0.12.7",
"postcss-flexbugs-fixes": "4.2.1", "postcss-flexbugs-fixes": "4.2.1",
"postcss-loader": "^3.0.0", "postcss-loader": "^3.0.0",
"react-hot-loader": "^4.12.21",
"style-loader": "^1.2.1", "style-loader": "^1.2.1",
"stylelint": "^13.5.0", "stylelint": "^13.5.0",
"stylelint-webpack-plugin": "2.0.0", "stylelint-webpack-plugin": "2.0.0",

View File

@ -148,7 +148,7 @@ const checkStatus = async (handleRequestSuccess, handleRequestError, attempts =
const rmTimeout = (t) => t && clearTimeout(t); const rmTimeout = (t) => t && clearTimeout(t);
try { try {
const response = await axios.get('control/status'); const response = await axios.get(`${apiClient.baseUrl}/status`);
rmTimeout(timeout); rmTimeout(timeout);
if (response?.status === 200) { if (response?.status === 200) {
handleRequestSuccess(response); handleRequestSuccess(response);

View File

@ -2,9 +2,10 @@ import axios from 'axios';
import { getPathWithQueryString } from '../helpers/helpers'; import { getPathWithQueryString } from '../helpers/helpers';
import { R_PATH_LAST_PART } from '../helpers/constants'; import { R_PATH_LAST_PART } from '../helpers/constants';
import { BASE_URL } from '../../constants';
class Api { class Api {
baseUrl = 'control'; baseUrl = BASE_URL;
async makeRequest(path, method = 'POST', config) { async makeRequest(path, method = 'POST', config) {
try { try {
@ -26,18 +27,30 @@ class Api {
throw new Error(`${errorPath} | ${error.response.data} | ${error.response.status}`); throw new Error(`${errorPath} | ${error.response.data} | ${error.response.status}`);
} }
throw new Error(`${errorPath} | ${error.message ? error.message : error}`); throw new Error(`${errorPath} | ${error.message || error}`);
} }
} }
// Global methods // Global methods
GLOBAL_STATUS = { path: 'status', method: 'GET' }; GLOBAL_STATUS = {
path: 'status',
method: 'GET',
};
GLOBAL_TEST_UPSTREAM_DNS = { path: 'test_upstream_dns', method: 'POST' }; GLOBAL_TEST_UPSTREAM_DNS = {
path: 'test_upstream_dns',
method: 'POST',
};
GLOBAL_VERSION = { path: 'version.json', method: 'POST' }; GLOBAL_VERSION = {
path: 'version.json',
method: 'POST',
};
GLOBAL_UPDATE = { path: 'update', method: 'POST' }; GLOBAL_UPDATE = {
path: 'update',
method: 'POST',
};
getGlobalStatus() { getGlobalStatus() {
const { path, method } = this.GLOBAL_STATUS; const { path, method } = this.GLOBAL_STATUS;
@ -68,21 +81,45 @@ class Api {
} }
// Filtering // Filtering
FILTERING_STATUS = { path: 'filtering/status', method: 'GET' }; FILTERING_STATUS = {
path: 'filtering/status',
method: 'GET',
};
FILTERING_ADD_FILTER = { path: 'filtering/add_url', method: 'POST' }; FILTERING_ADD_FILTER = {
path: 'filtering/add_url',
method: 'POST',
};
FILTERING_REMOVE_FILTER = { path: 'filtering/remove_url', method: 'POST' }; FILTERING_REMOVE_FILTER = {
path: 'filtering/remove_url',
method: 'POST',
};
FILTERING_SET_RULES = { path: 'filtering/set_rules', method: 'POST' }; FILTERING_SET_RULES = {
path: 'filtering/set_rules',
method: 'POST',
};
FILTERING_REFRESH = { path: 'filtering/refresh', method: 'POST' }; FILTERING_REFRESH = {
path: 'filtering/refresh',
method: 'POST',
};
FILTERING_SET_URL = { path: 'filtering/set_url', method: 'POST' }; FILTERING_SET_URL = {
path: 'filtering/set_url',
method: 'POST',
};
FILTERING_CONFIG = { path: 'filtering/config', method: 'POST' }; FILTERING_CONFIG = {
path: 'filtering/config',
method: 'POST',
};
FILTERING_CHECK_HOST = { path: 'filtering/check_host', method: 'GET' }; FILTERING_CHECK_HOST = {
path: 'filtering/check_host',
method: 'GET',
};
getFilteringStatus() { getFilteringStatus() {
const { path, method } = this.FILTERING_STATUS; const { path, method } = this.FILTERING_STATUS;
@ -153,11 +190,20 @@ class Api {
} }
// Parental // Parental
PARENTAL_STATUS = { path: 'parental/status', method: 'GET' }; PARENTAL_STATUS = {
path: 'parental/status',
method: 'GET',
};
PARENTAL_ENABLE = { path: 'parental/enable', method: 'POST' }; PARENTAL_ENABLE = {
path: 'parental/enable',
method: 'POST',
};
PARENTAL_DISABLE = { path: 'parental/disable', method: 'POST' }; PARENTAL_DISABLE = {
path: 'parental/disable',
method: 'POST',
};
getParentalStatus() { getParentalStatus() {
const { path, method } = this.PARENTAL_STATUS; const { path, method } = this.PARENTAL_STATUS;
@ -180,11 +226,20 @@ class Api {
} }
// Safebrowsing // Safebrowsing
SAFEBROWSING_STATUS = { path: 'safebrowsing/status', method: 'GET' }; SAFEBROWSING_STATUS = {
path: 'safebrowsing/status',
method: 'GET',
};
SAFEBROWSING_ENABLE = { path: 'safebrowsing/enable', method: 'POST' }; SAFEBROWSING_ENABLE = {
path: 'safebrowsing/enable',
method: 'POST',
};
SAFEBROWSING_DISABLE = { path: 'safebrowsing/disable', method: 'POST' }; SAFEBROWSING_DISABLE = {
path: 'safebrowsing/disable',
method: 'POST',
};
getSafebrowsingStatus() { getSafebrowsingStatus() {
const { path, method } = this.SAFEBROWSING_STATUS; const { path, method } = this.SAFEBROWSING_STATUS;
@ -202,11 +257,20 @@ class Api {
} }
// Safesearch // Safesearch
SAFESEARCH_STATUS = { path: 'safesearch/status', method: 'GET' }; SAFESEARCH_STATUS = {
path: 'safesearch/status',
method: 'GET',
};
SAFESEARCH_ENABLE = { path: 'safesearch/enable', method: 'POST' }; SAFESEARCH_ENABLE = {
path: 'safesearch/enable',
method: 'POST',
};
SAFESEARCH_DISABLE = { path: 'safesearch/disable', method: 'POST' }; SAFESEARCH_DISABLE = {
path: 'safesearch/disable',
method: 'POST',
};
getSafesearchStatus() { getSafesearchStatus() {
const { path, method } = this.SAFESEARCH_STATUS; const { path, method } = this.SAFESEARCH_STATUS;
@ -224,9 +288,15 @@ class Api {
} }
// Language // Language
CURRENT_LANGUAGE = { path: 'i18n/current_language', method: 'GET' }; CURRENT_LANGUAGE = {
path: 'i18n/current_language',
method: 'GET',
};
CHANGE_LANGUAGE = { path: 'i18n/change_language', method: 'POST' }; CHANGE_LANGUAGE = {
path: 'i18n/change_language',
method: 'POST',
};
getCurrentLanguage() { getCurrentLanguage() {
const { path, method } = this.CURRENT_LANGUAGE; const { path, method } = this.CURRENT_LANGUAGE;
@ -243,19 +313,40 @@ class Api {
} }
// DHCP // DHCP
DHCP_STATUS = { path: 'dhcp/status', method: 'GET' }; DHCP_STATUS = {
path: 'dhcp/status',
method: 'GET',
};
DHCP_SET_CONFIG = { path: 'dhcp/set_config', method: 'POST' }; DHCP_SET_CONFIG = {
path: 'dhcp/set_config',
method: 'POST',
};
DHCP_FIND_ACTIVE = { path: 'dhcp/find_active_dhcp', method: 'POST' }; DHCP_FIND_ACTIVE = {
path: 'dhcp/find_active_dhcp',
method: 'POST',
};
DHCP_INTERFACES = { path: 'dhcp/interfaces', method: 'GET' }; DHCP_INTERFACES = {
path: 'dhcp/interfaces',
method: 'GET',
};
DHCP_ADD_STATIC_LEASE = { path: 'dhcp/add_static_lease', method: 'POST' }; DHCP_ADD_STATIC_LEASE = {
path: 'dhcp/add_static_lease',
method: 'POST',
};
DHCP_REMOVE_STATIC_LEASE = { path: 'dhcp/remove_static_lease', method: 'POST' }; DHCP_REMOVE_STATIC_LEASE = {
path: 'dhcp/remove_static_lease',
method: 'POST',
};
DHCP_RESET = { path: 'dhcp/reset', method: 'POST' }; DHCP_RESET = {
path: 'dhcp/reset',
method: 'POST',
};
getDhcpStatus() { getDhcpStatus() {
const { path, method } = this.DHCP_STATUS; const { path, method } = this.DHCP_STATUS;
@ -309,11 +400,20 @@ class Api {
} }
// Installation // Installation
INSTALL_GET_ADDRESSES = { path: 'install/get_addresses', method: 'GET' }; INSTALL_GET_ADDRESSES = {
path: 'install/get_addresses',
method: 'GET',
};
INSTALL_CONFIGURE = { path: 'install/configure', method: 'POST' }; INSTALL_CONFIGURE = {
path: 'install/configure',
method: 'POST',
};
INSTALL_CHECK_CONFIG = { path: 'install/check_config', method: 'POST' }; INSTALL_CHECK_CONFIG = {
path: 'install/check_config',
method: 'POST',
};
getDefaultAddresses() { getDefaultAddresses() {
const { path, method } = this.INSTALL_GET_ADDRESSES; const { path, method } = this.INSTALL_GET_ADDRESSES;
@ -339,11 +439,20 @@ class Api {
} }
// DNS-over-HTTPS and DNS-over-TLS // DNS-over-HTTPS and DNS-over-TLS
TLS_STATUS = { path: 'tls/status', method: 'GET' }; TLS_STATUS = {
path: 'tls/status',
method: 'GET',
};
TLS_CONFIG = { path: 'tls/configure', method: 'POST' }; TLS_CONFIG = {
path: 'tls/configure',
method: 'POST',
};
TLS_VALIDATE = { path: 'tls/validate', method: 'POST' }; TLS_VALIDATE = {
path: 'tls/validate',
method: 'POST',
};
getTlsStatus() { getTlsStatus() {
const { path, method } = this.TLS_STATUS; const { path, method } = this.TLS_STATUS;
@ -369,15 +478,30 @@ class Api {
} }
// Per-client settings // Per-client settings
GET_CLIENTS = { path: 'clients', method: 'GET' }; GET_CLIENTS = {
path: 'clients',
method: 'GET',
};
FIND_CLIENTS = { path: 'clients/find', method: 'GET' }; FIND_CLIENTS = {
path: 'clients/find',
method: 'GET',
};
ADD_CLIENT = { path: 'clients/add', method: 'POST' }; ADD_CLIENT = {
path: 'clients/add',
method: 'POST',
};
DELETE_CLIENT = { path: 'clients/delete', method: 'POST' }; DELETE_CLIENT = {
path: 'clients/delete',
method: 'POST',
};
UPDATE_CLIENT = { path: 'clients/update', method: 'POST' }; UPDATE_CLIENT = {
path: 'clients/update',
method: 'POST',
};
getClients() { getClients() {
const { path, method } = this.GET_CLIENTS; const { path, method } = this.GET_CLIENTS;
@ -418,9 +542,15 @@ class Api {
} }
// DNS access settings // DNS access settings
ACCESS_LIST = { path: 'access/list', method: 'GET' }; ACCESS_LIST = {
path: 'access/list',
method: 'GET',
};
ACCESS_SET = { path: 'access/set', method: 'POST' }; ACCESS_SET = {
path: 'access/set',
method: 'POST',
};
getAccessList() { getAccessList() {
const { path, method } = this.ACCESS_LIST; const { path, method } = this.ACCESS_LIST;
@ -437,11 +567,20 @@ class Api {
} }
// DNS rewrites // DNS rewrites
REWRITES_LIST = { path: 'rewrite/list', method: 'GET' }; REWRITES_LIST = {
path: 'rewrite/list',
method: 'GET',
};
REWRITE_ADD = { path: 'rewrite/add', method: 'POST' }; REWRITE_ADD = {
path: 'rewrite/add',
method: 'POST',
};
REWRITE_DELETE = { path: 'rewrite/delete', method: 'POST' }; REWRITE_DELETE = {
path: 'rewrite/delete',
method: 'POST',
};
getRewritesList() { getRewritesList() {
const { path, method } = this.REWRITES_LIST; const { path, method } = this.REWRITES_LIST;
@ -467,9 +606,15 @@ class Api {
} }
// Blocked services // Blocked services
BLOCKED_SERVICES_LIST = { path: 'blocked_services/list', method: 'GET' }; BLOCKED_SERVICES_LIST = {
path: 'blocked_services/list',
method: 'GET',
};
BLOCKED_SERVICES_SET = { path: 'blocked_services/set', method: 'POST' }; BLOCKED_SERVICES_SET = {
path: 'blocked_services/set',
method: 'POST',
};
getBlockedServices() { getBlockedServices() {
const { path, method } = this.BLOCKED_SERVICES_LIST; const { path, method } = this.BLOCKED_SERVICES_LIST;
@ -486,13 +631,25 @@ class Api {
} }
// Settings for statistics // Settings for statistics
GET_STATS = { path: 'stats', method: 'GET' }; GET_STATS = {
path: 'stats',
method: 'GET',
};
STATS_INFO = { path: 'stats_info', method: 'GET' }; STATS_INFO = {
path: 'stats_info',
method: 'GET',
};
STATS_CONFIG = { path: 'stats_config', method: 'POST' }; STATS_CONFIG = {
path: 'stats_config',
method: 'POST',
};
STATS_RESET = { path: 'stats_reset', method: 'POST' }; STATS_RESET = {
path: 'stats_reset',
method: 'POST',
};
getStats() { getStats() {
const { path, method } = this.GET_STATS; const { path, method } = this.GET_STATS;
@ -519,13 +676,25 @@ class Api {
} }
// Query log // Query log
GET_QUERY_LOG = { path: 'querylog', method: 'GET' }; GET_QUERY_LOG = {
path: 'querylog',
method: 'GET',
};
QUERY_LOG_CONFIG = { path: 'querylog_config', method: 'POST' }; QUERY_LOG_CONFIG = {
path: 'querylog_config',
method: 'POST',
};
QUERY_LOG_INFO = { path: 'querylog_info', method: 'GET' }; QUERY_LOG_INFO = {
path: 'querylog_info',
method: 'GET',
};
QUERY_LOG_CLEAR = { path: 'querylog_clear', method: 'POST' }; QUERY_LOG_CLEAR = {
path: 'querylog_clear',
method: 'POST',
};
getQueryLog(params) { getQueryLog(params) {
const { path, method } = this.GET_QUERY_LOG; const { path, method } = this.GET_QUERY_LOG;
@ -553,7 +722,10 @@ class Api {
} }
// Login // Login
LOGIN = { path: 'login', method: 'POST' }; LOGIN = {
path: 'login',
method: 'POST',
};
login(data) { login(data) {
const { path, method } = this.LOGIN; const { path, method } = this.LOGIN;
@ -565,7 +737,10 @@ class Api {
} }
// Profile // Profile
GET_PROFILE = { path: 'profile', method: 'GET' }; GET_PROFILE = {
path: 'profile',
method: 'GET',
};
getProfile() { getProfile() {
const { path, method } = this.GET_PROFILE; const { path, method } = this.GET_PROFILE;
@ -573,9 +748,15 @@ class Api {
} }
// DNS config // DNS config
GET_DNS_CONFIG = { path: 'dns_info', method: 'GET' }; GET_DNS_CONFIG = {
path: 'dns_info',
method: 'GET',
};
SET_DNS_CONFIG = { path: 'dns_config', method: 'POST' }; SET_DNS_CONFIG = {
path: 'dns_config',
method: 'POST',
};
getDnsConfig() { getDnsConfig() {
const { path, method } = this.GET_DNS_CONFIG; const { path, method } = this.GET_DNS_CONFIG;

View File

@ -1,30 +1,16 @@
import React, { Component, Fragment } from 'react'; import React, { useEffect } from 'react';
import { HashRouter, Route } from 'react-router-dom'; import { HashRouter, Route } from 'react-router-dom';
import PropTypes from 'prop-types';
import { withTranslation } from 'react-i18next';
import LoadingBar from 'react-redux-loading-bar'; import LoadingBar from 'react-redux-loading-bar';
import { hot } from 'react-hot-loader/root';
import 'react-table/react-table.css'; import 'react-table/react-table.css';
import '../ui/Tabler.css'; import '../ui/Tabler.css';
import '../ui/ReactTable.css'; import '../ui/ReactTable.css';
import './index.css'; import './index.css';
import Header from '../../containers/Header'; import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import Dashboard from '../../containers/Dashboard';
import Settings from '../../containers/Settings';
import CustomRules from '../../containers/CustomRules'; import propTypes from 'prop-types';
import DnsBlocklist from '../../containers/DnsBlocklist';
import DnsAllowlist from '../../containers/DnsAllowlist';
import DnsRewrites from '../../containers/DnsRewrites';
import Dns from '../../containers/Dns';
import Encryption from '../../containers/Encryption';
import Dhcp from '../../containers/Dhcp';
import Clients from '../../containers/Clients';
import Logs from '../../containers/Logs';
import SetupGuide from '../../containers/SetupGuide';
import Toasts from '../Toasts'; import Toasts from '../Toasts';
import Footer from '../ui/Footer'; import Footer from '../ui/Footer';
import Status from '../ui/Status'; import Status from '../ui/Status';
@ -35,31 +21,107 @@ import Icons from '../ui/Icons';
import i18n from '../../i18n'; import i18n from '../../i18n';
import Loading from '../ui/Loading'; import Loading from '../ui/Loading';
import { FILTERS_URLS, MENU_URLS, SETTINGS_URLS } from '../../helpers/constants'; import { FILTERS_URLS, MENU_URLS, SETTINGS_URLS } from '../../helpers/constants';
import Services from '../Filters/Services';
import { getLogsUrlParams, setHtmlLangAttr } from '../../helpers/helpers'; import { getLogsUrlParams, setHtmlLangAttr } from '../../helpers/helpers';
import Header from '../Header';
import { changeLanguage, getDnsStatus } from '../../actions';
class App extends Component { import Dashboard from '../../containers/Dashboard';
componentDidMount() { import Logs from '../../containers/Logs';
this.props.getDnsStatus(); import SetupGuide from '../../containers/SetupGuide';
} import Settings from '../../containers/Settings';
import Dns from '../../containers/Dns';
import Encryption from '../../containers/Encryption';
import Dhcp from '../../containers/Dhcp';
import Clients from '../../containers/Clients';
import DnsBlocklist from '../../containers/DnsBlocklist';
import DnsAllowlist from '../../containers/DnsAllowlist';
import DnsRewrites from '../../containers/DnsRewrites';
import CustomRules from '../../containers/CustomRules';
import Services from '../Filters/Services';
componentDidUpdate(prevProps) { const ROUTES = [
if (this.props.dashboard.language !== prevProps.dashboard.language) { {
this.setLanguage(); path: MENU_URLS.root,
} component: Dashboard,
} exact: true,
},
{
path: [`${MENU_URLS.logs}${getLogsUrlParams(':search?', ':response_status?')}`, MENU_URLS.logs],
component: Logs,
},
{
path: MENU_URLS.guide,
component: SetupGuide,
},
{
path: SETTINGS_URLS.settings,
component: Settings,
},
{
path: SETTINGS_URLS.dns,
component: Dns,
},
{
path: SETTINGS_URLS.encryption,
component: Encryption,
},
{
path: SETTINGS_URLS.dhcp,
component: Dhcp,
},
{
path: SETTINGS_URLS.clients,
component: Clients,
},
{
path: FILTERS_URLS.dns_blocklists,
component: DnsBlocklist,
},
{
path: FILTERS_URLS.dns_allowlists,
component: DnsAllowlist,
},
{
path: FILTERS_URLS.dns_rewrites,
component: DnsRewrites,
},
{
path: FILTERS_URLS.custom_rules,
component: CustomRules,
},
{
path: FILTERS_URLS.blocked_services,
component: Services,
},
];
reloadPage = () => { const renderRoute = ({ path, component, exact }, idx) => <Route
window.location.reload(); key={idx}
}; exact={exact}
path={path}
component={component}
/>;
handleUpdate = () => { const App = () => {
this.props.getUpdate(); const dispatch = useDispatch();
}; const {
language,
isCoreRunning,
isUpdateAvailable,
processing,
} = useSelector((state) => state.dashboard, shallowEqual);
setLanguage = () => { const { processing: processingEncryption } = useSelector((
const { processing, language } = this.props.dashboard; state,
) => state.encryption, shallowEqual);
const updateAvailable = isCoreRunning && isUpdateAvailable;
useEffect(() => {
dispatch(getDnsStatus());
}, []);
const setLanguage = () => {
if (!processing) { if (!processing) {
if (language) { if (language) {
i18n.changeLanguage(language); i18n.changeLanguage(language);
@ -68,93 +130,52 @@ class App extends Component {
} }
i18n.on('languageChanged', (lang) => { i18n.on('languageChanged', (lang) => {
this.props.changeLanguage(lang); dispatch(changeLanguage(lang));
}); });
}; };
render() { useEffect(() => {
const { dashboard, encryption, getVersion } = this.props; setLanguage();
const updateAvailable = dashboard.isCoreRunning && dashboard.isUpdateAvailable; }, [language]);
return ( const reloadPage = () => {
<HashRouter hashType="noslash"> window.location.reload();
<Fragment> };
{updateAvailable && (
<Fragment> return (
<UpdateTopline <HashRouter hashType="noslash">
url={dashboard.announcementUrl} <>
version={dashboard.newVersion} {updateAvailable && <>
canAutoUpdate={dashboard.canAutoUpdate} <UpdateTopline />
getUpdate={this.handleUpdate} <UpdateOverlay />
processingUpdate={dashboard.processingUpdate} </>}
/> {!processingEncryption && <EncryptionTopline />}
<UpdateOverlay processingUpdate={dashboard.processingUpdate} /> <LoadingBar className="loading-bar" updateTime={1000} />
</Fragment> <Header />
)} <div className="container container--wrap pb-5">
{!encryption.processing && ( {processing && <Loading />}
<EncryptionTopline notAfter={encryption.not_after} /> {!isCoreRunning && (
)} <div className="row row-cards">
<LoadingBar className="loading-bar" updateTime={1000} /> <div className="col-lg-12">
<Route component={Header} /> <Status reloadPage={reloadPage} message="dns_start" />
<div className="container container--wrap pb-5"> <Loading />
{dashboard.processing && <Loading />}
{!dashboard.isCoreRunning && (
<div className="row row-cards">
<div className="col-lg-12">
<Status reloadPage={this.reloadPage}
message="dns_start"
/>
<Loading />
</div>
</div> </div>
)} </div>
{!dashboard.processing && dashboard.isCoreRunning && ( )}
<> {!processing && isCoreRunning && ROUTES.map(renderRoute)}
<Route path={MENU_URLS.root} exact component={Dashboard} /> </div>
<Route <Footer />
path={[`${MENU_URLS.logs}${getLogsUrlParams(':search?', ':response_status?')}`, MENU_URLS.logs]} <Toasts />
component={Logs} /> <Icons />
<Route path={MENU_URLS.guide} component={SetupGuide} /> </>
<Route path={SETTINGS_URLS.settings} component={Settings} /> </HashRouter>
<Route path={SETTINGS_URLS.dns} component={Dns} /> );
<Route path={SETTINGS_URLS.encryption} component={Encryption} />
<Route path={SETTINGS_URLS.dhcp} component={Dhcp} />
<Route path={SETTINGS_URLS.clients} component={Clients} />
<Route path={FILTERS_URLS.dns_blocklists}
component={DnsBlocklist} />
<Route path={FILTERS_URLS.dns_allowlists}
component={DnsAllowlist} />
<Route path={FILTERS_URLS.dns_rewrites} component={DnsRewrites} />
<Route path={FILTERS_URLS.custom_rules} component={CustomRules} />
<Route path={FILTERS_URLS.blocked_services} component={Services} />
</>
)}
</div>
<Footer
dnsVersion={dashboard.dnsVersion}
dnsPort={dashboard.dnsPort}
processingVersion={dashboard.processingVersion}
getVersion={getVersion}
checkUpdateFlag={dashboard.checkUpdateFlag}
/>
<Toasts />
<Icons />
</Fragment>
</HashRouter>
);
}
}
App.propTypes = {
getDnsStatus: PropTypes.func,
getUpdate: PropTypes.func,
enableDns: PropTypes.func,
dashboard: PropTypes.object,
isCoreRunning: PropTypes.bool,
error: PropTypes.string,
changeLanguage: PropTypes.func,
encryption: PropTypes.object,
getVersion: PropTypes.func,
}; };
export default withTranslation()(App); renderRoute.propTypes = {
path: propTypes.oneOfType([propTypes.string, propTypes.arrayOf(propTypes.string)]).isRequired,
component: propTypes.element.isRequired,
exact: propTypes.bool,
};
export default hot(App);

View File

@ -59,6 +59,4 @@ const Services = () => {
); );
}; };
Services.propTypes = {};
export default Services; export default Services;

View File

@ -90,9 +90,8 @@ class Menu extends Component {
}; };
getActiveClassForDropdown = (URLS) => { getActiveClassForDropdown = (URLS) => {
const { pathname } = this.props.location;
const isActivePage = Object.values(URLS) const isActivePage = Object.values(URLS)
.some((item) => item === pathname); .some((item) => item === this.props.pathname);
return isActivePage ? 'active' : ''; return isActivePage ? 'active' : '';
}; };
@ -180,9 +179,9 @@ class Menu extends Component {
} }
Menu.propTypes = { Menu.propTypes = {
isMenuOpen: PropTypes.bool, isMenuOpen: PropTypes.bool.isRequired,
closeMenu: PropTypes.func, closeMenu: PropTypes.func.isRequired,
location: PropTypes.object, pathname: PropTypes.string.isRequired,
t: PropTypes.func, t: PropTypes.func,
}; };

View File

@ -1,83 +1,77 @@
import React, { Component } from 'react'; import React, { useState } from 'react';
import { Link } from 'react-router-dom'; import { Link, useLocation } from 'react-router-dom';
import PropTypes from 'prop-types'; import { shallowEqual, useSelector } from 'react-redux';
import { Trans } from 'react-i18next';
import classnames from 'classnames'; import classnames from 'classnames';
import { Trans, withTranslation } from 'react-i18next';
import Menu from './Menu'; import Menu from './Menu';
import logo from '../ui/svg/logo.svg'; import logo from '../ui/svg/logo.svg';
import './Header.css'; import './Header.css';
class Header extends Component { const Header = () => {
state = { const [isMenuOpen, setIsMenuOpen] = useState(false);
isMenuOpen: false,
const {
protectionEnabled,
processing,
isCoreRunning,
processingProfile,
name,
} = useSelector((state) => state.dashboard, shallowEqual);
const { pathname } = useLocation();
const toggleMenuOpen = () => {
setIsMenuOpen((isMenuOpen) => !isMenuOpen);
}; };
toggleMenuOpen = () => { const closeMenu = () => {
this.setState((prevState) => ({ isMenuOpen: !prevState.isMenuOpen })); setIsMenuOpen(false);
}; };
closeMenu = () => { const badgeClass = classnames('badge dns-status', {
this.setState({ isMenuOpen: false }); 'badge-success': protectionEnabled,
}; 'badge-danger': !protectionEnabled,
});
render() { return (
const { dashboard, location } = this.props; <div className="header">
const { isMenuOpen } = this.state; <div className="header__container">
const badgeClass = classnames({ <div className="header__row">
'badge dns-status': true, <div
'badge-success': dashboard.protectionEnabled, className="header-toggler d-lg-none ml-lg-0 collapsed"
'badge-danger': !dashboard.protectionEnabled, onClick={toggleMenuOpen}
}); >
<span className="header-toggler-icon" />
return ( </div>
<div className="header"> <div className="header__column">
<div className="header__container"> <div className="d-flex align-items-center">
<div className="header__row"> <Link to="/" className="nav-link pl-0 pr-1">
<div <img src={logo} alt="" className="header-brand-img" />
className="header-toggler d-lg-none ml-lg-0 collapsed" </Link>
onClick={this.toggleMenuOpen} {!processing && isCoreRunning && (
> <span className={badgeClass}>
<span className="header-toggler-icon" /> <Trans>{protectionEnabled ? 'on' : 'off'}</Trans>
</div>
<div className="header__column">
<div className="d-flex align-items-center">
<Link to="/" className="nav-link pl-0 pr-1">
<img src={logo} alt="" className="header-brand-img" />
</Link>
{!dashboard.processing && dashboard.isCoreRunning && (
<span className={badgeClass}>
<Trans>{dashboard.protectionEnabled ? 'on' : 'off'}</Trans>
</span> </span>
)} )}
</div>
</div> </div>
<Menu </div>
location={location} <Menu
isMenuOpen={isMenuOpen} pathname={pathname}
closeMenu={this.closeMenu} isMenuOpen={isMenuOpen}
/> closeMenu={closeMenu}
<div className="header__column"> />
<div className="header__right"> <div className="header__column">
{!dashboard.processingProfile && dashboard.name <div className="header__right">
&& <a href="control/logout" className="btn btn-sm btn-outline-secondary"> {!processingProfile && name
<Trans>sign_out</Trans> && <a href="control/logout" className="btn btn-sm btn-outline-secondary">
</a> <Trans>sign_out</Trans>
} </a>}
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
); </div>
} );
}
Header.propTypes = {
dashboard: PropTypes.object.isRequired,
location: PropTypes.object.isRequired,
getVersion: PropTypes.func.isRequired,
t: PropTypes.func.isRequired,
}; };
export default withTranslation()(Header); export default Header;

View File

@ -437,7 +437,7 @@
} }
.custom-select__arrow--left { .custom-select__arrow--left {
background: #fff url('./chevron-down.svg') no-repeat; background: var(--white) url('../ui/svg/chevron-down.svg') no-repeat;
background-position: 5px 9px; background-position: 5px 9px;
background-size: 22px; background-size: 22px;
} }

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#9aa0ac" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-chevron-down"><polyline points="6 9 12 15 18 9"></polyline></svg>

Before

Width:  |  Height:  |  Size: 264 B

View File

@ -1,4 +1,4 @@
import React, { Component, Fragment } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import classnames from 'classnames'; import classnames from 'classnames';
import { Trans, withTranslation } from 'react-i18next'; import { Trans, withTranslation } from 'react-i18next';
@ -34,13 +34,14 @@ class Dhcp extends Component {
} = this.props.dhcp; } = this.props.dhcp;
const otherDhcpFound = check?.otherServer const otherDhcpFound = check?.otherServer
&& check.otherServer.found === DHCP_STATUS_RESPONSE.YES; && check.otherServer.found === DHCP_STATUS_RESPONSE.YES;
const filledConfig = Object.keys(config).every((key) => { const filledConfig = Object.keys(config)
if (key === 'enabled' || key === 'icmp_timeout_msec') { .every((key) => {
return true; if (key === 'enabled' || key === 'icmp_timeout_msec') {
} return true;
}
return config[key]; return config[key];
}); });
if (config.enabled) { if (config.enabled) {
return ( return (
@ -114,40 +115,35 @@ class Dhcp extends Component {
getStaticIpWarning = (t, check, interfaceName) => { getStaticIpWarning = (t, check, interfaceName) => {
if (check.staticIP.static === DHCP_STATUS_RESPONSE.ERROR) { if (check.staticIP.static === DHCP_STATUS_RESPONSE.ERROR) {
return ( return <>
<Fragment> <div className="text-danger mb-2">
<div className="text-danger mb-2"> <Trans>dhcp_static_ip_error</Trans>
<Trans>dhcp_static_ip_error</Trans> <div className="mt-2 mb-2">
<div className="mt-2 mb-2"> <Accordion label={t('error_details')}>
<Accordion label={t('error_details')}> <span>{check.staticIP.error}</span>
<span>{check.staticIP.error}</span> </Accordion>
</Accordion>
</div>
</div> </div>
<hr className="mt-4 mb-4" /> </div>
</Fragment> <hr className="mt-4 mb-4" />
); </>;
} if ( }
check.staticIP.static === DHCP_STATUS_RESPONSE.NO if (check.staticIP.static === DHCP_STATUS_RESPONSE.NO
&& check.staticIP.ip && check.staticIP.ip
&& interfaceName && interfaceName) {
) { return <>
return ( <div className="text-secondary mb-2">
<Fragment> <Trans
<div className="text-secondary mb-2"> components={[<strong key="0">example</strong>]}
<Trans values={{
components={[<strong key="0">example</strong>]} interfaceName,
values={{ ipAddress: check.staticIP.ip,
interfaceName, }}
ipAddress: check.staticIP.ip, >
}} dhcp_dynamic_ip_found
> </Trans>
dhcp_dynamic_ip_found </div>
</Trans> <hr className="mt-4 mb-4" />
</div> </>;
<hr className="mt-4 mb-4" />
</Fragment>
);
} }
return ''; return '';
@ -163,104 +159,101 @@ class Dhcp extends Component {
removeStaticLease, removeStaticLease,
toggleLeaseModal, toggleLeaseModal,
} = this.props; } = this.props;
const statusButtonClass = classnames({ const statusButtonClass = classnames({
'btn btn-primary btn-standard': true, 'btn btn-primary btn-standard': true,
'btn btn-primary btn-standard btn-loading': dhcp.processingStatus, 'btn btn-primary btn-standard btn-loading': dhcp.processingStatus,
}); });
const { enabled, interface_name, ...values } = dhcp.config; const { enabled, interface_name, ...values } = dhcp.config;
return ( return <>
<Fragment> <PageTitle title={t('dhcp_settings')} />
<PageTitle title={t('dhcp_settings')} /> {(dhcp.processing || dhcp.processingInterfaces) && <Loading />}
{(dhcp.processing || dhcp.processingInterfaces) && <Loading />} {!dhcp.processing && !dhcp.processingInterfaces && <>
{!dhcp.processing && !dhcp.processingInterfaces && ( <Card
<Fragment> title={t('dhcp_title')}
<Card subtitle={t('dhcp_description')}
title={t('dhcp_title')} bodyType="card-body box-body--settings"
subtitle={t('dhcp_description')} >
bodyType="card-body box-body--settings" <div className="dhcp">
> <>
<div className="dhcp"> <Form
<Fragment> onSubmit={this.handleFormSubmit}
<Form initialValues={{
onSubmit={this.handleFormSubmit} interface_name,
initialValues={{ ...values,
interface_name, }}
...values, interfaces={dhcp.interfaces}
}} processingConfig={dhcp.processingConfig}
interfaces={dhcp.interfaces} processingInterfaces={dhcp.processingInterfaces}
processingConfig={dhcp.processingConfig} enabled={enabled}
processingInterfaces={dhcp.processingInterfaces} resetDhcp={resetDhcp}
enabled={enabled} />
resetDhcp={resetDhcp} <hr />
/> <div className="card-actions mb-3">
<hr /> {this.getToggleDhcpButton()}
<div className="card-actions mb-3"> <button
{this.getToggleDhcpButton()} type="button"
<button className={statusButtonClass}
type="button" onClick={() => findActiveDhcp(interface_name)}
className={statusButtonClass} disabled={
onClick={() => findActiveDhcp(interface_name)} enabled || !interface_name || dhcp.processingConfig
disabled={ }
enabled || !interface_name || dhcp.processingConfig >
} <Trans>check_dhcp_servers</Trans>
> </button>
<Trans>check_dhcp_servers</Trans>
</button>
</div>
{!enabled && dhcp.check && (
<Fragment>
{this.getStaticIpWarning(t, dhcp.check, interface_name)}
{this.getActiveDhcpMessage(t, dhcp.check)}
{this.getDhcpWarning(dhcp.check)}
</Fragment>
)}
</Fragment>
</div> </div>
</Card> {!enabled && dhcp.check && (
{dhcp.config.enabled && ( <>
<Card {this.getStaticIpWarning(t, dhcp.check, interface_name)}
title={t('dhcp_leases')} {this.getActiveDhcpMessage(t, dhcp.check)}
bodyType="card-body box-body--settings" {this.getDhcpWarning(dhcp.check)}
> </>
<div className="row"> )}
<div className="col"> </>
<Leases leases={dhcp.leases} /> </div>
</div> </Card>
</div> {dhcp.config.enabled && (
</Card> <Card
)} title={t('dhcp_leases')}
<Card bodyType="card-body box-body--settings"
title={t('dhcp_static_leases')} >
bodyType="card-body box-body--settings" <div className="row">
> <div className="col">
<div className="row"> <Leases leases={dhcp.leases} />
<div className="col-12">
<StaticLeases
staticLeases={dhcp.staticLeases}
isModalOpen={dhcp.isModalOpen}
addStaticLease={addStaticLease}
removeStaticLease={removeStaticLease}
toggleLeaseModal={toggleLeaseModal}
processingAdding={dhcp.processingAdding}
processingDeleting={dhcp.processingDeleting}
/>
</div>
<div className="col-12">
<button
type="button"
className="btn btn-success btn-standard mt-3"
onClick={() => toggleLeaseModal()}
>
<Trans>dhcp_add_static_lease</Trans>
</button>
</div>
</div> </div>
</Card> </div>
</Fragment> </Card>
)} )}
</Fragment> <Card
); title={t('dhcp_static_leases')}
bodyType="card-body box-body--settings"
>
<div className="row">
<div className="col-12">
<StaticLeases
staticLeases={dhcp.staticLeases}
isModalOpen={dhcp.isModalOpen}
addStaticLease={addStaticLease}
removeStaticLease={removeStaticLease}
toggleLeaseModal={toggleLeaseModal}
processingAdding={dhcp.processingAdding}
processingDeleting={dhcp.processingDeleting}
/>
</div>
<div className="col-12">
<button
type="button"
className="btn btn-success btn-standard mt-3"
onClick={() => toggleLeaseModal()}
>
<Trans>dhcp_add_static_lease</Trans>
</button>
</div>
</div>
</Card>
</>}
</>;
} }
} }

View File

@ -1,40 +1,36 @@
import React, { Component } from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import { useTranslation } from 'react-i18next';
import { withTranslation } from 'react-i18next'; import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import Form from './Form'; import Form from './Form';
import Card from '../../../ui/Card'; import Card from '../../../ui/Card';
import { setAccessList } from '../../../../actions/access';
class Access extends Component { const Access = () => {
handleFormSubmit = (values) => { const { t } = useTranslation();
this.props.setAccessList(values); const dispatch = useDispatch();
const {
processing,
processingSet,
...values
} = useSelector((state) => state.access, shallowEqual);
const handleFormSubmit = (values) => {
dispatch(setAccessList(values));
}; };
render() { return (
const { t, access } = this.props; <Card
title={t('access_title')}
const { processing, processingSet, ...values } = access; subtitle={t('access_desc')}
bodyType="card-body box-body--settings"
return ( >
<Card <Form
title={t('access_title')} initialValues={values}
subtitle={t('access_desc')} onSubmit={handleFormSubmit}
bodyType="card-body box-body--settings" processingSet={processingSet}
> />
<Form </Card>
initialValues={values} );
onSubmit={this.handleFormSubmit}
processingSet={processingSet}
/>
</Card>
);
}
}
Access.propTypes = {
access: PropTypes.object.isRequired,
setAccessList: PropTypes.func.isRequired,
t: PropTypes.func.isRequired,
}; };
export default withTranslation()(Access); export default Access;

View File

@ -1,9 +1,8 @@
import React, { Fragment } from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { connect } from 'react-redux'; import { shallowEqual, useSelector } from 'react-redux';
import { Field, reduxForm, formValueSelector } from 'redux-form'; import { Field, reduxForm } from 'redux-form';
import { Trans, withTranslation } from 'react-i18next'; import { Trans, useTranslation } from 'react-i18next';
import flow from 'lodash/flow';
import { import {
renderInputField, renderInputField,
renderRadioField, renderRadioField,
@ -18,32 +17,36 @@ import {
} from '../../../../helpers/validators'; } from '../../../../helpers/validators';
import { BLOCKING_MODES, FORM_NAME } from '../../../../helpers/constants'; import { BLOCKING_MODES, FORM_NAME } from '../../../../helpers/constants';
const checkboxes = [{ const checkboxes = [
name: 'edns_cs_enabled', {
placeholder: 'edns_enable', name: 'edns_cs_enabled',
subtitle: 'edns_cs_desc', placeholder: 'edns_enable',
}, subtitle: 'edns_cs_desc',
{ },
name: 'dnssec_enabled', {
placeholder: 'dnssec_enable', name: 'dnssec_enabled',
subtitle: 'dnssec_enable_desc', placeholder: 'dnssec_enable',
}, subtitle: 'dnssec_enable_desc',
{ },
name: 'disable_ipv6', {
placeholder: 'disable_ipv6', name: 'disable_ipv6',
subtitle: 'disable_ipv6_desc', placeholder: 'disable_ipv6',
}]; subtitle: 'disable_ipv6_desc',
},
];
const customIps = [{ const customIps = [
description: 'blocking_ipv4_desc', {
name: 'blocking_ipv4', description: 'blocking_ipv4_desc',
validateIp: validateIpv4, name: 'blocking_ipv4',
}, validateIp: validateIpv4,
{ },
description: 'blocking_ipv6_desc', {
name: 'blocking_ipv6', description: 'blocking_ipv6_desc',
validateIp: validateIpv6, name: 'blocking_ipv6',
}]; validateIp: validateIpv6,
},
];
const getFields = (processing, t) => Object.values(BLOCKING_MODES) const getFields = (processing, t) => Object.values(BLOCKING_MODES)
.map((mode) => ( .map((mode) => (
@ -58,114 +61,107 @@ const getFields = (processing, t) => Object.values(BLOCKING_MODES)
/> />
)); ));
let Form = ({ const Form = ({
handleSubmit, submitting, invalid, processing, blockingMode, t, handleSubmit, submitting, invalid, processing,
}) => <form onSubmit={handleSubmit}> }) => {
<div className="row"> const { t } = useTranslation();
<div className="col-12 col-sm-6"> const {
<div className="form__group form__group--settings"> blocking_mode,
<label htmlFor="ratelimit" } = useSelector((state) => state.form[FORM_NAME.BLOCKING_MODE].values ?? {}, shallowEqual);
className="form__label form__label--with-desc">
<Trans>rate_limit</Trans> return <form onSubmit={handleSubmit}>
</label> <div className="row">
<div className="form__desc form__desc--top"> <div className="col-12 col-sm-6">
<Trans>rate_limit_desc</Trans> <div className="form__group form__group--settings">
</div> <label htmlFor="ratelimit"
<Field className="form__label form__label--with-desc">
name="ratelimit" <Trans>rate_limit</Trans>
type="number" </label>
component={renderInputField} <div className="form__desc form__desc--top">
className="form-control" <Trans>rate_limit_desc</Trans>
placeholder={t('form_enter_rate_limit')}
normalize={toNumber}
validate={[validateRequiredValue, validateBiggerOrEqualZeroValue]}
/>
</div>
</div>
{checkboxes.map(({ name, placeholder, subtitle }) => <div className="col-12" key={name}>
<div className="form__group form__group--settings">
<Field
name={name}
type="checkbox"
component={renderSelectField}
placeholder={t(placeholder)}
disabled={processing}
subtitle={t(subtitle)}
/>
</div>
</div>)}
<div className="col-12">
<div className="form__group form__group--settings mb-4">
<label className="form__label form__label--with-desc">
<Trans>blocking_mode</Trans>
</label>
<div className="form__desc form__desc--top">
{Object.values(BLOCKING_MODES)
.map((mode) => (
<li key={mode}>
<Trans>{`blocking_mode_${mode}`}</Trans>
</li>
))}
</div>
<div className="custom-controls-stacked">
{getFields(processing, t)}
</div>
</div>
</div>
{blockingMode === BLOCKING_MODES.custom_ip && (
<Fragment>
{customIps.map(({
description,
name,
validateIp,
}) => <div className="col-12 col-sm-6" key={name}>
<div className="form__group form__group--settings">
<label className="form__label form__label--with-desc"
htmlFor={name}><Trans>{name}</Trans>
</label>
<div className="form__desc form__desc--top">
<Trans>{description}</Trans>
</div>
<Field
name={name}
component={renderInputField}
className="form-control"
placeholder={t('form_enter_ip')}
validate={[validateIp, validateRequiredValue]}
/>
</div> </div>
</div>)} <Field
</Fragment> name="ratelimit"
)} type="number"
</div> component={renderInputField}
<button className="form-control"
type="submit" placeholder={t('form_enter_rate_limit')}
className="btn btn-success btn-standard btn-large" normalize={toNumber}
disabled={submitting || invalid || processing} validate={[validateRequiredValue, validateBiggerOrEqualZeroValue]}
> />
<Trans>save_btn</Trans> </div>
</button> </div>
</form>; {checkboxes.map(({ name, placeholder, subtitle }) => <div className="col-12" key={name}>
<div className="form__group form__group--settings">
<Field
name={name}
type="checkbox"
component={renderSelectField}
placeholder={t(placeholder)}
disabled={processing}
subtitle={t(subtitle)}
/>
</div>
</div>)}
<div className="col-12">
<div className="form__group form__group--settings mb-4">
<label className="form__label form__label--with-desc">
<Trans>blocking_mode</Trans>
</label>
<div className="form__desc form__desc--top">
{Object.values(BLOCKING_MODES)
.map((mode) => (
<li key={mode}>
<Trans>{`blocking_mode_${mode}`}</Trans>
</li>
))}
</div>
<div className="custom-controls-stacked">
{getFields(processing, t)}
</div>
</div>
</div>
{blocking_mode === BLOCKING_MODES.custom_ip && (
<>
{customIps.map(({
description,
name,
validateIp,
}) => <div className="col-12 col-sm-6" key={name}>
<div className="form__group form__group--settings">
<label className="form__label form__label--with-desc"
htmlFor={name}><Trans>{name}</Trans>
</label>
<div className="form__desc form__desc--top">
<Trans>{description}</Trans>
</div>
<Field
name={name}
component={renderInputField}
className="form-control"
placeholder={t('form_enter_ip')}
validate={[validateIp, validateRequiredValue]}
/>
</div>
</div>)}
</>
)}
</div>
<button
type="submit"
className="btn btn-success btn-standard btn-large"
disabled={submitting || invalid || processing}
>
<Trans>save_btn</Trans>
</button>
</form>;
};
Form.propTypes = { Form.propTypes = {
blockingMode: PropTypes.string.isRequired,
handleSubmit: PropTypes.func.isRequired, handleSubmit: PropTypes.func.isRequired,
submitting: PropTypes.bool.isRequired, submitting: PropTypes.bool.isRequired,
invalid: PropTypes.bool.isRequired, invalid: PropTypes.bool.isRequired,
processing: PropTypes.bool.isRequired, processing: PropTypes.bool.isRequired,
t: PropTypes.func.isRequired,
}; };
const selector = formValueSelector(FORM_NAME.BLOCKING_MODE); export default reduxForm({ form: FORM_NAME.BLOCKING_MODE })(Form);
Form = connect((state) => {
const blockingMode = selector(state, 'blocking_mode');
return {
blockingMode,
};
})(Form);
export default flow([
withTranslation(),
reduxForm({ form: FORM_NAME.BLOCKING_MODE }),
])(Form);

View File

@ -1,15 +1,13 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import { useTranslation } from 'react-i18next';
import { withTranslation } from 'react-i18next'; import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import Card from '../../../ui/Card'; import Card from '../../../ui/Card';
import Form from './Form'; import Form from './Form';
import { setDnsConfig } from '../../../../actions/dnsConfig';
const Config = ({ t, dnsConfig, setDnsConfig }) => { const Config = () => {
const handleFormSubmit = (values) => { const { t } = useTranslation();
setDnsConfig(values); const dispatch = useDispatch();
};
const { const {
blocking_mode, blocking_mode,
ratelimit, ratelimit,
@ -19,7 +17,11 @@ const Config = ({ t, dnsConfig, setDnsConfig }) => {
dnssec_enabled, dnssec_enabled,
disable_ipv6, disable_ipv6,
processingSetConfig, processingSetConfig,
} = dnsConfig; } = useSelector((state) => state.dnsConfig, shallowEqual);
const handleFormSubmit = (values) => {
dispatch(setDnsConfig(values));
};
return ( return (
<Card <Card
@ -46,10 +48,4 @@ const Config = ({ t, dnsConfig, setDnsConfig }) => {
); );
}; };
Config.propTypes = { export default Config;
dnsConfig: PropTypes.object.isRequired,
setDnsConfig: PropTypes.func.isRequired,
t: PropTypes.func.isRequired,
};
export default withTranslation()(Config);

View File

@ -1,29 +1,26 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux'; import { shallowEqual, useDispatch } from 'react-redux';
import Form from './Form'; import Form from './Form';
import Card from '../../../ui/Card'; import Card from '../../../ui/Card';
import { setDnsConfig } from '../../../../actions/dnsConfig'; import { setDnsConfig } from '../../../../actions/dnsConfig';
const Upstream = (props) => { const Upstream = () => {
const [t] = useTranslation(); const { t } = useTranslation();
const dispatch = useDispatch(); const dispatch = useDispatch();
const {
upstream_dns,
bootstrap_dns,
upstream_mode,
processingSetConfig,
} = ((state) => state.dnsConfig, shallowEqual);
const { processingTestUpstream } = ((state) => state.settings, shallowEqual);
const handleSubmit = (values) => { const handleSubmit = (values) => {
dispatch(setDnsConfig(values)); dispatch(setDnsConfig(values));
}; };
const {
processingTestUpstream,
dnsConfig: {
upstream_dns,
bootstrap_dns,
processingSetConfig,
upstream_mode,
},
} = props;
return ( return (
<Card <Card
title={t('upstream_dns')} title={t('upstream_dns')}
@ -48,9 +45,4 @@ const Upstream = (props) => {
); );
}; };
Upstream.propTypes = {
processingTestUpstream: PropTypes.bool.isRequired,
dnsConfig: PropTypes.object.isRequired,
};
export default Upstream; export default Upstream;

View File

@ -1,67 +1,40 @@
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
import Upstream from './Upstream'; import Upstream from './Upstream';
import Access from './Access'; import Access from './Access';
import Config from './Config'; import Config from './Config';
import PageTitle from '../../ui/PageTitle'; import PageTitle from '../../ui/PageTitle';
import Loading from '../../ui/Loading'; import Loading from '../../ui/Loading';
import CacheConfig from './Cache'; import CacheConfig from './Cache';
import { getDnsConfig } from '../../../actions/dnsConfig';
import { getAccessList } from '../../../actions/access';
const Dns = (props) => { const Dns = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useDispatch();
const processing = useSelector((state) => state.access.processing);
const processingGetConfig = useSelector((state) => state.dnsConfig.processingGetConfig);
const isDataLoading = processing || processingGetConfig;
useEffect(() => { useEffect(() => {
props.getAccessList(); dispatch(getAccessList());
props.getDnsConfig(); dispatch(getDnsConfig());
}, []); }, []);
const { return <>
settings, <PageTitle title={t('dns_settings')} />
access, {isDataLoading
setAccessList, ? <Loading />
dnsConfig, : <>
setDnsConfig, <Upstream />
} = props; <Config />
<CacheConfig />
const isDataLoading = access.processing || dnsConfig.processingGetConfig; <Access />
</>}
return ( </>;
<>
<PageTitle title={t('dns_settings')} />
{isDataLoading
? <Loading />
: <>
<Upstream
processingTestUpstream={settings.processingTestUpstream}
dnsConfig={dnsConfig}
/>
<Config
dnsConfig={dnsConfig}
setDnsConfig={setDnsConfig}
/>
<CacheConfig
dnsConfig={dnsConfig}
setDnsConfig={setDnsConfig}
/>
<Access
access={access}
setAccessList={setAccessList}
/>
</>}
</>
);
};
Dns.propTypes = {
settings: PropTypes.object.isRequired,
getAccessList: PropTypes.func.isRequired,
setAccessList: PropTypes.func.isRequired,
access: PropTypes.object.isRequired,
dnsConfig: PropTypes.object.isRequired,
setDnsConfig: PropTypes.func.isRequired,
getDnsConfig: PropTypes.func.isRequired,
}; };
export default Dns; export default Dns;

View File

@ -1,19 +1,20 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import { Trans } from 'react-i18next';
import { Trans, withTranslation } from 'react-i18next';
import isAfter from 'date-fns/is_after'; import isAfter from 'date-fns/is_after';
import addDays from 'date-fns/add_days'; import addDays from 'date-fns/add_days';
import { useSelector } from 'react-redux';
import Topline from './Topline'; import Topline from './Topline';
import { EMPTY_DATE } from '../../helpers/constants'; import { EMPTY_DATE } from '../../helpers/constants';
const EncryptionTopline = (props) => { const EncryptionTopline = () => {
if (props.notAfter === EMPTY_DATE) { const not_after = useSelector((state) => state.encryption.not_after);
return false;
if (not_after === EMPTY_DATE) {
return null;
} }
const isAboutExpire = isAfter(addDays(Date.now(), 30), props.notAfter); const isAboutExpire = isAfter(addDays(Date.now(), 30), not_after);
const isExpired = isAfter(Date.now(), props.notAfter); const isExpired = isAfter(Date.now(), not_after);
if (isExpired) { if (isExpired) {
return ( return (
@ -23,7 +24,9 @@ const EncryptionTopline = (props) => {
</Trans> </Trans>
</Topline> </Topline>
); );
} if (isAboutExpire) { }
if (isAboutExpire) {
return ( return (
<Topline type="warning"> <Topline type="warning">
<Trans components={[<a href="#encryption" key="0">link</a>]}> <Trans components={[<a href="#encryption" key="0">link</a>]}>
@ -36,8 +39,4 @@ const EncryptionTopline = (props) => {
return false; return false;
}; };
EncryptionTopline.propTypes = { export default EncryptionTopline;
notAfter: PropTypes.string.isRequired,
};
export default withTranslation()(EncryptionTopline);

View File

@ -1,5 +1,4 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import classNames from 'classnames'; import classNames from 'classnames';
@ -28,7 +27,7 @@ const linksData = [
}, },
]; ];
const Footer = (props) => { const Footer = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const getYear = () => { const getYear = () => {
@ -59,11 +58,6 @@ const Footer = (props) => {
{t(name)} {t(name)}
</a>); </a>);
const {
dnsVersion, processingVersion, getVersion, checkUpdateFlag,
} = props;
return ( return (
<> <>
<footer className="footer"> <footer className="footer">
@ -94,12 +88,7 @@ const Footer = (props) => {
<div className="footer__row"> <div className="footer__row">
{renderCopyright()} {renderCopyright()}
<div className="footer__column footer__column--language"> <div className="footer__column footer__column--language">
<Version <Version />
dnsVersion={dnsVersion}
processingVersion={processingVersion}
getVersion={getVersion}
checkUpdateFlag={checkUpdateFlag}
/>
</div> </div>
</div> </div>
</div> </div>
@ -108,11 +97,4 @@ const Footer = (props) => {
); );
}; };
Footer.propTypes = {
dnsVersion: PropTypes.string,
processingVersion: PropTypes.bool,
getVersion: PropTypes.func,
checkUpdateFlag: PropTypes.bool,
};
export default Footer; export default Footer;

View File

@ -1,9 +1,14 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import './Loading.css'; import './Loading.css';
const Loading = () => ( const Loading = ({ className }) => (
<div className="loading" /> <div className={classNames('loading', className)} />
); );
Loading.propTypes = {
className: PropTypes.string,
};
export default Loading; export default Loading;

View File

@ -1,14 +1,13 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import { Trans } from 'react-i18next';
import { Trans, withTranslation } from 'react-i18next';
import classnames from 'classnames'; import classnames from 'classnames';
import { useSelector } from 'react-redux';
import './Overlay.css'; import './Overlay.css';
const UpdateOverlay = (props) => { const UpdateOverlay = () => {
const overlayClass = classnames({ const processingUpdate = useSelector((state) => state.dashboard.processingUpdate);
overlay: true, const overlayClass = classnames('overlay', {
'overlay--visible': props.processingUpdate, 'overlay--visible': processingUpdate,
}); });
return ( return (
@ -19,8 +18,4 @@ const UpdateOverlay = (props) => {
); );
}; };
UpdateOverlay.propTypes = { export default UpdateOverlay;
processingUpdate: PropTypes.bool,
};
export default withTranslation()(UpdateOverlay);

View File

@ -1,42 +1,46 @@
import React, { Fragment } from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import { Trans } from 'react-i18next';
import { Trans, withTranslation } from 'react-i18next'; import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import Topline from './Topline'; import Topline from './Topline';
import { getUpdate } from '../../actions';
const UpdateTopline = (props) => ( const UpdateTopline = () => {
<Topline type="info"> const {
<Fragment> announcementUrl,
newVersion,
canAutoUpdate,
processingUpdate,
} = useSelector((state) => state.dashboard, shallowEqual);
const dispatch = useDispatch();
const handleUpdate = () => {
dispatch(getUpdate());
};
return <Topline type="info">
<>
<Trans <Trans
values={{ version: props.version }} values={{ version: newVersion }}
components={[ components={[
<a href={props.url} target="_blank" rel="noopener noreferrer" key="0"> <a href={announcementUrl} target="_blank" rel="noopener noreferrer" key="0">
Click here Click here
</a>, </a>,
]} ]}
> >
update_announcement update_announcement
</Trans> </Trans>
{props.canAutoUpdate {canAutoUpdate
&& <button && <button
type="button" type="button"
className="btn btn-sm btn-primary ml-3" className="btn btn-sm btn-primary ml-3"
onClick={props.getUpdate} onClick={handleUpdate}
disabled={props.processingUpdate} disabled={processingUpdate}
> >
<Trans>update_now</Trans> <Trans>update_now</Trans>
</button> </button>
} }
</Fragment> </>
</Topline> </Topline>;
);
UpdateTopline.propTypes = {
version: PropTypes.string,
url: PropTypes.string.isRequired,
canAutoUpdate: PropTypes.bool,
getUpdate: PropTypes.func,
processingUpdate: PropTypes.bool,
}; };
export default withTranslation()(UpdateTopline); export default UpdateTopline;

View File

@ -1,13 +1,21 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import { Trans, useTranslation } from 'react-i18next';
import { Trans, withTranslation } from 'react-i18next'; import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import { getVersion } from '../../actions';
import './Version.css'; import './Version.css';
const Version = (props) => { const Version = () => {
const dispatch = useDispatch();
const { t } = useTranslation();
const { const {
dnsVersion, processingVersion, t, checkUpdateFlag, dnsVersion,
} = props; processingVersion,
checkUpdateFlag,
} = useSelector((state) => state?.dashboard ?? {}, shallowEqual);
const onClick = () => {
dispatch(getVersion(true));
};
return ( return (
<div className="version"> <div className="version">
@ -20,7 +28,7 @@ const Version = (props) => {
{checkUpdateFlag && <button {checkUpdateFlag && <button
type="button" type="button"
className="btn btn-icon btn-icon-sm btn-outline-primary btn-sm ml-2" className="btn btn-icon btn-icon-sm btn-outline-primary btn-sm ml-2"
onClick={() => props.getVersion(true)} onClick={onClick}
disabled={processingVersion} disabled={processingVersion}
title={t('check_updates_now')} title={t('check_updates_now')}
> >
@ -33,12 +41,4 @@ const Version = (props) => {
); );
}; };
Version.propTypes = { export default Version;
dnsVersion: PropTypes.string,
getVersion: PropTypes.func,
processingVersion: PropTypes.bool,
checkUpdateFlag: PropTypes.bool,
t: PropTypes.func.isRequired,
};
export default withTranslation()(Version);

View File

@ -1 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#9aa0ac" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-chevron-down"><polyline points="6 9 12 15 18 9"></polyline></svg> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#9aa0ac"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-chevron-down">
<polyline points="6 9 12 15 18 9"></polyline>
</svg>

Before

Width:  |  Height:  |  Size: 264 B

After

Width:  |  Height:  |  Size: 276 B

View File

@ -1 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#9aa0ac" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-globe"><circle cx="12" cy="12" r="10"/><path d="M2 12h20M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#9aa0ac"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-globe">
<circle cx="12" cy="12" r="10"/>
<path d="M2 12h20M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/>
</svg>

Before

Width:  |  Height:  |  Size: 354 B

After

Width:  |  Height:  |  Size: 371 B

View File

@ -1 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#9aa0ac" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-help-circle"><circle cx="12" cy="12" r="10"></circle><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path><line x1="12" y1="17" x2="12" y2="17"></line></svg> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#9aa0ac"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-help-circle">
<circle cx="12" cy="12" r="10"></circle>
<path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path>
<line x1="12" y1="17" x2="12" y2="17"></line>
</svg>

Before

Width:  |  Height:  |  Size: 357 B

After

Width:  |  Height:  |  Size: 379 B

View File

@ -1 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" width="164" height="41" viewBox="0 0 164 41"><g fill-rule="evenodd"><path d="M129.984 22l-1.162-2.945h-5.792L121.931 22H118l6.277-15h3.509L134 22h-4.016zm-4.016-10.996l-1.902 5.149h3.762l-1.86-5.149zM117 16.1c0 .88-.153 1.682-.46 2.404a5.223 5.223 0 0 1-1.318 1.857c-.57.516-1.26.918-2.066 1.207-.807.289-1.703.433-2.688.433-1 0-1.9-.144-2.699-.433-.8-.29-1.477-.691-2.034-1.207a5.232 5.232 0 0 1-1.285-1.857c-.3-.722-.45-1.524-.45-2.404V7h3.64v8.81c0 .4.054.777.161 1.135.108.358.272.677.493.96.221.281.514.505.878.67.364.165.803.248 1.317.248.514 0 .953-.083 1.317-.248.365-.165.66-.389.89-.67.228-.283.392-.602.492-.96.1-.358.15-.736.15-1.135V7H117v9.099zm-16 4.673c-.733.362-1.59.658-2.57.886-.98.228-2.047.342-3.203.342-1.199 0-2.302-.181-3.31-.544-1.008-.362-1.875-.872-2.601-1.53a6.977 6.977 0 0 1-1.703-2.366c-.409-.92-.613-1.943-.613-3.07 0-1.141.208-2.175.624-3.1a6.903 6.903 0 0 1 1.723-2.367 7.71 7.71 0 0 1 2.58-1.5C92.914 7.174 93.98 7 95.121 7c1.184 0 2.284.171 3.299.513 1.015.343 1.84.802 2.474 1.38l-2.284 2.476c-.352-.39-.817-.708-1.395-.956-.579-.249-1.234-.373-1.967-.373-.635 0-1.22.111-1.756.332a4.23 4.23 0 0 0-1.395.927 4.178 4.178 0 0 0-.92 1.41 4.734 4.734 0 0 0-.328 1.78c0 .659.099 1.263.296 1.813.197.55.49 1.024.878 1.42.387.395.867.704 1.438.926.57.221 1.223.332 1.956.332.423 0 .825-.03 1.205-.09.381-.061.733-.158 1.058-.293V16h-2.855v-2.779H101v7.55zm63-6.314c0 1.313-.244 2.447-.73 3.4a6.855 6.855 0 0 1-1.928 2.352 8.035 8.035 0 0 1-2.7 1.356 10.94 10.94 0 0 1-3.05.434H150V7h5.422c1.06 0 2.104.124 3.135.37a7.866 7.866 0 0 1 2.753 1.23c.805.572 1.454 1.338 1.949 2.298.494.96.741 2.147.741 3.56zm-3.77 0c0-.848-.138-1.55-.413-2.108a3.549 3.549 0 0 0-1.101-1.335 4.405 4.405 0 0 0-1.568-.71 7.7 7.7 0 0 0-1.81-.212h-1.8v8.771h1.715c.65 0 1.274-.074 1.874-.222a4.43 4.43 0 0 0 1.589-.731 3.62 3.62 0 0 0 1.1-1.356c.276-.565.414-1.264.414-2.097zm-75.23 0c0 1.313-.244 2.447-.73 3.4a6.855 6.855 0 0 1-1.928 2.352 8.035 8.035 0 0 1-2.7 1.356 10.94 10.94 0 0 1-3.05.434H71V7h5.422c1.06 0 2.104.124 3.135.37A7.866 7.866 0 0 1 82.31 8.6c.805.572 1.454 1.338 1.949 2.298.494.96.741 2.147.741 3.56zm-3.77 0c0-.848-.138-1.55-.413-2.108a3.549 3.549 0 0 0-1.101-1.335 4.405 4.405 0 0 0-1.568-.71 7.7 7.7 0 0 0-1.81-.212h-1.8v8.771h1.715c.65 0 1.274-.074 1.874-.222a4.43 4.43 0 0 0 1.589-.731 3.62 3.62 0 0 0 1.1-1.356c.276-.565.414-1.264.414-2.097zM65.984 22l-1.162-2.945H59.03L57.931 22H54l6.277-15h3.509L70 22h-4.016zm-4.016-10.996l-1.902 5.149h3.762l-1.86-5.149zM143.855 22l-3.171-5.953h-1.202V22H136V7h5.596c.705 0 1.392.074 2.062.222.67.149 1.271.4 1.803.753a3.9 3.9 0 0 1 1.275 1.398c.318.579.476 1.3.476 2.16 0 1.018-.269 1.872-.808 2.564-.539.693-1.285 1.187-2.238 1.484L148 22h-4.145zm-.145-10.403c0-.353-.073-.639-.218-.858a1.502 1.502 0 0 0-.56-.508 2.393 2.393 0 0 0-.766-.244 5.535 5.535 0 0 0-.819-.063h-1.886v3.495h1.679c.29 0 .587-.024.891-.074.304-.05.58-.137.83-.264.248-.128.452-.311.61-.551.16-.24.239-.551.239-.933zM55 37.851v-8.702h.951v3.866h4.866V29.15h.952v8.702h-.952v-3.916h-4.866v3.916H55zM68.068 38c-2.565 0-4.288-2.076-4.288-4.5 0-2.4 1.747-4.5 4.312-4.5 2.565 0 4.288 2.076 4.288 4.5 0 2.4-1.747 4.5-4.312 4.5zm.024-.907c1.927 0 3.3-1.592 3.3-3.593 0-1.977-1.397-3.593-3.324-3.593-1.927 0-3.3 1.592-3.3 3.593 0 1.977 1.397 3.593 3.324 3.593zm6.3.758v-8.702h.963l3.07 4.749 3.072-4.749h.964v8.702h-.952v-7.049l-3.071 4.662h-.048l-3.071-4.65v7.037h-.928zm10.453 0v-8.702h6.095v.895h-5.143v2.971h4.6v.895h-4.6v3.046H91v.895h-6.155z"/><path fill-rule="nonzero" d="M2.831 14.045c.775 4.287 2.266 8.333 4.685 12.143 2.958 4.659 7.21 8.797 12.984 12.319 5.774-3.522 10.026-7.66 12.984-12.319 2.42-3.81 3.91-7.856 4.685-12.143.489-2.706.644-4.844.672-8.003C33.368 3.522 26.636 2.14 20.5 2.14c-6.137 0-12.869 1.381-18.341 3.9.028 3.16.183 5.298.672 8.004zM20.5 0C26.908 0 34.637 1.47 41 4.706c0 6.988.087 24.398-20.5 36.294C-.088 29.104 0 11.694 0 4.706 6.363 1.47 14.092 0 20.5 0z"/><path d="M20.234 27L33 11.344c-.935-.682-1.756-.2-2.208.172l-.016.001-10.644 10.076-4.01-4.392c-1.913-2.011-4.514-.477-5.122-.072L20.234 27"/></g></svg> <svg xmlns="http://www.w3.org/2000/svg" width="164" height="41" viewBox="0 0 164 41">
<g fill-rule="evenodd">
<path d="M129.984 22l-1.162-2.945h-5.792L121.931 22H118l6.277-15h3.509L134 22h-4.016zm-4.016-10.996l-1.902 5.149h3.762l-1.86-5.149zM117 16.1c0 .88-.153 1.682-.46 2.404a5.223 5.223 0 0 1-1.318 1.857c-.57.516-1.26.918-2.066 1.207-.807.289-1.703.433-2.688.433-1 0-1.9-.144-2.699-.433-.8-.29-1.477-.691-2.034-1.207a5.232 5.232 0 0 1-1.285-1.857c-.3-.722-.45-1.524-.45-2.404V7h3.64v8.81c0 .4.054.777.161 1.135.108.358.272.677.493.96.221.281.514.505.878.67.364.165.803.248 1.317.248.514 0 .953-.083 1.317-.248.365-.165.66-.389.89-.67.228-.283.392-.602.492-.96.1-.358.15-.736.15-1.135V7H117v9.099zm-16 4.673c-.733.362-1.59.658-2.57.886-.98.228-2.047.342-3.203.342-1.199 0-2.302-.181-3.31-.544-1.008-.362-1.875-.872-2.601-1.53a6.977 6.977 0 0 1-1.703-2.366c-.409-.92-.613-1.943-.613-3.07 0-1.141.208-2.175.624-3.1a6.903 6.903 0 0 1 1.723-2.367 7.71 7.71 0 0 1 2.58-1.5C92.914 7.174 93.98 7 95.121 7c1.184 0 2.284.171 3.299.513 1.015.343 1.84.802 2.474 1.38l-2.284 2.476c-.352-.39-.817-.708-1.395-.956-.579-.249-1.234-.373-1.967-.373-.635 0-1.22.111-1.756.332a4.23 4.23 0 0 0-1.395.927 4.178 4.178 0 0 0-.92 1.41 4.734 4.734 0 0 0-.328 1.78c0 .659.099 1.263.296 1.813.197.55.49 1.024.878 1.42.387.395.867.704 1.438.926.57.221 1.223.332 1.956.332.423 0 .825-.03 1.205-.09.381-.061.733-.158 1.058-.293V16h-2.855v-2.779H101v7.55zm63-6.314c0 1.313-.244 2.447-.73 3.4a6.855 6.855 0 0 1-1.928 2.352 8.035 8.035 0 0 1-2.7 1.356 10.94 10.94 0 0 1-3.05.434H150V7h5.422c1.06 0 2.104.124 3.135.37a7.866 7.866 0 0 1 2.753 1.23c.805.572 1.454 1.338 1.949 2.298.494.96.741 2.147.741 3.56zm-3.77 0c0-.848-.138-1.55-.413-2.108a3.549 3.549 0 0 0-1.101-1.335 4.405 4.405 0 0 0-1.568-.71 7.7 7.7 0 0 0-1.81-.212h-1.8v8.771h1.715c.65 0 1.274-.074 1.874-.222a4.43 4.43 0 0 0 1.589-.731 3.62 3.62 0 0 0 1.1-1.356c.276-.565.414-1.264.414-2.097zm-75.23 0c0 1.313-.244 2.447-.73 3.4a6.855 6.855 0 0 1-1.928 2.352 8.035 8.035 0 0 1-2.7 1.356 10.94 10.94 0 0 1-3.05.434H71V7h5.422c1.06 0 2.104.124 3.135.37A7.866 7.866 0 0 1 82.31 8.6c.805.572 1.454 1.338 1.949 2.298.494.96.741 2.147.741 3.56zm-3.77 0c0-.848-.138-1.55-.413-2.108a3.549 3.549 0 0 0-1.101-1.335 4.405 4.405 0 0 0-1.568-.71 7.7 7.7 0 0 0-1.81-.212h-1.8v8.771h1.715c.65 0 1.274-.074 1.874-.222a4.43 4.43 0 0 0 1.589-.731 3.62 3.62 0 0 0 1.1-1.356c.276-.565.414-1.264.414-2.097zM65.984 22l-1.162-2.945H59.03L57.931 22H54l6.277-15h3.509L70 22h-4.016zm-4.016-10.996l-1.902 5.149h3.762l-1.86-5.149zM143.855 22l-3.171-5.953h-1.202V22H136V7h5.596c.705 0 1.392.074 2.062.222.67.149 1.271.4 1.803.753a3.9 3.9 0 0 1 1.275 1.398c.318.579.476 1.3.476 2.16 0 1.018-.269 1.872-.808 2.564-.539.693-1.285 1.187-2.238 1.484L148 22h-4.145zm-.145-10.403c0-.353-.073-.639-.218-.858a1.502 1.502 0 0 0-.56-.508 2.393 2.393 0 0 0-.766-.244 5.535 5.535 0 0 0-.819-.063h-1.886v3.495h1.679c.29 0 .587-.024.891-.074.304-.05.58-.137.83-.264.248-.128.452-.311.61-.551.16-.24.239-.551.239-.933zM55 37.851v-8.702h.951v3.866h4.866V29.15h.952v8.702h-.952v-3.916h-4.866v3.916H55zM68.068 38c-2.565 0-4.288-2.076-4.288-4.5 0-2.4 1.747-4.5 4.312-4.5 2.565 0 4.288 2.076 4.288 4.5 0 2.4-1.747 4.5-4.312 4.5zm.024-.907c1.927 0 3.3-1.592 3.3-3.593 0-1.977-1.397-3.593-3.324-3.593-1.927 0-3.3 1.592-3.3 3.593 0 1.977 1.397 3.593 3.324 3.593zm6.3.758v-8.702h.963l3.07 4.749 3.072-4.749h.964v8.702h-.952v-7.049l-3.071 4.662h-.048l-3.071-4.65v7.037h-.928zm10.453 0v-8.702h6.095v.895h-5.143v2.971h4.6v.895h-4.6v3.046H91v.895h-6.155z"/>
<path fill-rule="nonzero"
d="M2.831 14.045c.775 4.287 2.266 8.333 4.685 12.143 2.958 4.659 7.21 8.797 12.984 12.319 5.774-3.522 10.026-7.66 12.984-12.319 2.42-3.81 3.91-7.856 4.685-12.143.489-2.706.644-4.844.672-8.003C33.368 3.522 26.636 2.14 20.5 2.14c-6.137 0-12.869 1.381-18.341 3.9.028 3.16.183 5.298.672 8.004zM20.5 0C26.908 0 34.637 1.47 41 4.706c0 6.988.087 24.398-20.5 36.294C-.088 29.104 0 11.694 0 4.706 6.363 1.47 14.092 0 20.5 0z"/>
<path d="M20.234 27L33 11.344c-.935-.682-1.756-.2-2.208.172l-.016.001-10.644 10.076-4.01-4.392c-1.913-2.011-4.514-.477-5.122-.072L20.234 27"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@ -1 +1,7 @@
<svg stroke="#84868c" fill="none" height="24" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="m3 6h2 16"/><path d="m19 6v14a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2-2v-14m3 0v-2a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><path d="m10 11v6"/><path d="m14 11v6"/></svg> <svg stroke="#84868c" fill="none" height="24" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<path d="m3 6h2 16"/>
<path d="m19 6v14a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2-2v-14m3 0v-2a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>
<path d="m10 11v6"/>
<path d="m14 11v6"/>
</svg>

Before

Width:  |  Height:  |  Size: 340 B

After

Width:  |  Height:  |  Size: 367 B

View File

@ -1 +1,5 @@
<svg stroke="#84868c" fill="none" height="24" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="m18 6-12 12"/><path d="m6 6 12 12"/></svg> <svg stroke="#84868c" fill="none" height="24" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<path d="m18 6-12 12"/>
<path d="m6 6 12 12"/>
</svg>

Before

Width:  |  Height:  |  Size: 227 B

After

Width:  |  Height:  |  Size: 244 B

View File

@ -1,14 +0,0 @@
import { connect } from 'react-redux';
import * as actionCreators from '../actions';
import App from '../components/App';
const mapStateToProps = (state) => {
const { dashboard, encryption } = state;
const props = { dashboard, encryption };
return props;
};
export default connect(
mapStateToProps,
actionCreators,
)(App);

View File

@ -1,18 +0,0 @@
import { connect } from 'react-redux';
import { getVersion } from '../actions';
import Header from '../components/Header';
const mapStateToProps = (state) => {
const { dashboard } = state;
const props = { dashboard };
return props;
};
const mapDispatchToProps = {
getVersion,
};
export default connect(
mapStateToProps,
mapDispatchToProps,
)(Header);

View File

@ -363,10 +363,11 @@ export const RESPONSE_FILTER = {
}, },
}; };
export const RESPONSE_FILTER_QUERIES = Object.values(RESPONSE_FILTER).reduce((acc, { query }) => { export const RESPONSE_FILTER_QUERIES = Object.values(RESPONSE_FILTER)
acc[query] = query; .reduce((acc, { query }) => {
return acc; acc[query] = query;
}, {}); return acc;
}, {});
export const FILTERED_STATUS_TO_META_MAP = { export const FILTERED_STATUS_TO_META_MAP = {
[FILTERED_STATUS.NOT_FILTERED_WHITE_LIST]: { [FILTERED_STATUS.NOT_FILTERED_WHITE_LIST]: {

View File

@ -1,13 +1,14 @@
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import './components/App/index.css';
import App from './containers/App';
import configureStore from './configureStore'; import configureStore from './configureStore';
import reducers from './reducers'; import reducers from './reducers';
import App from './components/App';
import './components/App/index.css';
import './i18n'; import './i18n';
const store = configureStore(reducers, {}); // set initial state const store = configureStore(reducers, {}); // set initial state
ReactDOM.render( ReactDOM.render(
<Provider store={store}> <Provider store={store}>
<App /> <App />

View File

@ -5,6 +5,7 @@ const flexBugsFixes = require('postcss-flexbugs-fixes');
const { CleanWebpackPlugin } = require('clean-webpack-plugin'); const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const CopyPlugin = require('copy-webpack-plugin'); const CopyPlugin = require('copy-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const { BUILD_ENVS } = require('./constants');
const RESOURCES_PATH = path.resolve(__dirname); const RESOURCES_PATH = path.resolve(__dirname);
const ENTRY_REACT = path.resolve(RESOURCES_PATH, 'src/index.js'); const ENTRY_REACT = path.resolve(RESOURCES_PATH, 'src/index.js');
@ -18,13 +19,10 @@ const ASSETS_PATH = path.resolve(RESOURCES_PATH, 'public/assets');
const PUBLIC_PATH = path.resolve(__dirname, '../build/static'); const PUBLIC_PATH = path.resolve(__dirname, '../build/static');
const PUBLIC_ASSETS_PATH = path.resolve(PUBLIC_PATH, 'assets'); const PUBLIC_ASSETS_PATH = path.resolve(PUBLIC_PATH, 'assets');
const BUILD_ENVS = {
dev: 'development',
prod: 'production',
};
const BUILD_ENV = BUILD_ENVS[process.env.BUILD_ENV]; const BUILD_ENV = BUILD_ENVS[process.env.BUILD_ENV];
const isDev = BUILD_ENV === BUILD_ENVS.dev;
const config = { const config = {
mode: BUILD_ENV, mode: BUILD_ENV,
target: 'web', target: 'web',
@ -36,22 +34,35 @@ const config = {
}, },
output: { output: {
path: PUBLIC_PATH, path: PUBLIC_PATH,
filename: '[name].[chunkhash].js', filename: '[name].[hash].js',
}, },
resolve: { resolve: {
modules: ['node_modules'], modules: ['node_modules'],
alias: { alias: {
MainRoot: path.resolve(__dirname, '../'), MainRoot: path.resolve(__dirname, '../'),
ClientRoot: path.resolve(__dirname, './src'), ClientRoot: path.resolve(__dirname, './src'),
// TODO: change to '@hot-loader/react-dom' when v16.13.1 is released
// https://stackoverflow.com/a/62671689/12942752
'react-dom': 'react-dom',
}, },
}, },
module: { module: {
rules: [ rules: [
{
test: /\.ya?ml$/,
type: 'json',
use: 'yaml-loader',
},
{ {
test: /\.css$/i, test: /\.css$/i,
use: [ use: [
'style-loader', 'style-loader',
MiniCssExtractPlugin.loader, {
loader: MiniCssExtractPlugin.loader,
options: {
hmr: isDev,
},
},
{ {
loader: 'css-loader', loader: 'css-loader',
options: { options: {
@ -122,7 +133,8 @@ const config = {
template: HTML_LOGIN_PATH, template: HTML_LOGIN_PATH,
}), }),
new MiniCssExtractPlugin({ new MiniCssExtractPlugin({
filename: '[name].[contenthash].css', filename: isDev ? '[name].css' : '[name].[hash].css',
chunkFilename: isDev ? '[id].css' : '[id].[hash].css',
}), }),
new CopyPlugin({ new CopyPlugin({
patterns: [ patterns: [

46
client/webpack.dev.js vendored
View File

@ -1,5 +1,50 @@
const merge = require('webpack-merge'); const merge = require('webpack-merge');
const yaml = require('js-yaml');
const fs = require('fs');
const common = require('./webpack.common.js'); const common = require('./webpack.common.js');
const { BASE_URL } = require('./constants');
const ZERO_HOST = '0.0.0.0';
const LOCALHOST = '127.0.0.1';
const DEFAULT_PORT = 80;
/**
* Get document, or throw exception on error
* @returns {{bind_host: string, bind_port: number}}
*/
const importConfig = () => {
try {
const doc = yaml.safeLoad(fs.readFileSync('../AdguardHome.yaml', 'utf8'));
const { bind_host, bind_port } = doc;
return {
bind_host,
bind_port,
};
} catch (e) {
console.error(e);
return {
bind_host: ZERO_HOST,
bind_port: DEFAULT_PORT,
};
}
};
const getDevServerConfig = (proxyUrl = BASE_URL) => {
const { bind_host: host, bind_port: port } = importConfig();
const { DEV_SERVER_PORT } = process.env;
const devServerHost = host === ZERO_HOST ? LOCALHOST : host;
const devServerPort = DEV_SERVER_PORT || port + 8000;
return {
hot: true,
host: devServerHost,
port: devServerPort,
proxy: {
[proxyUrl]: `http://${devServerHost}:${port}`,
},
};
};
module.exports = merge(common, { module.exports = merge(common, {
devtool: 'eval-source-map', devtool: 'eval-source-map',
@ -16,4 +61,5 @@ module.exports = merge(common, {
}, },
], ],
}, },
devServer: process.env.WEBPACK_DEV_SERVER ? getDevServerConfig(BASE_URL) : undefined,
}); });

View File

@ -2,18 +2,19 @@ const StyleLintPlugin = require('stylelint-webpack-plugin');
const merge = require('webpack-merge'); const merge = require('webpack-merge');
const common = require('./webpack.common.js'); const common = require('./webpack.common.js');
module.exports = merge(common, { module.exports = merge(common, {
module: { module: {
rules: [{ rules: [
test: /\.js$/, {
exclude: /node_modules/, test: /\.js$/,
loader: 'eslint-loader', exclude: /node_modules/,
options: { loader: 'eslint-loader',
failOnError: true, options: {
configFile: 'prod.eslintrc', failOnError: true,
configFile: 'prod.eslintrc',
},
}, },
}], ],
}, },
plugins: [ plugins: [
new StyleLintPlugin({ new StyleLintPlugin({