all: sync with master

This commit is contained in:
Ainar Garipov 2024-07-03 15:38:37 +03:00
parent f73717ec08
commit 158d4f0249
352 changed files with 33842 additions and 33276 deletions

View File

@ -1,7 +1,7 @@
'name': 'build' 'name': 'build'
'env': 'env':
'GO_VERSION': '1.22.4' 'GO_VERSION': '1.22.5'
'NODE_VERSION': '16' 'NODE_VERSION': '16'
'on': 'on':

View File

@ -1,7 +1,7 @@
'name': 'lint' 'name': 'lint'
'env': 'env':
'GO_VERSION': '1.22.4' 'GO_VERSION': '1.22.5'
'on': 'on':
'push': 'push':

View File

@ -7,6 +7,10 @@ The format is based on
and this project adheres to and this project adheres to
[Semantic Versioning](https://semver.org/spec/v2.0.0.html). [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
<!--
TODO(a.garipov): Use the common markdown formatting tools.
-->
## [Unreleased] ## [Unreleased]
@ -14,11 +18,11 @@ and this project adheres to
<!-- <!--
## [v0.108.0] - TBA ## [v0.108.0] - TBA
## [v0.107.52] - 2024-06-29 (APPROX.) ## [v0.107.53] - 2024-07-24 (APPROX.)
See also the [v0.107.52 GitHub milestone][ms-v0.107.52]. See also the [v0.107.53 GitHub milestone][ms-v0.107.53].
[ms-v0.107.52]: https://github.com/AdguardTeam/AdGuardHome/milestone/87?closed=1 [ms-v0.107.53]: https://github.com/AdguardTeam/AdGuardHome/milestone/88?closed=1
NOTE: Add new changes BELOW THIS COMMENT. NOTE: Add new changes BELOW THIS COMMENT.
--> -->
@ -29,6 +33,61 @@ NOTE: Add new changes ABOVE THIS COMMENT.
## [v0.107.52] - 2024-07-03
See also the [v0.107.52 GitHub milestone][ms-v0.107.52].
### Security
- Go version has been updated to prevent the possibility of exploiting the Go
vulnerabilities fixed in [Go 1.22.5][go-1.22.5].
### Added
- The ability to disable logging using the new `log.enabled` configuration
property ([#7079]).
### Changed
- Frontend rewritten in TypeScript.
- The `systemd`-based service now uses `journal` for logging by default. It
also doesn't create the `/var/log/` directory anymore ([#7053]).
**NOTE:** With an installed service for changes to take effect, you need to
reinstall the service using `-r` flag of the [install script][install-script]
or via the CLI (with root privileges):
```sh
./AdGuardHome -s uninstall
./AdGuardHome -s install
```
Don't forget to backup your configuration file and other important data before
reinstalling the service.
### Deprecated
- Node 18 support, Node 20 will be required in future releases.
### Fixed
- Panic caused by missing user-specific blocked services object in configuration
file ([#7069]).
- Tracking `/etc/hosts` file changes causing panics within particular
filesystems on start ([#7076]).
[#7053]: https://github.com/AdguardTeam/AdGuardHome/issues/7053
[#7069]: https://github.com/AdguardTeam/AdGuardHome/issues/7069
[#7076]: https://github.com/AdguardTeam/AdGuardHome/issues/7076
[#7079]: https://github.com/AdguardTeam/AdGuardHome/issues/7079
[go-1.22.5]: https://groups.google.com/g/golang-announce/c/gyb7aM1C9H4
[install-script]: https://github.com/AdguardTeam/AdGuardHome/?tab=readme-ov-file#automated-install-linux-and-mac
[ms-v0.107.52]: https://github.com/AdguardTeam/AdGuardHome/milestone/87?closed=1
## [v0.107.51] - 2024-06-06 ## [v0.107.51] - 2024-06-06
See also the [v0.107.51 GitHub milestone][ms-v0.107.51]. See also the [v0.107.51 GitHub milestone][ms-v0.107.51].
@ -3008,11 +3067,12 @@ See also the [v0.104.2 GitHub milestone][ms-v0.104.2].
<!-- <!--
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.52...HEAD [Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.53...HEAD
[v0.107.52]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.51...v0.107.52 [v0.107.53]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.52...v0.107.53
--> -->
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.51...HEAD [Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.52...HEAD
[v0.107.52]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.51...v0.107.52
[v0.107.51]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.50...v0.107.51 [v0.107.51]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.50...v0.107.51
[v0.107.50]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.49...v0.107.50 [v0.107.50]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.49...v0.107.50
[v0.107.49]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.48...v0.107.49 [v0.107.49]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.48...v0.107.49

View File

@ -27,7 +27,7 @@ DIST_DIR = dist
GOAMD64 = v1 GOAMD64 = v1
GOPROXY = https://goproxy.cn|https://proxy.golang.org|direct GOPROXY = https://goproxy.cn|https://proxy.golang.org|direct
GOSUMDB = sum.golang.google.cn GOSUMDB = sum.golang.google.cn
GOTOOLCHAIN = go1.22.4 GOTOOLCHAIN = go1.22.5
GPG_KEY = devteam@adguard.com GPG_KEY = devteam@adguard.com
GPG_KEY_PASSPHRASE = not-a-real-password GPG_KEY_PASSPHRASE = not-a-real-password
NPM = npm NPM = npm

View File

@ -206,9 +206,8 @@ Run `make init` to prepare the development environment.
You will need this to build AdGuard Home: You will need this to build AdGuard Home:
- [Go](https://golang.org/dl/) v1.22 or later; - [Go](https://golang.org/dl/) v1.22 or later;
- [Node.js](https://nodejs.org/en/download/) v16 or later; - [Node.js](https://nodejs.org/en/download/) v18.18 or later;
- [npm](https://www.npmjs.com/) v8 or later; - [npm](https://www.npmjs.com/) v8 or later;
- [yarn](https://yarnpkg.com/) v1.22.5 or later.
### <a href="#building" id="building" name="building">Building</a> ### <a href="#building" id="building" name="building">Building</a>
@ -220,14 +219,6 @@ cd AdGuardHome
make make
``` ```
#### <a href="#building-node" id="building-node" name="building-node">Building with Node.js 17 and later</a>
In order to build AdGuard Home with Node.js 17 and later, specify `--openssl-legacy-provider` option.
```sh
export NODE_OPTIONS=--openssl-legacy-provider
```
> [!WARNING] > [!WARNING]
> 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`. > 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`.

View File

@ -7,8 +7,8 @@
# Make sure to sync any changes with the branch overrides below. # Make sure to sync any changes with the branch overrides below.
'variables': 'variables':
'channel': 'edge' 'channel': 'edge'
'dockerFrontend': 'adguard/home-js-builder:1.1' 'dockerFrontend': 'adguard/home-js-builder:2.0'
'dockerGo': '${bamboo.adguardRegistryBasePath}/go-builder:1.22.4--1' 'dockerGo': 'adguard/go-builder:1.22.5--1'
'stages': 'stages':
- 'Build frontend': - 'Build frontend':
@ -265,8 +265,8 @@
# need to build a few of these. # need to build a few of these.
'variables': 'variables':
'channel': 'beta' 'channel': 'beta'
'dockerFrontend': 'adguard/home-js-builder:1.1' 'dockerFrontend': '${bamboo.adguardRegistryBasePath}/home-js-builder:2.0'
'dockerGo': '${bamboo.adguardRegistryBasePath}/go-builder:1.22.4--1' 'dockerGo': '${bamboo.adguardRegistryBasePath}/go-builder:1.22.5--1'
# release-vX.Y.Z branches are the branches from which the actual final # release-vX.Y.Z branches are the branches from which the actual final
# release is built. # release is built.
- '^release-v[0-9]+\.[0-9]+\.[0-9]+': - '^release-v[0-9]+\.[0-9]+\.[0-9]+':
@ -281,5 +281,5 @@
# are the ones that actually get released. # are the ones that actually get released.
'variables': 'variables':
'channel': 'release' 'channel': 'release'
'dockerFrontend': 'adguard/home-js-builder:1.1' 'dockerFrontend': '${bamboo.adguardRegistryBasePath}/home-js-builder:2.0'
'dockerGo': '${bamboo.adguardRegistryBasePath}/go-builder:1.22.4--1' 'dockerGo': '${bamboo.adguardRegistryBasePath}/go-builder:1.22.5--1'

View File

@ -5,8 +5,8 @@
'key': 'AHBRTSPECS' 'key': 'AHBRTSPECS'
'name': 'AdGuard Home - Build and run tests' 'name': 'AdGuard Home - Build and run tests'
'variables': 'variables':
'dockerFrontend': 'adguard/home-js-builder:1.1' 'dockerFrontend': 'adguard/home-js-builder:2.0'
'dockerGo': '${bamboo.adguardRegistryBasePath}/go-builder:1.22.4--1' 'dockerGo': 'adguard/go-builder:1.22.5--1'
'channel': 'development' 'channel': 'development'
'stages': 'stages':
@ -194,6 +194,6 @@
# Set the default release channel on the release branch to beta, as we # Set the default release channel on the release branch to beta, as we
# may need to build a few of these. # may need to build a few of these.
'variables': 'variables':
'dockerFrontend': 'adguard/home-js-builder:1.1' 'dockerFrontend': '${bamboo.adguardRegistryBasePath}/home-js-builder:2.0'
'dockerGo': '${bamboo.adguardRegistryBasePath}/go-builder:1.22.4--1' 'dockerGo': '${bamboo.adguardRegistryBasePath}/go-builder:1.22.5--1'
'channel': 'candidate' 'channel': 'candidate'

60
client/.eslintrc.json vendored
View File

@ -1,9 +1,13 @@
{ {
"parser": "babel-eslint", "plugins": ["prettier"],
"extends": [ "extends": [
"airbnb-base",
"prettier",
"eslint:recommended",
"plugin:react/recommended", "plugin:react/recommended",
"airbnb-base" "plugin:@typescript-eslint/recommended"
], ],
"parser": "@typescript-eslint/parser",
"env": { "env": {
"jest": true, "jest": true,
"node": true, "node": true,
@ -16,50 +20,21 @@
"version": "16.4" "version": "16.4"
}, },
"import/resolver": { "import/resolver": {
"webpack": { "node": {
"config": "webpack.common.js" "extensions": [".js", ".jsx", ".ts", ".tsx"]
} }
} }
}, },
"rules": { "rules": {
"indent": [ "@typescript-eslint/no-explicit-any": "off",
"import/extensions": [
"error", "error",
4, "ignorePackages",
{ {
"SwitchCase": 1, "js": "never",
"VariableDeclarator": 1, "jsx": "never",
"outerIIFEBody": 1, "ts": "never",
"FunctionDeclaration": { "tsx": "never"
"parameters": 1,
"body": 1
},
"FunctionExpression": {
"parameters": 1,
"body": 1
},
"CallExpression": {
"arguments": 1
},
"ArrayExpression": 1,
"ObjectExpression": 1,
"ImportDeclaration": 1,
"flatTernaryExpressions": false,
"ignoredNodes": [
"JSXElement",
"JSXElement > *",
"JSXAttribute",
"JSXIdentifier",
"JSXNamespacedName",
"JSXMemberExpression",
"JSXSpreadAttribute",
"JSXExpressionContainer",
"JSXOpeningElement",
"JSXClosingElement",
"JSXText",
"JSXEmptyExpression",
"JSXSpreadChild"
],
"ignoreComments": false
} }
], ],
"class-methods-use-this": "off", "class-methods-use-this": "off",
@ -68,10 +43,7 @@
"no-console": [ "no-console": [
"warn", "warn",
{ {
"allow": [ "allow": ["warn", "error"]
"warn",
"error"
]
} }
], ],
"import/no-extraneous-dependencies": [ "import/no-extraneous-dependencies": [

View File

@ -1 +1 @@
*.js text eol=lf *.ts text eol=lf

10
client/.prettierrc vendored Normal file
View File

@ -0,0 +1,10 @@
{
"printWidth": 120,
"singleQuote": true,
"trailingComma": "all",
"bracketSpacing": true,
"bracketSameLine": true,
"tabWidth": 4,
"semi": true,
"arrowParens": "always",
}

46
client/.stylelintrc vendored
View File

@ -1,46 +0,0 @@
{
"defaultSeverity": "warning",
"rules": {
"block-closing-brace-empty-line-before": "never",
"block-no-empty": true,
"block-opening-brace-newline-after": "always",
"block-opening-brace-space-before": "always",
"color-hex-case": "lower",
"color-named": "never",
"color-no-invalid-hex": true,
"length-zero-no-unit": true,
"declaration-block-trailing-semicolon": "always",
"custom-property-empty-line-before": ["always", {
"except": [
"after-custom-property",
"first-nested"
]
}],
"declaration-block-no-duplicate-properties": true,
"declaration-colon-space-after": "always",
"declaration-empty-line-before": ["always", {
"except": [
"after-declaration",
"first-nested",
"after-comment"
]
}],
"font-weight-notation": "numeric",
"indentation": [4, {
"except": ["value"]
}],
"max-empty-lines": 2,
"no-missing-end-of-source-newline": true,
"number-leading-zero": "always",
"property-no-unknown": [true, {
"ignoreProperties": "/lost-.+/"
}],
"rule-empty-line-before": [ "always-multi-line", {
"except": ["first-nested"],
"ignore": ["after-comment"]
}],
"string-quotes": "double",
"value-list-comma-space-after": "always",
"unit-case": "lower"
}
}

44
client/.stylelintrc.js vendored Normal file
View File

@ -0,0 +1,44 @@
module.exports = {
rules: {
"selector-type-no-unknown": true,
"block-closing-brace-empty-line-before": "never",
"block-no-empty": true,
"block-opening-brace-newline-after": "always",
"block-opening-brace-space-before": "always",
"color-hex-case": "lower",
"color-named": "never",
"color-no-invalid-hex": true,
"length-zero-no-unit": true,
"declaration-block-trailing-semicolon": "always",
"custom-property-empty-line-before": ["always", {
"except": [
"after-custom-property",
"first-nested"
]
}],
"declaration-block-no-duplicate-properties": true,
"declaration-colon-space-after": "always",
"declaration-empty-line-before": ["always", {
"except": [
"after-declaration",
"first-nested",
"after-comment"
]
}],
"font-weight-notation": "numeric",
"indentation": [4, {
"except": ["value"]
}],
"max-empty-lines": 2,
"no-missing-end-of-source-newline": true,
"number-leading-zero": "always",
"property-no-unknown": true,
"rule-empty-line-before": ["always-multi-line", {
"except": ["first-nested"],
"ignore": ["after-comment"]
}],
"string-quotes": "double",
"value-list-comma-space-after": "always",
"unit-case": "lower"
}
}

14
client/babel.config.cjs vendored Normal file
View File

@ -0,0 +1,14 @@
module.exports = (api) => {
api.cache(false);
return {
presets: ['@babel/preset-env', '@babel/preset-typescript', '@babel/preset-react'],
plugins: [
'@babel/plugin-transform-runtime',
'@babel/plugin-transform-class-properties',
'@babel/plugin-transform-object-rest-spread',
'@babel/plugin-transform-nullish-coalescing-operator',
'@babel/plugin-transform-optional-chaining',
'react-hot-loader/babel',
],
};
};

View File

@ -1,17 +0,0 @@
module.exports = (api) => {
api.cache(false);
return {
presets: [
'@babel/preset-env',
'@babel/preset-react',
],
plugins: [
'@babel/plugin-proposal-class-properties',
'@babel/plugin-transform-runtime',
'@babel/plugin-proposal-object-rest-spread',
'@babel/plugin-proposal-nullish-coalescing-operator',
'@babel/plugin-proposal-optional-chaining',
'react-hot-loader/babel',
],
};
};

9
client/constants.js vendored
View File

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

6
client/global.d.ts vendored Normal file
View File

@ -0,0 +1,6 @@
import React from 'react';
declare module '*.svg' {
const content: React.FunctionComponent<React.SVGAttributes<SVGElement>>;
export default content;
}

View File

@ -1,5 +0,0 @@
module.exports = {
transform: {
'^.+\\.jsx?$': 'babel-jest',
},
};

6
client/jest.config.mjs vendored Normal file
View File

@ -0,0 +1,6 @@
export default {
testEnvironment: 'jsdom',
transform: {
'^.+\\.tsx?$': 'babel-jest',
},
};

39735
client/package-lock.json generated vendored

File diff suppressed because it is too large Load Diff

110
client/package.json vendored
View File

@ -3,19 +3,23 @@
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"scripts": { "scripts": {
"build-dev": "cross-env BUILD_ENV=dev webpack --config webpack.dev.js", "build-dev": "cross-env NODE_ENV=development BUILD_ENV=dev webpack --config webpack.dev.js",
"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": "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", "watch:hot": "cross-env BUILD_ENV=dev webpack-dev-server --config webpack.dev.js",
"lint": "eslint src", "lint": "echo 'Lint temporarily disabled'",
"lint:fix": "eslint src --fix", "lint-new": "eslint './src/**/*.(ts|tsx)'",
"lint:fix": "eslint './src/**/*.(ts|tsx)' --fix",
"test": "jest", "test": "jest",
"test:watch": "jest --watch" "test:watch": "jest --watch",
"typecheck": "tsc --noEmit",
"typecheck:watch": "tsc --noEmit --watch"
}, },
"type": "module",
"dependencies": { "dependencies": {
"@nivo/line": "^0.64.0", "@nivo/line": "^0.64.0",
"axios": "^0.19.2", "axios": "^0.19.2",
"classnames": "^2.2.6", "classnames": "^2.5.1",
"countries-and-timezones": "^3.6.0", "countries-and-timezones": "^3.6.0",
"date-fns": "^1.29.0", "date-fns": "^1.29.0",
"i18next": "^19.6.2", "i18next": "^19.6.2",
@ -24,7 +28,8 @@
"js-yaml": "^3.14.0", "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", "popper.js": "^1.16.1",
"prop-types": "^15.8.1",
"query-string": "^6.13.1", "query-string": "^6.13.1",
"react": "^16.13.1", "react": "^16.13.1",
"react-click-outside": "^3.0.1", "react-click-outside": "^3.0.1",
@ -38,53 +43,64 @@
"react-router-hash-link": "^1.2.2", "react-router-hash-link": "^1.2.2",
"react-select": "^3.1.0", "react-select": "^3.1.0",
"react-table": "^6.11.4", "react-table": "^6.11.4",
"react-transition-group": "^4.4.1", "react-transition-group": "^4.4.5",
"redux": "^4.0.5", "redux": "^4.0.5",
"redux-actions": "^2.6.5", "redux-actions": "^2.6.5",
"redux-form": "^8.3.5", "redux-form": "^8.3.10",
"redux-thunk": "^2.3.0", "redux-thunk": "^2.3.0",
"url-polyfill": "^1.1.9" "ts-migrate": "^0.1.35",
"url-polyfill": "^1.1.12"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.9.6", "@babel/core": "^7.24.5",
"@babel/plugin-proposal-class-properties": "^7.8.3", "@babel/plugin-transform-class-properties": "^7.24.1",
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.10.4", "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.1",
"@babel/plugin-proposal-object-rest-spread": "^7.9.6", "@babel/plugin-transform-object-rest-spread": "^7.24.5",
"@babel/plugin-proposal-optional-chaining": "^7.10.4", "@babel/plugin-transform-optional-chaining": "^7.24.5",
"@babel/plugin-transform-runtime": "^7.9.6", "@babel/plugin-transform-runtime": "^7.24.3",
"@babel/preset-env": "^7.9.6", "@babel/preset-env": "^7.24.5",
"@babel/preset-react": "^7.9.4", "@babel/preset-react": "^7.24.1",
"autoprefixer": "^9.8.0", "@types/jest": "^29.5.12",
"babel-eslint": "^10.1.0", "@types/lodash": "^4.17.4",
"babel-loader": "^8.1.0", "@types/react": "^17.0.80",
"clean-webpack-plugin": "^3.0.0", "@types/react-dom": "^18.3.0",
"copy-webpack-plugin": "^6.0.1", "@types/react-redux": "^7.1.33",
"cross-env": "^7.0.2", "@types/react-router-dom": "^5.3.3",
"css-loader": "^3.5.3", "@types/react-table": "^7.7.20",
"eslint": "^6.8.0", "@types/redux-actions": "^2.6.5",
"eslint-config-airbnb": "^18.1.0", "@types/redux-form": "^8.3.10",
"eslint-import-resolver-webpack": "^0.12.1", "@typescript-eslint/eslint-plugin": "^7.11.0",
"eslint-loader": "^4.0.2", "@typescript-eslint/parser": "^7.10.0",
"eslint-plugin-import": "^2.22.1", "babel-loader": "^9.1.3",
"eslint-plugin-jsx-a11y": "^6.2.3", "clean-webpack-plugin": "^4.0.0",
"eslint-plugin-react": "^7.24.0", "copy-webpack-plugin": "^12.0.2",
"eslint-plugin-react-hooks": "^2.5.0", "cross-env": "^7.0.3",
"file-loader": "6.0.0", "css-loader": "^7.1.2",
"html-webpack-plugin": "^4.3.0", "eslint": "^8.57.0",
"jest": "^26.0.1", "eslint-config-airbnb": "^19.0.4",
"mini-css-extract-plugin": "^0.9.0", "eslint-config-prettier": "^9.1.0",
"eslint-plugin-jsx-a11y": "^6.8.0",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-react": "^7.34.1",
"eslint-plugin-react-hooks": "^4.6.2",
"file-loader": "^6.2.0",
"html-webpack-plugin": "^5.6.0",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"jscodeshift": "^0.15.2",
"mini-css-extract-plugin": "^2.9.0",
"path": "^0.12.7", "path": "^0.12.7",
"postcss-flexbugs-fixes": "4.2.1", "postcss-loader": "^8.1.1",
"postcss-loader": "^3.0.0", "prettier": "^3.2.5",
"react-hot-loader": "^4.12.21", "react-hot-loader": "^4.13.1",
"style-loader": "^1.2.1", "style-loader": "^4.0.0",
"stylelint": "^13.5.0", "stylelint": "^16.5.0",
"stylelint-webpack-plugin": "2.0.0", "ts-loader": "^9.5.1",
"url-loader": "^4.1.0", "url-loader": "^4.1.1",
"webpack": "^4.43.0", "webpack": "^5.91.0",
"webpack-cli": "^3.3.11", "webpack-cli": "^5.1.4",
"webpack-dev-server": "^3.11.0", "webpack-dev-server": "^5.0.4",
"webpack-merge": "^4.2.2" "webpack-merge": "^5.10.0"
}, },
"browserslist": { "browserslist": {
"development": [ "development": [

View File

@ -6,7 +6,7 @@
"upstream_parallel": "Použijte paralelní požadavky na urychlení řešení simultánním dotazováním na všechny navazující servery.", "upstream_parallel": "Použijte paralelní požadavky na urychlení řešení simultánním dotazováním na všechny navazující servery.",
"parallel_requests": "Paralelní požadavky", "parallel_requests": "Paralelní požadavky",
"load_balancing": "Optimalizace vytížení", "load_balancing": "Optimalizace vytížení",
"load_balancing_desc": "Optimalizovaný dotaz na odchozí server. AdGuard Home použije vážený náhodný algoritmus k výběru serveru, takže nejrychlejší server je používán častěji.", "load_balancing_desc": "Dotazy jednoho odchozího serveru ve stejný čas. AdGuard Home používá náhodný algoritmus pro výběr serverů s nejnižším počtem neúspěšných vyhledávání a nejnižší průměrnou dobou vyhledávání.",
"bootstrap_dns": "Bootstrap DNS servery", "bootstrap_dns": "Bootstrap DNS servery",
"bootstrap_dns_desc": "IP adresy DNS serverů používaných k překladu IP adres řešitelů DoH/DoT, které zadáte jako odchozí servery. Komentáře nejsou povoleny.", "bootstrap_dns_desc": "IP adresy DNS serverů používaných k překladu IP adres řešitelů DoH/DoT, které zadáte jako odchozí servery. Komentáře nejsou povoleny.",
"fallback_dns_title": "Záložní DNS servery", "fallback_dns_title": "Záložní DNS servery",

View File

@ -6,7 +6,7 @@
"upstream_parallel": "Brug parallelforespørgsler til at accelerere fortolkningen ved at forespørge alle upstream-servere samtidigt.", "upstream_parallel": "Brug parallelforespørgsler til at accelerere fortolkningen ved at forespørge alle upstream-servere samtidigt.",
"parallel_requests": "Parallelle forespørgsler", "parallel_requests": "Parallelle forespørgsler",
"load_balancing": "Belastningsfordeling", "load_balancing": "Belastningsfordeling",
"load_balancing_desc": "Forespørg én server ad gangen. AdGuard Home vil bruge en vægtet randomiseringsalgoritme til valg af server, så den hurtigste server oftere anvendes.", "load_balancing_desc": "Forespørg én upstream-server ad gangen. AdGuard Home bruger en vægtet tilfældighedsalgoritme til vælg af servere med det laveste antal fejlslagne opslag og den laveste gennemsnitlige opslagstid.",
"bootstrap_dns": "Bootstrap DNS-servere", "bootstrap_dns": "Bootstrap DNS-servere",
"bootstrap_dns_desc": "IP-adresser på DNS-servere, som bruges til at opløse IP-adresser på de DoH/DoT-opløsere, som angives som upstreams. Kommentarer er ikke tilladt.", "bootstrap_dns_desc": "IP-adresser på DNS-servere, som bruges til at opløse IP-adresser på de DoH/DoT-opløsere, som angives som upstreams. Kommentarer er ikke tilladt.",
"fallback_dns_title": "Reserve DNS-servere", "fallback_dns_title": "Reserve DNS-servere",

View File

@ -6,7 +6,7 @@
"upstream_parallel": "Parallele Abfragen verwenden, um das Auflösen zu beschleunigen, indem alle Upstream-Server gleichzeitig abgefragt werden.", "upstream_parallel": "Parallele Abfragen verwenden, um das Auflösen zu beschleunigen, indem alle Upstream-Server gleichzeitig abgefragt werden.",
"parallel_requests": "Paralleles Abfragen", "parallel_requests": "Paralleles Abfragen",
"load_balancing": "Lastverteilung", "load_balancing": "Lastverteilung",
"load_balancing_desc": "Einen Server nach dem anderen abfragen. AdGuard Home verwendet den gewichteten Zufallsalgorithmus, um den Server so auszuwählen, dass der schnellste Server häufiger verwendet wird.", "load_balancing_desc": "Es wird jeweils ein Upstream-Server abgefragt. AdGuard Home verwendet einen gewichteten Zufallsalgorithmus, um die Server mit der geringsten Anzahl an fehlgeschlagenen Suchvorgängen und der niedrigsten durchschnittlichen Suchzeit auszuwählen.",
"bootstrap_dns": "Bootstrap-DNS-Server", "bootstrap_dns": "Bootstrap-DNS-Server",
"bootstrap_dns_desc": "IP-Adressen der DNS-Server, die zum Auflösen der IP-Adressen von DoH/DoT Upstream-Servern verwendet werden, die Sie angegeben haben. Kommentare sind nicht erlaubt.", "bootstrap_dns_desc": "IP-Adressen der DNS-Server, die zum Auflösen der IP-Adressen von DoH/DoT Upstream-Servern verwendet werden, die Sie angegeben haben. Kommentare sind nicht erlaubt.",
"fallback_dns_title": "Fallback-DNS-Server", "fallback_dns_title": "Fallback-DNS-Server",

View File

@ -6,7 +6,7 @@
"upstream_parallel": "Use parallel queries to speed up resolving by querying all upstream servers simultaneously.", "upstream_parallel": "Use parallel queries to speed up resolving by querying all upstream servers simultaneously.",
"parallel_requests": "Parallel requests", "parallel_requests": "Parallel requests",
"load_balancing": "Load-balancing", "load_balancing": "Load-balancing",
"load_balancing_desc": "Query one upstream server at a time. AdGuard Home uses its weighted random algorithm to pick the server so that the fastest server is used more often.", "load_balancing_desc": "Query one upstream server at a time. AdGuard Home uses a weighted random algorithm to select servers with the lowest number of failed lookups and the lowest average lookup time.",
"bootstrap_dns": "Bootstrap DNS servers", "bootstrap_dns": "Bootstrap DNS servers",
"bootstrap_dns_desc": "IP addresses of DNS servers used to resolve IP addresses of the DoH/DoT resolvers you specify as upstreams. Comments are not permitted.", "bootstrap_dns_desc": "IP addresses of DNS servers used to resolve IP addresses of the DoH/DoT resolvers you specify as upstreams. Comments are not permitted.",
"fallback_dns_title": "Fallback DNS servers", "fallback_dns_title": "Fallback DNS servers",

View File

@ -6,7 +6,7 @@
"upstream_parallel": "Usar consultas paralelas para acelerar la resolución al consultar simultáneamente a todos los servidores DNS de subida.", "upstream_parallel": "Usar consultas paralelas para acelerar la resolución al consultar simultáneamente a todos los servidores DNS de subida.",
"parallel_requests": "Consultas paralelas", "parallel_requests": "Consultas paralelas",
"load_balancing": "Balanceo de carga", "load_balancing": "Balanceo de carga",
"load_balancing_desc": "Consulta un servidor DNS de subida a la vez. AdGuard Home utiliza su algoritmo aleatorio ponderado para elegir el servidor más rápido y sea utilizado con más frecuencia.", "load_balancing_desc": "Consulta un servidor upstream a la vez. AdGuard Home utiliza un algoritmo aleatorio ponderado para seleccionar los servidores con el menor número de fallos y el menor tiempo medio de búsqueda.",
"bootstrap_dns": "Servidores DNS de arranque", "bootstrap_dns": "Servidores DNS de arranque",
"bootstrap_dns_desc": "Direcciones IP de servidores DNS utilizadas para resolver direcciones IP de los solucionadores DoH/DoT que especifiques como ascendentes. No se permiten comentarios.", "bootstrap_dns_desc": "Direcciones IP de servidores DNS utilizadas para resolver direcciones IP de los solucionadores DoH/DoT que especifiques como ascendentes. No se permiten comentarios.",
"fallback_dns_title": "Servidores DNS alternativos", "fallback_dns_title": "Servidores DNS alternativos",

View File

@ -589,6 +589,7 @@
"cache_optimistic_desc": "AdGuard Home را وادار می کند که از سمت حافظه پنهان پاسخ دهد حتی وقتی که موارد وارد شده منقضی شده باشد و همچنین سعی بر تازه کردن آنها می کند.", "cache_optimistic_desc": "AdGuard Home را وادار می کند که از سمت حافظه پنهان پاسخ دهد حتی وقتی که موارد وارد شده منقضی شده باشد و همچنین سعی بر تازه کردن آنها می کند.",
"filter_category_general": "General", "filter_category_general": "General",
"filter_category_security": "مسدودسازی بدافزار و فیشینگ", "filter_category_security": "مسدودسازی بدافزار و فیشینگ",
"filter_category_regional": "منطقه‌ای",
"filter_category_other": "ساير", "filter_category_other": "ساير",
"use_saved_key": "از کلید ذخیره شده قبلی استفاده کنید", "use_saved_key": "از کلید ذخیره شده قبلی استفاده کنید",
"parental_control": "نظارت والدین", "parental_control": "نظارت والدین",

View File

@ -6,7 +6,7 @@
"upstream_parallel": "Utilisez des requêtes parallèles pour accélérer la résolution en requêtant simultanément tous les serveurs en amont.", "upstream_parallel": "Utilisez des requêtes parallèles pour accélérer la résolution en requêtant simultanément tous les serveurs en amont.",
"parallel_requests": "Requêtes en parallèle", "parallel_requests": "Requêtes en parallèle",
"load_balancing": "Équilibrage de charge", "load_balancing": "Équilibrage de charge",
"load_balancing_desc": "Interroger un serveur en amont à la fois. AdGuard Home utilise son algorithme aléatoire pondéré pour choisir le serveur de sorte que le serveur le plus rapide soit utilisé plus souvent.", "load_balancing_desc": "Une requête par serveur en amont à la fois. AdGuard Home utilise un algorithme aléatoire pondéré pour sélectionner les serveurs avec le plus petit nombre d'échecs de recherche et le temps de recherche moyen le plus bas.",
"bootstrap_dns": "Serveurs DNS d'amorçage", "bootstrap_dns": "Serveurs DNS d'amorçage",
"bootstrap_dns_desc": "Les adresses IP des serveurs DNS utilisées pour résoudre les adresses IP des résolveurs DoH/DoT que vous spécifiez comme en amont. Les commentaires ne sont pas autorisés.", "bootstrap_dns_desc": "Les adresses IP des serveurs DNS utilisées pour résoudre les adresses IP des résolveurs DoH/DoT que vous spécifiez comme en amont. Les commentaires ne sont pas autorisés.",
"fallback_dns_title": "Serveurs DNS de repli", "fallback_dns_title": "Serveurs DNS de repli",

View File

@ -13,14 +13,14 @@
"fallback_dns_desc": "Popis rezervnih DNS poslužitelja koji se koriste kada uzvodni DNS poslužitelji ne odgovaraju. Sintaksa je ista kao u gornjem polju glavnog uzvodnog toka.", "fallback_dns_desc": "Popis rezervnih DNS poslužitelja koji se koriste kada uzvodni DNS poslužitelji ne odgovaraju. Sintaksa je ista kao u gornjem polju glavnog uzvodnog toka.",
"fallback_dns_placeholder": "Unesite jedan rezervni DNS poslužitelj po retku", "fallback_dns_placeholder": "Unesite jedan rezervni DNS poslužitelj po retku",
"local_ptr_title": "Privatni obrnuti DNS poslužitelji", "local_ptr_title": "Privatni obrnuti DNS poslužitelji",
"local_ptr_desc": "DNS poslužitelji koje AdGuard Home koristi za lokalne PTR upite. Ti se poslužitelji koriste za razrješavanje naziva glavnog računala klijenata s privatnim IP adresama, na primjer \"192.168.12.34\", koristeći obrnuti DNS. Ako nije postavljeno, AdGuard Home koristi adrese zadanih DNS razrješivača vašeg OS-a, osim za adrese samog AdGuard Homea.", "local_ptr_desc": "DNS poslužitelji koje koristi AdGuard Home za privatne PTR, SOA i NS zahtjeve. Zahtjev se smatra privatnim ako traži ARPA domenu koja sadrži podmrežu unutar privatnih IP raspona (kao što je \"192.168.12.34\") i dolazi od klijenta s privatnom IP adresom. Ako nije postavljeno, koristit će se zadani DNS rezolveri vašeg OS-a, osim za AdGuard Home IP adrese.",
"local_ptr_default_resolver": "Prema zadanim postavkama AdGuard Home koristi sljedeće obrnute DNS razrješivače: {{ip}}.", "local_ptr_default_resolver": "Prema zadanim postavkama AdGuard Home koristi sljedeće obrnute DNS razrješivače: {{ip}}.",
"local_ptr_no_default_resolver": "AdGuard Home nije mogao odrediti prikladne privatne obrnute DNS razrješivače za ovaj sustav.", "local_ptr_no_default_resolver": "AdGuard Home nije mogao odrediti prikladne privatne obrnute DNS razrješivače za ovaj sustav.",
"local_ptr_placeholder": "Unesite jednu adresu poslužitelja po retku", "local_ptr_placeholder": "Unesite jednu adresu poslužitelja po retku",
"resolve_clients_title": "Omogući obrnuto rješavanje IP adresa klijenata", "resolve_clients_title": "Omogući obrnuto rješavanje IP adresa klijenata",
"resolve_clients_desc": "Obrnuto razriješite IP adrese klijenata u nazive glavnih računala slanjem PTR upita odgovarajućim razrješivačima (privatni DNS poslužitelji za lokalne klijente, uzvodni poslužitelji za klijente s javnim IP adresama).", "resolve_clients_desc": "Obrnuto razriješite IP adrese klijenata u nazive glavnih računala slanjem PTR upita odgovarajućim razrješivačima (privatni DNS poslužitelji za lokalne klijente, uzvodni poslužitelji za klijente s javnim IP adresama).",
"use_private_ptr_resolvers_title": "Koristi privatne reverzne DNS razrješivače", "use_private_ptr_resolvers_title": "Koristi privatne reverzne DNS razrješivače",
"use_private_ptr_resolvers_desc": "Izvršite obrnuta DNS traženja za lokalno poslužene adrese pomoću ovih uzlaznih poslužitelja. Ako je onemogućen, AdGuard Home odgovara S NXDOMAIN-om na sve takve PTR zahtjeve osim za klijente poznate iz DHCP-a, /etc/hosts i tako dalje.", "use_private_ptr_resolvers_desc": "Razriješi PTR, SOA i NS zahtjeve za ARPA domene koje sadrže privatne IP adrese putem privatnih uzvodnih poslužitelja, DHCP-a, /etc/hostova itd. Ako je onemogućeno, AdGuard Home će na sve takve zahtjeve odgovoriti s NXDOMAIN.",
"check_dhcp_servers": "Provjera DHCP poslužitelja", "check_dhcp_servers": "Provjera DHCP poslužitelja",
"save_config": "Spremi konfiguraciju", "save_config": "Spremi konfiguraciju",
"enabled_dhcp": "DHCP poslužitelj je omogućen", "enabled_dhcp": "DHCP poslužitelj je omogućen",
@ -425,6 +425,9 @@
"encryption_hostnames": "Nazivi računala", "encryption_hostnames": "Nazivi računala",
"encryption_reset": "Jeste li sigurni da želite poništiti postavke šifriranja?", "encryption_reset": "Jeste li sigurni da želite poništiti postavke šifriranja?",
"encryption_warning": "Upozorenje", "encryption_warning": "Upozorenje",
"encryption_plain_dns_enable": "Omogući obični DNS",
"encryption_plain_dns_desc": "Obični DNS je omogućen prema zadanim postavkama. Možete ga onemogućiti kako biste prisilili sve uređaje da koriste šifrirani DNS. Da biste to učinili, morate omogućiti barem jedan kriptirani DNS protokol",
"encryption_plain_dns_error": "Da biste onemogućili obični DNS, omogućite barem jedan kriptirani DNS protokol",
"topline_expiring_certificate": "Vaš SSL certifikat uskoro ističe. Ažurirajte <0>Postavke šifriranja</0>.", "topline_expiring_certificate": "Vaš SSL certifikat uskoro ističe. Ažurirajte <0>Postavke šifriranja</0>.",
"topline_expired_certificate": "Vaš SSL certifikat je istekao. Ažurirajte <0>Postavke šifriranja</0>.", "topline_expired_certificate": "Vaš SSL certifikat je istekao. Ažurirajte <0>Postavke šifriranja</0>.",
"form_error_port_range": "Unesite broj porta od 80 do 65536", "form_error_port_range": "Unesite broj porta od 80 do 65536",
@ -675,7 +678,7 @@
"use_saved_key": "Korištenje prethodno spremljenog ključa", "use_saved_key": "Korištenje prethodno spremljenog ključa",
"parental_control": "Roditeljska zaštita", "parental_control": "Roditeljska zaštita",
"safe_browsing": "Sigurno surfanje", "safe_browsing": "Sigurno surfanje",
"served_from_cache": "{{value}} <i>(dohvaćeno iz predmemorije)</i>", "served_from_cache_label": "Posluženo iz predmemorije",
"form_error_password_length": "Lozinka mora sadržavati od {{min}} do {{max}} znakova", "form_error_password_length": "Lozinka mora sadržavati od {{min}} do {{max}} znakova",
"anonymizer_notification": "<0>Napomena:</0>IP anonimizacija je omogućena. Možete ju onemogućiti u <1>općim postavkama</1>.", "anonymizer_notification": "<0>Napomena:</0>IP anonimizacija je omogućena. Možete ju onemogućiti u <1>općim postavkama</1>.",
"confirm_dns_cache_clear": "Jeste li sigurni da želite očistiti DNS predmemoriju?", "confirm_dns_cache_clear": "Jeste li sigurni da želite očistiti DNS predmemoriju?",

View File

@ -6,7 +6,7 @@
"upstream_parallel": "Utilizza richieste parallele per accelerare la risoluzione interrogando simultaneamente tutti i server upstream.", "upstream_parallel": "Utilizza richieste parallele per accelerare la risoluzione interrogando simultaneamente tutti i server upstream.",
"parallel_requests": "Richieste parallele", "parallel_requests": "Richieste parallele",
"load_balancing": "Bilanciamento del carico", "load_balancing": "Bilanciamento del carico",
"load_balancing_desc": "Interroga un server upstream per volta. AdGuard Home utilizzerà un algoritmo casuale ponderato per la selezione del server, in maniera tale da scegliere spesso il più veloce.", "load_balancing_desc": "Esegui una query su un server upstream alla volta. AdGuard Home utilizza un algoritmo casuale ponderato per selezionare i server con il minor numero di ricerche fallite e il tempo medio di ricerca più basso.",
"bootstrap_dns": "Server DNS bootstrap", "bootstrap_dns": "Server DNS bootstrap",
"bootstrap_dns_desc": "Indirizzi IP dei server DNS utilizzati per risolvere gli indirizzi IP dei resolver DoH/DoT specificati come upstream. I commenti non sono ammessi.", "bootstrap_dns_desc": "Indirizzi IP dei server DNS utilizzati per risolvere gli indirizzi IP dei resolver DoH/DoT specificati come upstream. I commenti non sono ammessi.",
"fallback_dns_title": "Server DNS di fallback", "fallback_dns_title": "Server DNS di fallback",

View File

@ -6,7 +6,7 @@
"upstream_parallel": "並列リクエストを使用する(同時にすべてのアップストリームサーバーに処理要求することで解決スピードが向上)", "upstream_parallel": "並列リクエストを使用する(同時にすべてのアップストリームサーバーに処理要求することで解決スピードが向上)",
"parallel_requests": "並列リクエスト", "parallel_requests": "並列リクエスト",
"load_balancing": "ロードバランシング", "load_balancing": "ロードバランシング",
"load_balancing_desc": "一度に1つのアップストリームサーバに処理要求します。 AdGuard Homeは、重み付きランダムアルゴリズムweighted random algorithmを使用してサーバを選択するため、最速のサーバがより頻繁に使用されます。", "load_balancing_desc": "一度に1つのアップストリームサーバーをクエリします。AdGuard Home は、重み付き乱択アルゴリズムを使用して、ルックアップに失敗した回数が最も少なく、平均ルックアップ時間が最も短いサーバーを選択します。",
"bootstrap_dns": "ブートストラップDNSサーバ", "bootstrap_dns": "ブートストラップDNSサーバ",
"bootstrap_dns_desc": "アップストリームとして指定したDoH/DoTリゾルバのIPアドレスを解決するために使用されるDNSサーバーのIPアドレスです。コメントは許可されていません", "bootstrap_dns_desc": "アップストリームとして指定したDoH/DoTリゾルバのIPアドレスを解決するために使用されるDNSサーバーのIPアドレスです。コメントは許可されていません",
"fallback_dns_title": "フォールバックDNSサーバー", "fallback_dns_title": "フォールバックDNSサーバー",

View File

@ -6,7 +6,7 @@
"upstream_parallel": "쿼리 처리 속도를 높이려면 모든 업스트림 서버에서 동시에 병렬 쿼리를 사용해주세요.", "upstream_parallel": "쿼리 처리 속도를 높이려면 모든 업스트림 서버에서 동시에 병렬 쿼리를 사용해주세요.",
"parallel_requests": "병렬 처리 요청", "parallel_requests": "병렬 처리 요청",
"load_balancing": "로드 밸런싱", "load_balancing": "로드 밸런싱",
"load_balancing_desc": "한 번에 하나의 서버씩 질의합니다. AdGuard Home은 가중 랜덤 알고리즘를 사용해서 가장 빠른 서버가 자주 사용되도록 서버를 선택합니다.", "load_balancing_desc": "한 번에 하나의 업스트림 서버를 쿼리합니다. AdGuard Home은 가중 무작위 알고리즘을 사용하여 조회 실패 횟수가 가장 적고 평균 조회 시간이 가장 짧은 서버를 선택합니다.",
"bootstrap_dns": "부트스트랩 DNS 서버", "bootstrap_dns": "부트스트랩 DNS 서버",
"bootstrap_dns_desc": "업스트림으로 지정한 DoH/DoT 리졸버의 IP 주소를 확인하는 데 사용되는 DNS 서버의 IP 주소입니다. 주석은 허용되지 않습니다.", "bootstrap_dns_desc": "업스트림으로 지정한 DoH/DoT 리졸버의 IP 주소를 확인하는 데 사용되는 DNS 서버의 IP 주소입니다. 주석은 허용되지 않습니다.",
"fallback_dns_title": "폴백 DNS 서버", "fallback_dns_title": "폴백 DNS 서버",

View File

@ -6,7 +6,7 @@
"upstream_parallel": "Parallelle verzoeken gebruiken om te versnellen door gelijktijdig verzoeken te sturen naar alle upstream servers.", "upstream_parallel": "Parallelle verzoeken gebruiken om te versnellen door gelijktijdig verzoeken te sturen naar alle upstream servers.",
"parallel_requests": "Parallelle verzoeken", "parallel_requests": "Parallelle verzoeken",
"load_balancing": "Volume balanceren", "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.", "load_balancing_desc": "Voer zoekopdrachten uit op één upstream-server tegelijk. AdGuard Home gebruikt een gewogen willekeurig algoritme om servers te selecteren met het laagste aantal mislukte zoekopdrachten en de laagste gemiddelde opzoektijd.",
"bootstrap_dns": "Bootstrap DNS-servers", "bootstrap_dns": "Bootstrap DNS-servers",
"bootstrap_dns_desc": "IP-adressen van DNS-servers die worden gebruikt om IP-adressen om te zetten van de DoH/DoT-resolvers die je opgeeft als upstreams. Opmerkingen zijn niet toegestaan.", "bootstrap_dns_desc": "IP-adressen van DNS-servers die worden gebruikt om IP-adressen om te zetten van de DoH/DoT-resolvers die je opgeeft als upstreams. Opmerkingen zijn niet toegestaan.",
"fallback_dns_title": "Back-up DNS-servers", "fallback_dns_title": "Back-up DNS-servers",
@ -495,7 +495,7 @@
"setup_dns_privacy_2": "<0>DNS-via-HTTPS:</0> Gebruik <1>{{address}}</1> string.", "setup_dns_privacy_2": "<0>DNS-via-HTTPS:</0> Gebruik <1>{{address}}</1> 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><0>Hier is een lijst van te gebruiken software.</0>", "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><0>Hier is een lijst van te gebruiken software.</0>",
"setup_dns_privacy_4": "Op een iOS 14 of macOS Big Sur apparaat kan je een speciaal '.mobileconfig'-bestand downloaden dat <highlight>DNS-via-HTTPS</highlight> of <highlight>DNS-via-TLS</highlight> servers aan de DNS-instellingen toevoegt.", "setup_dns_privacy_4": "Op een iOS 14 of macOS Big Sur apparaat kan je een speciaal '.mobileconfig'-bestand downloaden dat <highlight>DNS-via-HTTPS</highlight> of <highlight>DNS-via-TLS</highlight> servers aan de DNS-instellingen toevoegt.",
"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_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 Android</0>ondersteunt<1>DNS-via-HTTPS </1>en<1>DNS-via-TLS</1>.", "setup_dns_privacy_android_2": "<0>AdGuard voor Android</0>ondersteunt<1>DNS-via-HTTPS </1>en<1>DNS-via-TLS</1>.",
"setup_dns_privacy_android_3": "<0> Intra </0> voegt <1> DNS-via-HTTPS</1> ondersteuning toe aan Android.", "setup_dns_privacy_android_3": "<0> Intra </0> voegt <1> DNS-via-HTTPS</1> ondersteuning toe aan Android.",
"setup_dns_privacy_ios_1": "<0>DNSCloak</0> ondersteunt <1> DNS-via-HTTPS </1>, maar om het te configureren op jouw eigen server moet er een <2> DNS-stempel </2> gegenereerd worden.", "setup_dns_privacy_ios_1": "<0>DNSCloak</0> ondersteunt <1> DNS-via-HTTPS </1>, maar om het te configureren op jouw eigen server moet er een <2> DNS-stempel </2> gegenereerd worden.",

View File

@ -6,7 +6,7 @@
"upstream_parallel": "Usar consultas paralelas para acelerar a resolução consultando simultaneamente todos os servidores DNS primário", "upstream_parallel": "Usar consultas paralelas para acelerar a resolução consultando simultaneamente todos os servidores DNS primário",
"parallel_requests": "Solicitações paralelas", "parallel_requests": "Solicitações paralelas",
"load_balancing": "Balanceamento de carga", "load_balancing": "Balanceamento de carga",
"load_balancing_desc": "Consulte um servidor DNS primário por vez. O AdGuard Home usa seu algoritmo aleatório ponderado para escolher o servidor para que o servidor mais rápido seja usado com mais frequência.", "load_balancing_desc": "Consulte um servidor upstream por vez. O AdGuard Home usa um algoritmo aleatório ponderado para selecionar servidores com o menor número de falhas e o menor tempo médio de consulta.",
"bootstrap_dns": "Servidores DNS de inicialização", "bootstrap_dns": "Servidores DNS de inicialização",
"bootstrap_dns_desc": "Endereços IP de servidores DNS usados para resolver endereços IP dos resolvedores DoH/DoT que você especifica como upstreams. Comentários não são permitidos.", "bootstrap_dns_desc": "Endereços IP de servidores DNS usados para resolver endereços IP dos resolvedores DoH/DoT que você especifica como upstreams. Comentários não são permitidos.",
"fallback_dns_title": "Servidores DNS Fallback", "fallback_dns_title": "Servidores DNS Fallback",

View File

@ -6,7 +6,7 @@
"upstream_parallel": "Usar consultas paralelas para acelerar a resolução consultando simultaneamente todos os servidores DNS", "upstream_parallel": "Usar consultas paralelas para acelerar a resolução consultando simultaneamente todos os servidores DNS",
"parallel_requests": "Solicitações paralelas", "parallel_requests": "Solicitações paralelas",
"load_balancing": "Balanceamento de carga", "load_balancing": "Balanceamento de carga",
"load_balancing_desc": "Consulte um servidor DNS primário por vez. O AdGuard Home usa seu algoritmo aleatório ponderado para escolher o servidor para que o servidor mais rápido seja usado com mais frequência.", "load_balancing_desc": "Consulta um servidor a montante de cada vez. O AdGuard Home usa um algoritmo aleatório ponderado para selecionar servidores com o menor número de pesquisas com falha e o menor tempo médio de pesquisa.",
"bootstrap_dns": "Servidores DNS de arranque", "bootstrap_dns": "Servidores DNS de arranque",
"bootstrap_dns_desc": "Endereços IP de servidores DNS usados para resolver endereços IP dos resolvedores DoH/DoT que você especifica como upstreams. Comentários não são permitidos.", "bootstrap_dns_desc": "Endereços IP de servidores DNS usados para resolver endereços IP dos resolvedores DoH/DoT que você especifica como upstreams. Comentários não são permitidos.",
"fallback_dns_title": "Servidores DNS de fallback", "fallback_dns_title": "Servidores DNS de fallback",

View File

@ -6,7 +6,7 @@
"upstream_parallel": "Использовать параллельные запросы ко всем серверам одновременно для ускорения обработки запроса.", "upstream_parallel": "Использовать параллельные запросы ко всем серверам одновременно для ускорения обработки запроса.",
"parallel_requests": "Параллельные запросы", "parallel_requests": "Параллельные запросы",
"load_balancing": "Распределение нагрузки\n", "load_balancing": "Распределение нагрузки\n",
"load_balancing_desc": "Запрашивать по одному серверу за раз. AdGuard Home использует алгоритм взвешенного случайного выбора сервера, так что самый быстрый сервер используется чаще.", "load_balancing_desc": "Запрашивайте по одному серверу за раз. AdGuard Home использует алгоритм случайной выборки с учётом веса для выбора серверов с наименьшим количеством неудачных запросов и наименьшим средним временем выполнения запроса.",
"bootstrap_dns": "Bootstrap DNS-серверы", "bootstrap_dns": "Bootstrap DNS-серверы",
"bootstrap_dns_desc": "IP-адреса DNS-серверов, используемых для поиска IP-адресов DoH/DoT upstream-серверов, которые вы указали. Комментарии не допускаются.", "bootstrap_dns_desc": "IP-адреса DNS-серверов, используемых для поиска IP-адресов DoH/DoT upstream-серверов, которые вы указали. Комментарии не допускаются.",
"fallback_dns_title": "Резервные DNS-серверы", "fallback_dns_title": "Резервные DNS-серверы",

View File

@ -6,7 +6,7 @@
"upstream_parallel": "Používať paralelné dopyty na zrýchlenie súčasným dopytovaním všetkých upstream serverov súčasne.", "upstream_parallel": "Používať paralelné dopyty na zrýchlenie súčasným dopytovaním všetkých upstream serverov súčasne.",
"parallel_requests": "Paralelné dopyty", "parallel_requests": "Paralelné dopyty",
"load_balancing": "Vyrovnávanie záťaže", "load_balancing": "Vyrovnávanie záťaže",
"load_balancing_desc": "Dopytovať len jeden server v danom čase. AdGuard Home použije na výber servera vážený náhodný algoritmus, aby sa najrýchlejší server používal častejšie.", "load_balancing_desc": "Dopytuje sa súčasne len jeden upstream server. AdGuard Home používa vážený náhodný algoritmus na výber serverov s najnižším počtom neúspešných vyhľadávaní a najnižším priemerným časom vyhľadávania.",
"bootstrap_dns": "Bootstrap DNS servery", "bootstrap_dns": "Bootstrap DNS servery",
"bootstrap_dns_desc": "IP adresy serverov DNS používaných na rozlíšenie IP adries prekladačov DoH/DoT, ktoré zadáte ako upstream. Komentáre nie sú povolené.", "bootstrap_dns_desc": "IP adresy serverov DNS používaných na rozlíšenie IP adries prekladačov DoH/DoT, ktoré zadáte ako upstream. Komentáre nie sú povolené.",
"fallback_dns_title": "Záložné servery DNS", "fallback_dns_title": "Záložné servery DNS",
@ -89,7 +89,7 @@
"form_enter_hostname": "Zadajte meno hostiteľa", "form_enter_hostname": "Zadajte meno hostiteľa",
"error_details": "Podrobnosti chyby", "error_details": "Podrobnosti chyby",
"response_details": "Podrobnosti odpovede", "response_details": "Podrobnosti odpovede",
"request_details": "Podrobnosti požiadavky", "request_details": "Podrobnosti dopytu",
"client_details": "Podrobnosti klienta", "client_details": "Podrobnosti klienta",
"details": "Podrobnosti", "details": "Podrobnosti",
"back": "Naspäť", "back": "Naspäť",
@ -308,7 +308,7 @@
"form_enter_rate_limit": "Zadajte rýchlostný limit", "form_enter_rate_limit": "Zadajte rýchlostný limit",
"rate_limit": "Rýchlostný limit", "rate_limit": "Rýchlostný limit",
"edns_enable": "Povoliť klientsku podsiete EDNS", "edns_enable": "Povoliť klientsku podsiete EDNS",
"edns_cs_desc": "Pridáva možnosť EDNS Client Subnet (ECS) do upstream požiadaviek a zapíše hodnoty odoslané klientmi do denníka dopytov.", "edns_cs_desc": "Pridáva možnosť EDNS Client Subnet (ECS) do upstream dopytov a zapíše hodnoty odoslané klientami do denníka dopytov.",
"edns_use_custom_ip": "Použiť vlastnú IP adresu pre EDNS", "edns_use_custom_ip": "Použiť vlastnú IP adresu pre EDNS",
"edns_use_custom_ip_desc": "Povoliť používanie vlastnej IP adresy pre EDNS", "edns_use_custom_ip_desc": "Povoliť používanie vlastnej IP adresy pre EDNS",
"rate_limit_desc": "Počet požiadaviek za sekundu, ktoré môže jeden klient vykonať. Nastavenie na hodnotu 0 znamená neobmedzene.", "rate_limit_desc": "Počet požiadaviek za sekundu, ktoré môže jeden klient vykonať. Nastavenie na hodnotu 0 znamená neobmedzene.",
@ -480,9 +480,9 @@
"access_title": "Nastavenia prístupu", "access_title": "Nastavenia prístupu",
"access_desc": "Tu môžete konfigurovať pravidlá prístupu pre server DNS AdGuard Home.", "access_desc": "Tu môžete konfigurovať pravidlá prístupu pre server DNS AdGuard Home.",
"access_allowed_title": "Povolení klienti", "access_allowed_title": "Povolení klienti",
"access_allowed_desc": "Zoznam CIDR, IP adries alebo <a>ClientID</a>. Ak tento zoznam obsahuje položky, AdGuard Home bude akceptovať požiadavky iba od týchto klientov.", "access_allowed_desc": "Zoznam CIDR, IP adries alebo <a>ClientID</a>. Ak tento zoznam obsahuje položky, AdGuard Home bude akceptovať dopyty iba od týchto klientov.",
"access_disallowed_title": "Nepovolení klienti", "access_disallowed_title": "Nepovolení klienti",
"access_disallowed_desc": "Zoznam CIDR, IP adries alebo <a>ClientID</a>. Ak tento zoznam obsahuje položky, AdGuard Home zruší požiadavky od týchto klientov. Toto pole sa ignoruje, ak sú v poli Povolení klienti položky.", "access_disallowed_desc": "Zoznam CIDR, IP adries alebo <a>ClientID</a>. Ak tento zoznam obsahuje položky, AdGuard Home zruší dopyty od týchto klientov. Toto pole sa ignoruje, ak sú v poli Povolení klienti položky.",
"access_blocked_title": "Nepovolené domény", "access_blocked_title": "Nepovolené domény",
"access_blocked_desc": "Nesmie byť zamieňaná s filtrami. AdGuard Home zruší DNS dopyty, ktoré sa zhodujú s týmito doménami, a tieto dopyty sa nezobrazia ani v denníku dopytov. Môžete určiť presné názvy domén, zástupné znaky alebo pravidlá filtrovania URL adries, napr. \"example.org\", \"*.example.org\" alebo ||example.org^\" zodpovedajúcim spôsobom.", "access_blocked_desc": "Nesmie byť zamieňaná s filtrami. AdGuard Home zruší DNS dopyty, ktoré sa zhodujú s týmito doménami, a tieto dopyty sa nezobrazia ani v denníku dopytov. Môžete určiť presné názvy domén, zástupné znaky alebo pravidlá filtrovania URL adries, napr. \"example.org\", \"*.example.org\" alebo ||example.org^\" zodpovedajúcim spôsobom.",
"access_settings_saved": "Nastavenia prístupu úspešne uložené", "access_settings_saved": "Nastavenia prístupu úspešne uložené",

View File

@ -6,7 +6,7 @@
"upstream_parallel": "Tüm üst sunucuları eş zamanlı sorgulayarak çözümlemeyi hızlandırmak için paralel sorgular kullanın.", "upstream_parallel": "Tüm üst sunucuları eş zamanlı sorgulayarak çözümlemeyi hızlandırmak için paralel sorgular kullanın.",
"parallel_requests": "Paralel istekler", "parallel_requests": "Paralel istekler",
"load_balancing": "Yük dengeleme", "load_balancing": "Yük dengeleme",
"load_balancing_desc": "Her seferde bir üst sunucuyu sorgulayın. AdGuard Home, sunucuyu seçmek için ağırlıklı rastgele algoritmasını kullanır, böylece en hızlı sunucu daha sık kullanılır.", "load_balancing_desc": "Aynı anda bir üst kaynak sunucusunu sorgulayın. AdGuard Home, en düşük başarısız arama sayısına ve en düşük ortalama arama süresine sahip sunucuları seçmek için ağırlıklı rastgele bir algoritma kullanır.",
"bootstrap_dns": "DNS Önyükleme sunucuları", "bootstrap_dns": "DNS Önyükleme sunucuları",
"bootstrap_dns_desc": "Üst kaynak olarak belirttiğiniz DoH/DoT çözümleyicilerin IP adreslerini çözümlemek için kullanılan DNS sunucularının IP adresleri. Yorumlara izin verilmez.", "bootstrap_dns_desc": "Üst kaynak olarak belirttiğiniz DoH/DoT çözümleyicilerin IP adreslerini çözümlemek için kullanılan DNS sunucularının IP adresleri. Yorumlara izin verilmez.",
"fallback_dns_title": "Yedek DNS sunucuları", "fallback_dns_title": "Yedek DNS sunucuları",

View File

@ -6,7 +6,7 @@
"upstream_parallel": "使用并行请求以同时查询所有上游服务器来加快解析速度。", "upstream_parallel": "使用并行请求以同时查询所有上游服务器来加快解析速度。",
"parallel_requests": "并行请求", "parallel_requests": "并行请求",
"load_balancing": "负载均衡", "load_balancing": "负载均衡",
"load_balancing_desc": "一次查询一台服务器。AdGuard Home 将使用加权随机算法来选择服务器,以便更常使用最快的服务器。", "load_balancing_desc": "一次查询一台服务器。AdGuard Home 使用加权随机算法来选择具有最少失败查找和最低平均查找时间的服务器。",
"bootstrap_dns": "Bootstrap DNS 服务器", "bootstrap_dns": "Bootstrap DNS 服务器",
"bootstrap_dns_desc": "DNS 服务器的 IP 地址,用于解析指定为上游的 DoH/DoT 解析器的 IP 地址。不允许添加注释。", "bootstrap_dns_desc": "DNS 服务器的 IP 地址,用于解析指定为上游的 DoH/DoT 解析器的 IP 地址。不允许添加注释。",
"fallback_dns_title": "后备 DNS 服务器", "fallback_dns_title": "后备 DNS 服务器",

View File

@ -6,7 +6,7 @@
"upstream_parallel": "透過同時地查詢所有上游的伺服器,使用並行的查詢以加速解析。", "upstream_parallel": "透過同時地查詢所有上游的伺服器,使用並行的查詢以加速解析。",
"parallel_requests": "並行的請求", "parallel_requests": "並行的請求",
"load_balancing": "負載平衡", "load_balancing": "負載平衡",
"load_balancing_desc": "每次查詢一個上游伺服器。AdGuard Home 使用它的加權隨機的演算法來選擇伺服器,以便最快的伺服器被更常使用。", "load_balancing_desc": "一次查詢一台伺服器。AdGuard Home 使用加權隨機演算法來選擇具有最少失敗查詢和最低平均查詢時間的伺服器。",
"bootstrap_dns": "自我啟動BootstrapDNS 伺服器", "bootstrap_dns": "自我啟動BootstrapDNS 伺服器",
"bootstrap_dns_desc": "DNS 伺服器的 IP 位址,用於解析您指定為上游伺服器的 DoH/DoT 解析器的 IP 位址。不允許註釋。", "bootstrap_dns_desc": "DNS 伺服器的 IP 位址,用於解析您指定為上游伺服器的 DoH/DoT 解析器的 IP 位址。不允許註釋。",
"fallback_dns_title": "應變 DNS 伺服器", "fallback_dns_title": "應變 DNS 伺服器",

View File

@ -1,26 +1,15 @@
import { import { sortIp, countClientsStatistics, findAddressType, subnetMaskToBitMask } from '../helpers/helpers';
sortIp,
countClientsStatistics,
findAddressType,
subnetMaskToBitMask,
} from '../helpers/helpers';
import { ADDRESS_TYPES } from '../helpers/constants'; import { ADDRESS_TYPES } from '../helpers/constants';
describe('sortIp', () => { describe('sortIp', () => {
describe('ipv4', () => { describe('ipv4', () => {
test('one octet differ', () => { test('one octet differ', () => {
const arr = [ const arr = ['127.0.2.0', '127.0.3.0', '127.0.1.0'];
'127.0.2.0', const sortedArr = ['127.0.1.0', '127.0.2.0', '127.0.3.0'];
'127.0.3.0',
'127.0.1.0',
];
const sortedArr = [
'127.0.1.0',
'127.0.2.0',
'127.0.3.0',
];
expect(arr.sort(sortIp)).toStrictEqual(sortedArr); expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
}); });
test('few octets differ', () => { test('few octets differ', () => {
const arr = [ const arr = [
'192.168.11.10', '192.168.11.10',
@ -58,6 +47,7 @@ describe('sortIp', () => {
'192.168.11.10', '192.168.11.10',
'192.168.11.11', '192.168.11.11',
]; ];
expect(arr.sort(sortIp)).toStrictEqual(sortedArr); expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
// Example from issue https://github.com/AdguardTeam/AdGuardHome/issues/1778#issuecomment-640937599 // Example from issue https://github.com/AdguardTeam/AdGuardHome/issues/1778#issuecomment-640937599
@ -83,36 +73,26 @@ describe('sortIp', () => {
'192.168.2.200', '192.168.2.200',
'192.168.3.1', '192.168.3.1',
]; ];
expect(arr2.sort(sortIp)).toStrictEqual(sortedArr2); expect(arr2.sort(sortIp)).toStrictEqual(sortedArr2);
}); });
}); });
describe('ipv6', () => { describe('ipv6', () => {
test('only long form', () => { test('only long form', () => {
const arr = [ const arr = ['2001:db8:11a3:9d7:0:0:0:2', '2001:db8:11a3:9d7:0:0:0:3', '2001:db8:11a3:9d7:0:0:0:1'];
'2001:db8:11a3:9d7:0:0:0:2', const sortedArr = ['2001:db8:11a3:9d7:0:0:0:1', '2001:db8:11a3:9d7:0:0:0:2', '2001:db8:11a3:9d7:0:0:0:3'];
'2001:db8:11a3:9d7:0:0:0:3',
'2001:db8:11a3:9d7:0:0:0:1',
];
const sortedArr = [
'2001:db8:11a3:9d7:0:0:0:1',
'2001:db8:11a3:9d7:0:0:0:2',
'2001:db8:11a3:9d7:0:0:0:3',
];
expect(arr.sort(sortIp)).toStrictEqual(sortedArr); expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
}); });
test('only short form', () => { test('only short form', () => {
const arr = [ const arr = ['2001:db8::', '2001:db7::', '2001:db9::'];
'2001:db8::', const sortedArr = ['2001:db7::', '2001:db8::', '2001:db9::'];
'2001:db7::',
'2001:db9::',
];
const sortedArr = [
'2001:db7::',
'2001:db8::',
'2001:db9::',
];
expect(arr.sort(sortIp)).toStrictEqual(sortedArr); expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
}); });
test('long and short forms', () => { test('long and short forms', () => {
const arr = [ const arr = [
'2001:db8::', '2001:db8::',
@ -130,9 +110,11 @@ describe('sortIp', () => {
'2001:db7:11a3:9d7:0:0:0:2', '2001:db7:11a3:9d7:0:0:0:2',
'2001:db8::', '2001:db8::',
]; ];
expect(arr.sort(sortIp)).toStrictEqual(sortedArr); expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
}); });
}); });
describe('ipv4 and ipv6', () => { describe('ipv4 and ipv6', () => {
test('ipv6 long form', () => { test('ipv6 long form', () => {
const arr = [ const arr = [
@ -151,8 +133,10 @@ describe('sortIp', () => {
'2001:db8:11a3:9d7:0:0:0:2', '2001:db8:11a3:9d7:0:0:0:2',
'2001:db8:11a3:9d7:0:0:0:3', '2001:db8:11a3:9d7:0:0:0:3',
]; ];
expect(arr.sort(sortIp)).toStrictEqual(sortedArr); expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
}); });
test('ipv6 short form', () => { test('ipv6 short form', () => {
const arr = [ const arr = [
'2001:db8:11a3:9d7::1', '2001:db8:11a3:9d7::1',
@ -170,8 +154,10 @@ describe('sortIp', () => {
'2001:db8:11a3:9d7::2', '2001:db8:11a3:9d7::2',
'2001:db8:11a3:9d7::3', '2001:db8:11a3:9d7::3',
]; ];
expect(arr.sort(sortIp)).toStrictEqual(sortedArr); expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
}); });
test('ipv6 long and short forms', () => { test('ipv6 long and short forms', () => {
const arr = [ const arr = [
'2001:db8:11a3:9d7::1', '2001:db8:11a3:9d7::1',
@ -189,8 +175,10 @@ describe('sortIp', () => {
'2001:db8:11a3:9d7:0:0:0:2', '2001:db8:11a3:9d7:0:0:0:2',
'2001:db8:11a3:9d7::3', '2001:db8:11a3:9d7::3',
]; ];
expect(arr.sort(sortIp)).toStrictEqual(sortedArr); expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
}); });
test('always put ipv4 before ipv6', () => { test('always put ipv4 before ipv6', () => {
const arr = [ const arr = [
'::1', '::1',
@ -210,40 +198,26 @@ describe('sortIp', () => {
'2001:db8:11a3:9d7::1', '2001:db8:11a3:9d7::1',
'2001:db8:11a3:9d7:0:0:0:2', '2001:db8:11a3:9d7:0:0:0:2',
]; ];
expect(arr.sort(sortIp)).toStrictEqual(sortedArr); expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
}); });
}); });
describe('cidr', () => { describe('cidr', () => {
test('only ipv4 cidr', () => { test('only ipv4 cidr', () => {
const arr = [ const arr = ['192.168.0.1/9', '192.168.0.1/7', '192.168.0.1/8'];
'192.168.0.1/9', const sortedArr = ['192.168.0.1/7', '192.168.0.1/8', '192.168.0.1/9'];
'192.168.0.1/7',
'192.168.0.1/8',
];
const sortedArr = [
'192.168.0.1/7',
'192.168.0.1/8',
'192.168.0.1/9',
];
expect(arr.sort(sortIp)).toStrictEqual(sortedArr); expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
}); });
test('ipv4 and cidr ipv4', () => { test('ipv4 and cidr ipv4', () => {
const arr = [ const arr = ['192.168.0.1/9', '192.168.0.1', '192.168.0.1/32', '192.168.0.1/7', '192.168.0.1/8'];
'192.168.0.1/9', const sortedArr = ['192.168.0.1/7', '192.168.0.1/8', '192.168.0.1/9', '192.168.0.1/32', '192.168.0.1'];
'192.168.0.1',
'192.168.0.1/32',
'192.168.0.1/7',
'192.168.0.1/8',
];
const sortedArr = [
'192.168.0.1/7',
'192.168.0.1/8',
'192.168.0.1/9',
'192.168.0.1/32',
'192.168.0.1',
];
expect(arr.sort(sortIp)).toStrictEqual(sortedArr); expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
}); });
test('only ipv6 cidr', () => { test('only ipv6 cidr', () => {
const arr = [ const arr = [
'2001:db8:11a3:9d7::1/32', '2001:db8:11a3:9d7::1/32',
@ -257,8 +231,10 @@ describe('sortIp', () => {
'2001:db8:11a3:9d7::1/64', '2001:db8:11a3:9d7::1/64',
'2001:db8:11a3:9d7::1/128', '2001:db8:11a3:9d7::1/128',
]; ];
expect(arr.sort(sortIp)).toStrictEqual(sortedArr); expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
}); });
test('ipv6 and cidr ipv6', () => { test('ipv6 and cidr ipv6', () => {
const arr = [ const arr = [
'2001:db8:11a3:9d7::1/32', '2001:db8:11a3:9d7::1/32',
@ -274,9 +250,11 @@ describe('sortIp', () => {
'2001:db8:11a3:9d7::1/128', '2001:db8:11a3:9d7::1/128',
'2001:db8:11a3:9d7::1', '2001:db8:11a3:9d7::1',
]; ];
expect(arr.sort(sortIp)).toStrictEqual(sortedArr); expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
}); });
}); });
describe('invalid input', () => { describe('invalid input', () => {
const originalWarn = console.warn; const originalWarn = console.warn;
@ -291,21 +269,29 @@ describe('sortIp', () => {
test('invalid strings', () => { test('invalid strings', () => {
const arr = ['invalid ip', 'invalid cidr']; const arr = ['invalid ip', 'invalid cidr'];
expect(arr.sort(sortIp)).toStrictEqual(arr); expect(arr.sort(sortIp)).toStrictEqual(arr);
}); });
test('invalid ip', () => { test('invalid ip', () => {
const arr = ['127.0.0.2.', '.127.0.0.1.', '.2001:db8:11a3:9d7:0:0:0:0']; const arr = ['127.0.0.2.', '.127.0.0.1.', '.2001:db8:11a3:9d7:0:0:0:0'];
expect(arr.sort(sortIp)).toStrictEqual(arr); expect(arr.sort(sortIp)).toStrictEqual(arr);
}); });
test('invalid cidr', () => { test('invalid cidr', () => {
const arr = ['127.0.0.2/33', '2001:db8:11a3:9d7:0:0:0:0/129']; const arr = ['127.0.0.2/33', '2001:db8:11a3:9d7:0:0:0:0/129'];
expect(arr.sort(sortIp)).toStrictEqual(arr); expect(arr.sort(sortIp)).toStrictEqual(arr);
}); });
test('valid and invalid ip', () => { test('valid and invalid ip', () => {
const arr = ['127.0.0.4.', '127.0.0.1', '.127.0.0.3', '127.0.0.2']; const arr = ['127.0.0.4.', '127.0.0.1', '.127.0.0.3', '127.0.0.2'];
expect(arr.sort(sortIp)).toStrictEqual(arr); expect(arr.sort(sortIp)).toStrictEqual(arr);
}); });
}); });
describe('mixed', () => { describe('mixed', () => {
test('ipv4, ipv6 in short and long forms and cidr', () => { test('ipv4, ipv6 in short and long forms and cidr', () => {
const arr = [ const arr = [
@ -354,6 +340,7 @@ describe('sortIp', () => {
'2001:db8:11a3:9d7:0:0:0:1', '2001:db8:11a3:9d7:0:0:0:1',
'2001:db8:11a3:9d7:0:0:0:2', '2001:db8:11a3:9d7:0:0:0:2',
]; ];
expect(arr.sort(sortIp)).toStrictEqual(sortedArr); expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
}); });
}); });
@ -363,9 +350,11 @@ describe('findAddressType', () => {
describe('ip', () => { describe('ip', () => {
expect(findAddressType('127.0.0.1')).toStrictEqual(ADDRESS_TYPES.IP); expect(findAddressType('127.0.0.1')).toStrictEqual(ADDRESS_TYPES.IP);
}); });
describe('cidr', () => { describe('cidr', () => {
expect(findAddressType('127.0.0.1/8')).toStrictEqual(ADDRESS_TYPES.CIDR); expect(findAddressType('127.0.0.1/8')).toStrictEqual(ADDRESS_TYPES.CIDR);
}); });
describe('mac', () => { describe('mac', () => {
expect(findAddressType('00:1B:44:11:3A:B7')).toStrictEqual(ADDRESS_TYPES.UNKNOWN); expect(findAddressType('00:1B:44:11:3A:B7')).toStrictEqual(ADDRESS_TYPES.UNKNOWN);
}); });
@ -373,42 +362,59 @@ describe('findAddressType', () => {
describe('countClientsStatistics', () => { describe('countClientsStatistics', () => {
test('single ip', () => { test('single ip', () => {
expect(countClientsStatistics(['127.0.0.1'], { expect(
countClientsStatistics(['127.0.0.1'], {
'127.0.0.1': 1, '127.0.0.1': 1,
})).toStrictEqual(1); }),
).toStrictEqual(1);
}); });
test('multiple ip', () => { test('multiple ip', () => {
expect(countClientsStatistics(['127.0.0.1', '127.0.0.2'], { expect(
countClientsStatistics(['127.0.0.1', '127.0.0.2'], {
'127.0.0.1': 1, '127.0.0.1': 1,
'127.0.0.2': 2, '127.0.0.2': 2,
})).toStrictEqual(1 + 2); }),
).toStrictEqual(1 + 2);
}); });
test('cidr', () => { test('cidr', () => {
expect(countClientsStatistics(['127.0.0.0/8'], { expect(
countClientsStatistics(['127.0.0.0/8'], {
'127.0.0.1': 1, '127.0.0.1': 1,
'127.0.0.2': 2, '127.0.0.2': 2,
})).toStrictEqual(1 + 2); }),
).toStrictEqual(1 + 2);
}); });
test('cidr and multiple ip', () => { test('cidr and multiple ip', () => {
expect(countClientsStatistics(['1.1.1.1', '2.2.2.2', '3.3.3.0/24'], { expect(
countClientsStatistics(['1.1.1.1', '2.2.2.2', '3.3.3.0/24'], {
'1.1.1.1': 1, '1.1.1.1': 1,
'2.2.2.2': 2, '2.2.2.2': 2,
'3.3.3.3': 3, '3.3.3.3': 3,
})).toStrictEqual(1 + 2 + 3); }),
).toStrictEqual(1 + 2 + 3);
}); });
test('mac', () => { test('mac', () => {
expect(countClientsStatistics(['00:1B:44:11:3A:B7', '2.2.2.2', '3.3.3.0/24'], { expect(
countClientsStatistics(['00:1B:44:11:3A:B7', '2.2.2.2', '3.3.3.0/24'], {
'1.1.1.1': 1, '1.1.1.1': 1,
'2.2.2.2': 2, '2.2.2.2': 2,
'3.3.3.3': 3, '3.3.3.3': 3,
})).toStrictEqual(2 + 3); }),
).toStrictEqual(2 + 3);
}); });
test('not found', () => { test('not found', () => {
expect(countClientsStatistics(['4.4.4.4', '5.5.5.5', '6.6.6.6'], { expect(
countClientsStatistics(['4.4.4.4', '5.5.5.5', '6.6.6.6'], {
'1.1.1.1': 1, '1.1.1.1': 1,
'2.2.2.2': 2, '2.2.2.2': 2,
'3.3.3.3': 3, '3.3.3.3': 3,
})).toStrictEqual(0); }),
).toStrictEqual(0);
}); });
}); });
@ -451,10 +457,12 @@ describe('subnetMaskToBitMask', () => {
test('correct for all subnetMasks', () => { test('correct for all subnetMasks', () => {
expect( expect(
subnetMasks.map((subnetMask) => { subnetMasks
.map((subnetMask) => {
const bitmask = subnetMaskToBitMask(subnetMask); const bitmask = subnetMaskToBitMask(subnetMask);
return subnetMasks[bitmask] === subnetMask; return subnetMasks[bitmask] === subnetMask;
}).every((res) => res === true), })
.every((res) => res === true),
).toEqual(true); ).toEqual(true);
}); });
}); });

View File

@ -3,13 +3,14 @@ import i18next from 'i18next';
import apiClient from '../api/Api'; import apiClient from '../api/Api';
import { addErrorToast, addSuccessToast } from './toasts'; import { addErrorToast, addSuccessToast } from './toasts';
import { splitByNewLine } from '../helpers/helpers'; import { splitByNewLine } from '../helpers/helpers';
export const getAccessListRequest = createAction('GET_ACCESS_LIST_REQUEST'); export const getAccessListRequest = createAction('GET_ACCESS_LIST_REQUEST');
export const getAccessListFailure = createAction('GET_ACCESS_LIST_FAILURE'); export const getAccessListFailure = createAction('GET_ACCESS_LIST_FAILURE');
export const getAccessListSuccess = createAction('GET_ACCESS_LIST_SUCCESS'); export const getAccessListSuccess = createAction('GET_ACCESS_LIST_SUCCESS');
export const getAccessList = () => async (dispatch) => { export const getAccessList = () => async (dispatch: any) => {
dispatch(getAccessListRequest()); dispatch(getAccessListRequest());
try { try {
const data = await apiClient.getAccessList(); const data = await apiClient.getAccessList();
@ -24,7 +25,7 @@ export const setAccessListRequest = createAction('SET_ACCESS_LIST_REQUEST');
export const setAccessListFailure = createAction('SET_ACCESS_LIST_FAILURE'); export const setAccessListFailure = createAction('SET_ACCESS_LIST_FAILURE');
export const setAccessListSuccess = createAction('SET_ACCESS_LIST_SUCCESS'); export const setAccessListSuccess = createAction('SET_ACCESS_LIST_SUCCESS');
export const setAccessList = (config) => async (dispatch) => { export const setAccessList = (config: any) => async (dispatch: any) => {
dispatch(setAccessListRequest()); dispatch(setAccessListRequest());
try { try {
const { allowed_clients, disallowed_clients, blocked_hosts } = config; const { allowed_clients, disallowed_clients, blocked_hosts } = config;
@ -48,7 +49,7 @@ export const toggleClientBlockRequest = createAction('TOGGLE_CLIENT_BLOCK_REQUES
export const toggleClientBlockFailure = createAction('TOGGLE_CLIENT_BLOCK_FAILURE'); export const toggleClientBlockFailure = createAction('TOGGLE_CLIENT_BLOCK_FAILURE');
export const toggleClientBlockSuccess = createAction('TOGGLE_CLIENT_BLOCK_SUCCESS'); export const toggleClientBlockSuccess = createAction('TOGGLE_CLIENT_BLOCK_SUCCESS');
export const toggleClientBlock = (ip, disallowed, disallowed_rule) => async (dispatch) => { export const toggleClientBlock = (ip: any, disallowed: any, disallowed_rule: any) => async (dispatch: any) => {
dispatch(toggleClientBlockRequest()); dispatch(toggleClientBlockRequest());
try { try {
const accessList = await apiClient.getAccessList(); const accessList = await apiClient.getAccessList();
@ -60,12 +61,10 @@ export const toggleClientBlock = (ip, disallowed, disallowed_rule) => async (dis
if (!disallowed_rule) { if (!disallowed_rule) {
allowed_clients = allowed_clients.concat(ip); allowed_clients = allowed_clients.concat(ip);
} else { } else {
disallowed_clients = disallowed_clients disallowed_clients = disallowed_clients.filter((client: any) => client !== disallowed_rule);
.filter((client) => client !== disallowed_rule);
} }
} else if (allowed_clients.length > 1) { } else if (allowed_clients.length > 1) {
allowed_clients = allowed_clients allowed_clients = allowed_clients.filter((client: any) => client !== disallowed_rule);
.filter((client) => client !== disallowed_rule);
} else { } else {
disallowed_clients = disallowed_clients.concat(ip); disallowed_clients = disallowed_clients.concat(ip);
} }

View File

@ -1,6 +1,7 @@
import { createAction } from 'redux-actions'; import { createAction } from 'redux-actions';
import i18next from 'i18next'; import i18next from 'i18next';
import apiClient from '../api/Api'; import apiClient from '../api/Api';
import { getClients } from './index'; import { getClients } from './index';
import { addErrorToast, addSuccessToast } from './toasts'; import { addErrorToast, addSuccessToast } from './toasts';
@ -10,7 +11,7 @@ export const addClientRequest = createAction('ADD_CLIENT_REQUEST');
export const addClientFailure = createAction('ADD_CLIENT_FAILURE'); export const addClientFailure = createAction('ADD_CLIENT_FAILURE');
export const addClientSuccess = createAction('ADD_CLIENT_SUCCESS'); export const addClientSuccess = createAction('ADD_CLIENT_SUCCESS');
export const addClient = (config) => async (dispatch) => { export const addClient = (config: any) => async (dispatch: any) => {
dispatch(addClientRequest()); dispatch(addClientRequest());
try { try {
await apiClient.addClient(config); await apiClient.addClient(config);
@ -28,7 +29,7 @@ export const deleteClientRequest = createAction('DELETE_CLIENT_REQUEST');
export const deleteClientFailure = createAction('DELETE_CLIENT_FAILURE'); export const deleteClientFailure = createAction('DELETE_CLIENT_FAILURE');
export const deleteClientSuccess = createAction('DELETE_CLIENT_SUCCESS'); export const deleteClientSuccess = createAction('DELETE_CLIENT_SUCCESS');
export const deleteClient = (config) => async (dispatch) => { export const deleteClient = (config: any) => async (dispatch: any) => {
dispatch(deleteClientRequest()); dispatch(deleteClientRequest());
try { try {
await apiClient.deleteClient(config); await apiClient.deleteClient(config);
@ -45,7 +46,7 @@ export const updateClientRequest = createAction('UPDATE_CLIENT_REQUEST');
export const updateClientFailure = createAction('UPDATE_CLIENT_FAILURE'); export const updateClientFailure = createAction('UPDATE_CLIENT_FAILURE');
export const updateClientSuccess = createAction('UPDATE_CLIENT_SUCCESS'); export const updateClientSuccess = createAction('UPDATE_CLIENT_SUCCESS');
export const updateClient = (config, name) => async (dispatch) => { export const updateClient = (config: any, name: any) => async (dispatch: any) => {
dispatch(updateClientRequest()); dispatch(updateClientRequest());
try { try {
const data = { name, data: { ...config } }; const data = { name, data: { ...config } };

View File

@ -2,6 +2,7 @@ import { createAction } from 'redux-actions';
import i18next from 'i18next'; import i18next from 'i18next';
import apiClient from '../api/Api'; import apiClient from '../api/Api';
import { splitByNewLine } from '../helpers/helpers'; import { splitByNewLine } from '../helpers/helpers';
import { addErrorToast, addSuccessToast } from './toasts'; import { addErrorToast, addSuccessToast } from './toasts';
@ -9,7 +10,7 @@ export const getDnsConfigRequest = createAction('GET_DNS_CONFIG_REQUEST');
export const getDnsConfigFailure = createAction('GET_DNS_CONFIG_FAILURE'); export const getDnsConfigFailure = createAction('GET_DNS_CONFIG_FAILURE');
export const getDnsConfigSuccess = createAction('GET_DNS_CONFIG_SUCCESS'); export const getDnsConfigSuccess = createAction('GET_DNS_CONFIG_SUCCESS');
export const getDnsConfig = () => async (dispatch) => { export const getDnsConfig = () => async (dispatch: any) => {
dispatch(getDnsConfigRequest()); dispatch(getDnsConfigRequest());
try { try {
const data = await apiClient.getDnsConfig(); const data = await apiClient.getDnsConfig();
@ -24,7 +25,7 @@ export const clearDnsCacheRequest = createAction('CLEAR_DNS_CACHE_REQUEST');
export const clearDnsCacheFailure = createAction('CLEAR_DNS_CACHE_FAILURE'); export const clearDnsCacheFailure = createAction('CLEAR_DNS_CACHE_FAILURE');
export const clearDnsCacheSuccess = createAction('CLEAR_DNS_CACHE_SUCCESS'); export const clearDnsCacheSuccess = createAction('CLEAR_DNS_CACHE_SUCCESS');
export const clearDnsCache = () => async (dispatch) => { export const clearDnsCache = () => async (dispatch: any) => {
dispatch(clearDnsCacheRequest()); dispatch(clearDnsCacheRequest());
try { try {
const data = await apiClient.clearCache(); const data = await apiClient.clearCache();
@ -40,7 +41,7 @@ export const setDnsConfigRequest = createAction('SET_DNS_CONFIG_REQUEST');
export const setDnsConfigFailure = createAction('SET_DNS_CONFIG_FAILURE'); export const setDnsConfigFailure = createAction('SET_DNS_CONFIG_FAILURE');
export const setDnsConfigSuccess = createAction('SET_DNS_CONFIG_SUCCESS'); export const setDnsConfigSuccess = createAction('SET_DNS_CONFIG_SUCCESS');
export const setDnsConfig = (config) => async (dispatch) => { export const setDnsConfig = (config: any) => async (dispatch: any) => {
dispatch(setDnsConfigRequest()); dispatch(setDnsConfigRequest());
try { try {
const data = { ...config }; const data = { ...config };

View File

@ -1,5 +1,6 @@
import { createAction } from 'redux-actions'; import { createAction } from 'redux-actions';
import apiClient from '../api/Api'; import apiClient from '../api/Api';
import { redirectToCurrentProtocol } from '../helpers/helpers'; import { redirectToCurrentProtocol } from '../helpers/helpers';
import { addErrorToast, addSuccessToast } from './toasts'; import { addErrorToast, addSuccessToast } from './toasts';
@ -7,7 +8,7 @@ export const getTlsStatusRequest = createAction('GET_TLS_STATUS_REQUEST');
export const getTlsStatusFailure = createAction('GET_TLS_STATUS_FAILURE'); export const getTlsStatusFailure = createAction('GET_TLS_STATUS_FAILURE');
export const getTlsStatusSuccess = createAction('GET_TLS_STATUS_SUCCESS'); export const getTlsStatusSuccess = createAction('GET_TLS_STATUS_SUCCESS');
export const getTlsStatus = () => async (dispatch) => { export const getTlsStatus = () => async (dispatch: any) => {
dispatch(getTlsStatusRequest()); dispatch(getTlsStatusRequest());
try { try {
const status = await apiClient.getTlsStatus(); const status = await apiClient.getTlsStatus();
@ -26,7 +27,7 @@ export const setTlsConfigFailure = createAction('SET_TLS_CONFIG_FAILURE');
export const setTlsConfigSuccess = createAction('SET_TLS_CONFIG_SUCCESS'); export const setTlsConfigSuccess = createAction('SET_TLS_CONFIG_SUCCESS');
export const dnsStatusSuccess = createAction('DNS_STATUS_SUCCESS'); export const dnsStatusSuccess = createAction('DNS_STATUS_SUCCESS');
export const setTlsConfig = (config) => async (dispatch, getState) => { export const setTlsConfig = (config: any) => async (dispatch: any, getState: any) => {
dispatch(setTlsConfigRequest()); dispatch(setTlsConfigRequest());
try { try {
const { httpPort } = getState().dashboard; const { httpPort } = getState().dashboard;
@ -67,7 +68,7 @@ export const validateTlsConfigRequest = createAction('VALIDATE_TLS_CONFIG_REQUES
export const validateTlsConfigFailure = createAction('VALIDATE_TLS_CONFIG_FAILURE'); export const validateTlsConfigFailure = createAction('VALIDATE_TLS_CONFIG_FAILURE');
export const validateTlsConfigSuccess = createAction('VALIDATE_TLS_CONFIG_SUCCESS'); export const validateTlsConfigSuccess = createAction('VALIDATE_TLS_CONFIG_SUCCESS');
export const validateTlsConfig = (config) => async (dispatch) => { export const validateTlsConfig = (config: any) => async (dispatch: any) => {
dispatch(validateTlsConfigRequest()); dispatch(validateTlsConfigRequest());
try { try {
const values = { ...config }; const values = { ...config };

View File

@ -13,7 +13,7 @@ export const getFilteringStatusRequest = createAction('GET_FILTERING_STATUS_REQU
export const getFilteringStatusFailure = createAction('GET_FILTERING_STATUS_FAILURE'); export const getFilteringStatusFailure = createAction('GET_FILTERING_STATUS_FAILURE');
export const getFilteringStatusSuccess = createAction('GET_FILTERING_STATUS_SUCCESS'); export const getFilteringStatusSuccess = createAction('GET_FILTERING_STATUS_SUCCESS');
export const getFilteringStatus = () => async (dispatch) => { export const getFilteringStatus = () => async (dispatch: any) => {
dispatch(getFilteringStatusRequest()); dispatch(getFilteringStatusRequest());
try { try {
const status = await apiClient.getFilteringStatus(); const status = await apiClient.getFilteringStatus();
@ -28,7 +28,7 @@ export const setRulesRequest = createAction('SET_RULES_REQUEST');
export const setRulesFailure = createAction('SET_RULES_FAILURE'); export const setRulesFailure = createAction('SET_RULES_FAILURE');
export const setRulesSuccess = createAction('SET_RULES_SUCCESS'); export const setRulesSuccess = createAction('SET_RULES_SUCCESS');
export const setRules = (rules) => async (dispatch) => { export const setRules = (rules: any) => async (dispatch: any) => {
dispatch(setRulesRequest()); dispatch(setRulesRequest());
try { try {
const normalizedRules = { const normalizedRules = {
@ -47,7 +47,9 @@ export const addFilterRequest = createAction('ADD_FILTER_REQUEST');
export const addFilterFailure = createAction('ADD_FILTER_FAILURE'); export const addFilterFailure = createAction('ADD_FILTER_FAILURE');
export const addFilterSuccess = createAction('ADD_FILTER_SUCCESS'); export const addFilterSuccess = createAction('ADD_FILTER_SUCCESS');
export const addFilter = (url, name, whitelist = false) => async (dispatch, getState) => { export const addFilter =
(url: any, name: any, whitelist = false) =>
async (dispatch: any, getState: any) => {
dispatch(addFilterRequest()); dispatch(addFilterRequest());
try { try {
await apiClient.addFilter({ url, name, whitelist }); await apiClient.addFilter({ url, name, whitelist });
@ -61,13 +63,15 @@ export const addFilter = (url, name, whitelist = false) => async (dispatch, getS
dispatch(addErrorToast({ error })); dispatch(addErrorToast({ error }));
dispatch(addFilterFailure()); dispatch(addFilterFailure());
} }
}; };
export const removeFilterRequest = createAction('REMOVE_FILTER_REQUEST'); export const removeFilterRequest = createAction('REMOVE_FILTER_REQUEST');
export const removeFilterFailure = createAction('REMOVE_FILTER_FAILURE'); export const removeFilterFailure = createAction('REMOVE_FILTER_FAILURE');
export const removeFilterSuccess = createAction('REMOVE_FILTER_SUCCESS'); export const removeFilterSuccess = createAction('REMOVE_FILTER_SUCCESS');
export const removeFilter = (url, whitelist = false) => async (dispatch, getState) => { export const removeFilter =
(url: any, whitelist = false) =>
async (dispatch: any, getState: any) => {
dispatch(removeFilterRequest()); dispatch(removeFilterRequest());
try { try {
await apiClient.removeFilter({ url, whitelist }); await apiClient.removeFilter({ url, whitelist });
@ -81,13 +85,15 @@ export const removeFilter = (url, whitelist = false) => async (dispatch, getStat
dispatch(addErrorToast({ error })); dispatch(addErrorToast({ error }));
dispatch(removeFilterFailure()); dispatch(removeFilterFailure());
} }
}; };
export const toggleFilterRequest = createAction('FILTER_TOGGLE_REQUEST'); export const toggleFilterRequest = createAction('FILTER_TOGGLE_REQUEST');
export const toggleFilterFailure = createAction('FILTER_TOGGLE_FAILURE'); export const toggleFilterFailure = createAction('FILTER_TOGGLE_FAILURE');
export const toggleFilterSuccess = createAction('FILTER_TOGGLE_SUCCESS'); export const toggleFilterSuccess = createAction('FILTER_TOGGLE_SUCCESS');
export const toggleFilterStatus = (url, data, whitelist = false) => async (dispatch) => { export const toggleFilterStatus =
(url: any, data: any, whitelist = false) =>
async (dispatch: any) => {
dispatch(toggleFilterRequest()); dispatch(toggleFilterRequest());
try { try {
await apiClient.setFilterUrl({ url, data, whitelist }); await apiClient.setFilterUrl({ url, data, whitelist });
@ -97,13 +103,15 @@ export const toggleFilterStatus = (url, data, whitelist = false) => async (dispa
dispatch(addErrorToast({ error })); dispatch(addErrorToast({ error }));
dispatch(toggleFilterFailure()); dispatch(toggleFilterFailure());
} }
}; };
export const editFilterRequest = createAction('EDIT_FILTER_REQUEST'); export const editFilterRequest = createAction('EDIT_FILTER_REQUEST');
export const editFilterFailure = createAction('EDIT_FILTER_FAILURE'); export const editFilterFailure = createAction('EDIT_FILTER_FAILURE');
export const editFilterSuccess = createAction('EDIT_FILTER_SUCCESS'); export const editFilterSuccess = createAction('EDIT_FILTER_SUCCESS');
export const editFilter = (url, data, whitelist = false) => async (dispatch, getState) => { export const editFilter =
(url: any, data: any, whitelist = false) =>
async (dispatch: any, getState: any) => {
dispatch(editFilterRequest()); dispatch(editFilterRequest());
try { try {
await apiClient.setFilterUrl({ url, data, whitelist }); await apiClient.setFilterUrl({ url, data, whitelist });
@ -117,13 +125,13 @@ export const editFilter = (url, data, whitelist = false) => async (dispatch, get
dispatch(addErrorToast({ error })); dispatch(addErrorToast({ error }));
dispatch(editFilterFailure()); dispatch(editFilterFailure());
} }
}; };
export const refreshFiltersRequest = createAction('FILTERING_REFRESH_REQUEST'); export const refreshFiltersRequest = createAction('FILTERING_REFRESH_REQUEST');
export const refreshFiltersFailure = createAction('FILTERING_REFRESH_FAILURE'); export const refreshFiltersFailure = createAction('FILTERING_REFRESH_FAILURE');
export const refreshFiltersSuccess = createAction('FILTERING_REFRESH_SUCCESS'); export const refreshFiltersSuccess = createAction('FILTERING_REFRESH_SUCCESS');
export const refreshFilters = (config) => async (dispatch) => { export const refreshFilters = (config: any) => async (dispatch: any) => {
dispatch(refreshFiltersRequest()); dispatch(refreshFiltersRequest());
dispatch(showLoading()); dispatch(showLoading());
try { try {
@ -150,7 +158,7 @@ export const setFiltersConfigRequest = createAction('SET_FILTERS_CONFIG_REQUEST'
export const setFiltersConfigFailure = createAction('SET_FILTERS_CONFIG_FAILURE'); export const setFiltersConfigFailure = createAction('SET_FILTERS_CONFIG_FAILURE');
export const setFiltersConfigSuccess = createAction('SET_FILTERS_CONFIG_SUCCESS'); export const setFiltersConfigSuccess = createAction('SET_FILTERS_CONFIG_SUCCESS');
export const setFiltersConfig = (config) => async (dispatch, getState) => { export const setFiltersConfig = (config: any) => async (dispatch: any, getState: any) => {
dispatch(setFiltersConfigRequest()); dispatch(setFiltersConfigRequest());
try { try {
const { enabled } = config; const { enabled } = config;
@ -180,16 +188,18 @@ export const checkHostSuccess = createAction('CHECK_HOST_SUCCESS');
* @param {string} host.name * @param {string} host.name
* @returns {undefined} * @returns {undefined}
*/ */
export const checkHost = (host) => async (dispatch) => { export const checkHost = (host: any) => async (dispatch: any) => {
dispatch(checkHostRequest()); dispatch(checkHostRequest());
try { try {
const data = await apiClient.checkHost(host); const data = await apiClient.checkHost(host);
const { name: hostname } = host; const { name: hostname } = host;
dispatch(checkHostSuccess({ dispatch(
checkHostSuccess({
hostname, hostname,
...data, ...data,
})); }),
);
} catch (error) { } catch (error) {
dispatch(addErrorToast({ error })); dispatch(addErrorToast({ error }));
dispatch(checkHostFailure()); dispatch(checkHostFailure());

View File

@ -38,7 +38,7 @@ export const showSettingsFailure = createAction('SETTINGS_FAILURE_SHOW');
* @param {*} status: boolean | SafeSearchConfig * @param {*} status: boolean | SafeSearchConfig
* @returns * @returns
*/ */
export const toggleSetting = (settingKey, status) => async (dispatch) => { export const toggleSetting = (settingKey: any, status: any) => async (dispatch: any) => {
let successMessage = ''; let successMessage = '';
try { try {
switch (settingKey) { switch (settingKey) {
@ -80,18 +80,20 @@ export const initSettingsRequest = createAction('SETTINGS_INIT_REQUEST');
export const initSettingsFailure = createAction('SETTINGS_INIT_FAILURE'); export const initSettingsFailure = createAction('SETTINGS_INIT_FAILURE');
export const initSettingsSuccess = createAction('SETTINGS_INIT_SUCCESS'); export const initSettingsSuccess = createAction('SETTINGS_INIT_SUCCESS');
export const initSettings = (settingsList = { export const initSettings =
safebrowsing: {}, parental: {}, (
}) => async (dispatch) => { settingsList = {
safebrowsing: {},
parental: {},
},
) =>
async (dispatch: any) => {
dispatch(initSettingsRequest()); dispatch(initSettingsRequest());
try { try {
const safebrowsingStatus = await apiClient.getSafebrowsingStatus(); const safebrowsingStatus = await apiClient.getSafebrowsingStatus();
const parentalStatus = await apiClient.getParentalStatus(); const parentalStatus = await apiClient.getParentalStatus();
const safesearchStatus = await apiClient.getSafesearchStatus(); const safesearchStatus = await apiClient.getSafesearchStatus();
const { const { safebrowsing, parental } = settingsList;
safebrowsing,
parental,
} = settingsList;
const newSettingsList = { const newSettingsList = {
safebrowsing: { safebrowsing: {
...safebrowsing, ...safebrowsing,
@ -110,34 +112,26 @@ export const initSettings = (settingsList = {
dispatch(addErrorToast({ error })); dispatch(addErrorToast({ error }));
dispatch(initSettingsFailure()); dispatch(initSettingsFailure());
} }
}; };
export const toggleProtectionRequest = createAction('TOGGLE_PROTECTION_REQUEST'); export const toggleProtectionRequest = createAction('TOGGLE_PROTECTION_REQUEST');
export const toggleProtectionFailure = createAction('TOGGLE_PROTECTION_FAILURE'); export const toggleProtectionFailure = createAction('TOGGLE_PROTECTION_FAILURE');
export const toggleProtectionSuccess = createAction('TOGGLE_PROTECTION_SUCCESS'); export const toggleProtectionSuccess = createAction('TOGGLE_PROTECTION_SUCCESS');
const getDisabledMessage = (time) => { const getDisabledMessage = (time: any) => {
switch (time) { switch (time) {
case DISABLE_PROTECTION_TIMINGS.HALF_MINUTE: case DISABLE_PROTECTION_TIMINGS.HALF_MINUTE:
return i18next.t( return i18next.t('disable_notify_for_seconds', {
'disable_notify_for_seconds', count: msToSeconds(DISABLE_PROTECTION_TIMINGS.HALF_MINUTE),
{ count: msToSeconds(DISABLE_PROTECTION_TIMINGS.HALF_MINUTE) }, });
);
case DISABLE_PROTECTION_TIMINGS.MINUTE: case DISABLE_PROTECTION_TIMINGS.MINUTE:
return i18next.t( return i18next.t('disable_notify_for_minutes', { count: msToMinutes(DISABLE_PROTECTION_TIMINGS.MINUTE) });
'disable_notify_for_minutes',
{ count: msToMinutes(DISABLE_PROTECTION_TIMINGS.MINUTE) },
);
case DISABLE_PROTECTION_TIMINGS.TEN_MINUTES: case DISABLE_PROTECTION_TIMINGS.TEN_MINUTES:
return i18next.t( return i18next.t('disable_notify_for_minutes', {
'disable_notify_for_minutes', count: msToMinutes(DISABLE_PROTECTION_TIMINGS.TEN_MINUTES),
{ count: msToMinutes(DISABLE_PROTECTION_TIMINGS.TEN_MINUTES) }, });
);
case DISABLE_PROTECTION_TIMINGS.HOUR: case DISABLE_PROTECTION_TIMINGS.HOUR:
return i18next.t( return i18next.t('disable_notify_for_hours', { count: msToHours(DISABLE_PROTECTION_TIMINGS.HOUR) });
'disable_notify_for_hours',
{ count: msToHours(DISABLE_PROTECTION_TIMINGS.HOUR) },
);
case DISABLE_PROTECTION_TIMINGS.TOMORROW: case DISABLE_PROTECTION_TIMINGS.TOMORROW:
return i18next.t('disable_notify_until_tomorrow'); return i18next.t('disable_notify_until_tomorrow');
default: default:
@ -145,7 +139,9 @@ const getDisabledMessage = (time) => {
} }
}; };
export const toggleProtection = (status, time = null) => async (dispatch) => { export const toggleProtection =
(status: any, time = null) =>
async (dispatch: any) => {
dispatch(toggleProtectionRequest()); dispatch(toggleProtectionRequest());
try { try {
const successMessage = status ? getDisabledMessage(time) : 'enabled_protection'; const successMessage = status ? getDisabledMessage(time) : 'enabled_protection';
@ -156,11 +152,11 @@ export const toggleProtection = (status, time = null) => async (dispatch) => {
dispatch(addErrorToast({ error })); dispatch(addErrorToast({ error }));
dispatch(toggleProtectionFailure()); dispatch(toggleProtectionFailure());
} }
}; };
export const setDisableDurationTime = createAction('SET_DISABLED_DURATION_TIME'); export const setDisableDurationTime = createAction('SET_DISABLED_DURATION_TIME');
export const setProtectionTimerTime = (updatedTime) => async (dispatch) => { export const setProtectionTimerTime = (updatedTime: any) => async (dispatch: any) => {
dispatch(setDisableDurationTime({ timeToEnableProtection: updatedTime })); dispatch(setDisableDurationTime({ timeToEnableProtection: updatedTime }));
}; };
@ -168,7 +164,9 @@ export const getVersionRequest = createAction('GET_VERSION_REQUEST');
export const getVersionFailure = createAction('GET_VERSION_FAILURE'); export const getVersionFailure = createAction('GET_VERSION_FAILURE');
export const getVersionSuccess = createAction('GET_VERSION_SUCCESS'); export const getVersionSuccess = createAction('GET_VERSION_SUCCESS');
export const getVersion = (recheck = false) => async (dispatch, getState) => { export const getVersion =
(recheck = false) =>
async (dispatch: any, getState: any) => {
dispatch(getVersionRequest()); dispatch(getVersionRequest());
try { try {
const data = await apiClient.getGlobalVersion({ recheck_now: recheck }); const data = await apiClient.getGlobalVersion({ recheck_now: recheck });
@ -188,20 +186,20 @@ export const getVersion = (recheck = false) => async (dispatch, getState) => {
dispatch(addErrorToast({ error: 'version_request_error' })); dispatch(addErrorToast({ error: 'version_request_error' }));
dispatch(getVersionFailure()); dispatch(getVersionFailure());
} }
}; };
export const getUpdateRequest = createAction('GET_UPDATE_REQUEST'); export const getUpdateRequest = createAction('GET_UPDATE_REQUEST');
export const getUpdateFailure = createAction('GET_UPDATE_FAILURE'); export const getUpdateFailure = createAction('GET_UPDATE_FAILURE');
export const getUpdateSuccess = createAction('GET_UPDATE_SUCCESS'); export const getUpdateSuccess = createAction('GET_UPDATE_SUCCESS');
const checkStatus = async (handleRequestSuccess, handleRequestError, attempts = 60) => { const checkStatus = async (handleRequestSuccess: any, handleRequestError: any, attempts = 60) => {
let timeout; let timeout;
if (attempts === 0) { if (attempts === 0) {
handleRequestError(); handleRequestError();
} }
const rmTimeout = (t) => t && clearTimeout(t); const rmTimeout = (t: any) => t && clearTimeout(t);
try { try {
const response = await axios.get(`${apiClient.baseUrl}/status`); const response = await axios.get(`${apiClient.baseUrl}/status`);
@ -220,25 +218,18 @@ const checkStatus = async (handleRequestSuccess, handleRequestError, attempts =
} }
} catch (error) { } catch (error) {
rmTimeout(timeout); rmTimeout(timeout);
timeout = setTimeout( timeout = setTimeout(checkStatus, CHECK_TIMEOUT, handleRequestSuccess, handleRequestError, attempts - 1);
checkStatus,
CHECK_TIMEOUT,
handleRequestSuccess,
handleRequestError,
attempts - 1,
);
} }
}; };
export const getUpdate = () => async (dispatch, getState) => { export const getUpdate = () => async (dispatch: any, getState: any) => {
const { dnsVersion } = getState().dashboard; const { dnsVersion } = getState().dashboard;
dispatch(getUpdateRequest()); dispatch(getUpdateRequest());
const handleRequestError = () => { const handleRequestError = () => {
const options = { const options = {
components: { components: {
a: <a href={MANUAL_UPDATE_LINK} target="_blank" a: <a href={MANUAL_UPDATE_LINK} target="_blank" rel="noopener noreferrer" />,
rel="noopener noreferrer" />,
}, },
}; };
@ -246,12 +237,13 @@ export const getUpdate = () => async (dispatch, getState) => {
dispatch(getUpdateFailure()); dispatch(getUpdateFailure());
}; };
const handleRequestSuccess = (response) => { const handleRequestSuccess = (response: any) => {
const responseVersion = response.data?.version; const responseVersion = response.data?.version;
if (dnsVersion !== responseVersion) { if (dnsVersion !== responseVersion) {
dispatch(getUpdateSuccess()); dispatch(getUpdateSuccess());
window.location.reload(true);
window.location.reload();
} }
}; };
@ -267,18 +259,20 @@ export const getClientsRequest = createAction('GET_CLIENTS_REQUEST');
export const getClientsFailure = createAction('GET_CLIENTS_FAILURE'); export const getClientsFailure = createAction('GET_CLIENTS_FAILURE');
export const getClientsSuccess = createAction('GET_CLIENTS_SUCCESS'); export const getClientsSuccess = createAction('GET_CLIENTS_SUCCESS');
export const getClients = () => async (dispatch) => { export const getClients = () => async (dispatch: any) => {
dispatch(getClientsRequest()); dispatch(getClientsRequest());
try { try {
const data = await apiClient.getClients(); const data = await apiClient.getClients();
const sortedClients = data.clients && sortClients(data.clients); const sortedClients = data.clients && sortClients(data.clients);
const sortedAutoClients = data.auto_clients && sortClients(data.auto_clients); const sortedAutoClients = data.auto_clients && sortClients(data.auto_clients);
dispatch(getClientsSuccess({ dispatch(
getClientsSuccess({
clients: sortedClients || [], clients: sortedClients || [],
autoClients: sortedAutoClients || [], autoClients: sortedAutoClients || [],
supportedTags: data.supported_tags || [], supportedTags: data.supported_tags || [],
})); }),
);
} catch (error) { } catch (error) {
dispatch(addErrorToast({ error })); dispatch(addErrorToast({ error }));
dispatch(getClientsFailure()); dispatch(getClientsFailure());
@ -289,7 +283,7 @@ export const getProfileRequest = createAction('GET_PROFILE_REQUEST');
export const getProfileFailure = createAction('GET_PROFILE_FAILURE'); export const getProfileFailure = createAction('GET_PROFILE_FAILURE');
export const getProfileSuccess = createAction('GET_PROFILE_SUCCESS'); export const getProfileSuccess = createAction('GET_PROFILE_SUCCESS');
export const getProfile = () => async (dispatch) => { export const getProfile = () => async (dispatch: any) => {
dispatch(getProfileRequest()); dispatch(getProfileRequest());
try { try {
const profile = await apiClient.getProfile(); const profile = await apiClient.getProfile();
@ -305,16 +299,17 @@ export const dnsStatusFailure = createAction('DNS_STATUS_FAILURE');
export const dnsStatusSuccess = createAction('DNS_STATUS_SUCCESS'); export const dnsStatusSuccess = createAction('DNS_STATUS_SUCCESS');
export const setDnsRunningStatus = createAction('SET_DNS_RUNNING_STATUS'); export const setDnsRunningStatus = createAction('SET_DNS_RUNNING_STATUS');
export const getDnsStatus = () => async (dispatch) => { export const getDnsStatus = () => async (dispatch: any) => {
dispatch(dnsStatusRequest()); dispatch(dnsStatusRequest());
const handleRequestError = () => { const handleRequestError = () => {
dispatch(addErrorToast({ error: 'dns_status_error' })); dispatch(addErrorToast({ error: 'dns_status_error' }));
dispatch(dnsStatusFailure()); dispatch(dnsStatusFailure());
window.location.reload(true);
window.location.reload();
}; };
const handleRequestSuccess = (response) => { const handleRequestSuccess = (response: any) => {
const dnsStatus = response.data; const dnsStatus = response.data;
if (dnsStatus.protection_disabled_duration === 0) { if (dnsStatus.protection_disabled_duration === 0) {
dnsStatus.protection_disabled_duration = null; dnsStatus.protection_disabled_duration = null;
@ -342,16 +337,17 @@ export const timerStatusRequest = createAction('TIMER_STATUS_REQUEST');
export const timerStatusFailure = createAction('TIMER_STATUS_FAILURE'); export const timerStatusFailure = createAction('TIMER_STATUS_FAILURE');
export const timerStatusSuccess = createAction('TIMER_STATUS_SUCCESS'); export const timerStatusSuccess = createAction('TIMER_STATUS_SUCCESS');
export const getTimerStatus = () => async (dispatch) => { export const getTimerStatus = () => async (dispatch: any) => {
dispatch(timerStatusRequest()); dispatch(timerStatusRequest());
const handleRequestError = () => { const handleRequestError = () => {
dispatch(addErrorToast({ error: 'dns_status_error' })); dispatch(addErrorToast({ error: 'dns_status_error' }));
dispatch(dnsStatusFailure()); dispatch(dnsStatusFailure());
window.location.reload(true);
window.location.reload();
}; };
const handleRequestSuccess = (response) => { const handleRequestSuccess = (response: any) => {
const dnsStatus = response.data; const dnsStatus = response.data;
if (dnsStatus.protection_disabled_duration === 0) { if (dnsStatus.protection_disabled_duration === 0) {
dnsStatus.protection_disabled_duration = null; dnsStatus.protection_disabled_duration = null;
@ -376,14 +372,9 @@ export const testUpstreamRequest = createAction('TEST_UPSTREAM_REQUEST');
export const testUpstreamFailure = createAction('TEST_UPSTREAM_FAILURE'); export const testUpstreamFailure = createAction('TEST_UPSTREAM_FAILURE');
export const testUpstreamSuccess = createAction('TEST_UPSTREAM_SUCCESS'); export const testUpstreamSuccess = createAction('TEST_UPSTREAM_SUCCESS');
export const testUpstream = ( export const testUpstream =
{ ({ bootstrap_dns, upstream_dns, local_ptr_upstreams, fallback_dns }: any, upstream_dns_file: any) =>
bootstrap_dns, async (dispatch: any) => {
upstream_dns,
local_ptr_upstreams,
fallback_dns,
}, upstream_dns_file,
) => async (dispatch) => {
dispatch(testUpstreamRequest()); dispatch(testUpstreamRequest());
try { try {
const removeComments = compose(filterOutComments, splitByNewLine); const removeComments = compose(filterOutComments, splitByNewLine);
@ -392,14 +383,15 @@ export const testUpstream = (
bootstrap_dns: splitByNewLine(bootstrap_dns), bootstrap_dns: splitByNewLine(bootstrap_dns),
private_upstream: splitByNewLine(local_ptr_upstreams), private_upstream: splitByNewLine(local_ptr_upstreams),
fallback_dns: splitByNewLine(fallback_dns), fallback_dns: splitByNewLine(fallback_dns),
...(upstream_dns_file ? null : { ...(upstream_dns_file
? null
: {
upstream_dns: removeComments(upstream_dns), upstream_dns: removeComments(upstream_dns),
}), }),
}; };
const upstreamResponse = await apiClient.testUpstream(config); const upstreamResponse = await apiClient.testUpstream(config);
const testMessages = Object.keys(upstreamResponse) const testMessages = Object.keys(upstreamResponse).map((key) => {
.map((key) => {
const message = upstreamResponse[key]; const message = upstreamResponse[key];
if (message.startsWith('WARNING:')) { if (message.startsWith('WARNING:')) {
dispatch(addErrorToast({ error: i18next.t('dns_test_warning_toast', { key }) })); dispatch(addErrorToast({ error: i18next.t('dns_test_warning_toast', { key }) }));
@ -407,7 +399,14 @@ export const testUpstream = (
const info = message.substring(0, message.indexOf(':')); const info = message.substring(0, message.indexOf(':'));
const [sectionKey, line] = info.split(' '); const [sectionKey, line] = info.split(' ');
const section = i18next.t(sectionKey); const section = i18next.t(sectionKey);
dispatch(addErrorToast({ error: i18next.t('dns_test_parsing_error_toast', { section, line }) })); dispatch(
addErrorToast({
error: i18next.t('dns_test_parsing_error_toast', {
section,
line,
}),
}),
);
} else if (message !== 'OK') { } else if (message !== 'OK') {
dispatch(addErrorToast({ error: i18next.t('dns_test_not_ok_toast', { key }) })); dispatch(addErrorToast({ error: i18next.t('dns_test_not_ok_toast', { key }) }));
} }
@ -423,30 +422,31 @@ export const testUpstream = (
dispatch(addErrorToast({ error })); dispatch(addErrorToast({ error }));
dispatch(testUpstreamFailure()); dispatch(testUpstreamFailure());
} }
}; };
export const testUpstreamWithFormValues = () => async (dispatch, getState) => { export const testUpstreamWithFormValues = () => async (dispatch: any, getState: any) => {
const { upstream_dns_file } = getState().dnsConfig; const { upstream_dns_file } = getState().dnsConfig;
const { const { bootstrap_dns, upstream_dns, local_ptr_upstreams, fallback_dns } =
bootstrap_dns, getState().form[FORM_NAME.UPSTREAM].values;
upstream_dns,
local_ptr_upstreams,
fallback_dns,
} = getState().form[FORM_NAME.UPSTREAM].values;
return dispatch(testUpstream({ return dispatch(
testUpstream(
{
bootstrap_dns, bootstrap_dns,
upstream_dns, upstream_dns,
local_ptr_upstreams, local_ptr_upstreams,
fallback_dns, fallback_dns,
}, upstream_dns_file)); },
upstream_dns_file,
),
);
}; };
export const changeLanguageRequest = createAction('CHANGE_LANGUAGE_REQUEST'); export const changeLanguageRequest = createAction('CHANGE_LANGUAGE_REQUEST');
export const changeLanguageFailure = createAction('CHANGE_LANGUAGE_FAILURE'); export const changeLanguageFailure = createAction('CHANGE_LANGUAGE_FAILURE');
export const changeLanguageSuccess = createAction('CHANGE_LANGUAGE_SUCCESS'); export const changeLanguageSuccess = createAction('CHANGE_LANGUAGE_SUCCESS');
export const changeLanguage = (lang) => async (dispatch) => { export const changeLanguage = (lang: any) => async (dispatch: any) => {
dispatch(changeLanguageRequest()); dispatch(changeLanguageRequest());
try { try {
await apiClient.changeLanguage({ language: lang }); await apiClient.changeLanguage({ language: lang });
@ -461,7 +461,7 @@ export const changeThemeRequest = createAction('CHANGE_THEME_REQUEST');
export const changeThemeFailure = createAction('CHANGE_THEME_FAILURE'); export const changeThemeFailure = createAction('CHANGE_THEME_FAILURE');
export const changeThemeSuccess = createAction('CHANGE_THEME_SUCCESS'); export const changeThemeSuccess = createAction('CHANGE_THEME_SUCCESS');
export const changeTheme = (theme) => async (dispatch) => { export const changeTheme = (theme: any) => async (dispatch: any) => {
dispatch(changeThemeRequest()); dispatch(changeThemeRequest());
try { try {
await apiClient.changeTheme({ theme }); await apiClient.changeTheme({ theme });
@ -476,7 +476,7 @@ export const getDhcpStatusRequest = createAction('GET_DHCP_STATUS_REQUEST');
export const getDhcpStatusSuccess = createAction('GET_DHCP_STATUS_SUCCESS'); export const getDhcpStatusSuccess = createAction('GET_DHCP_STATUS_SUCCESS');
export const getDhcpStatusFailure = createAction('GET_DHCP_STATUS_FAILURE'); export const getDhcpStatusFailure = createAction('GET_DHCP_STATUS_FAILURE');
export const getDhcpStatus = () => async (dispatch) => { export const getDhcpStatus = () => async (dispatch: any) => {
dispatch(getDhcpStatusRequest()); dispatch(getDhcpStatusRequest());
try { try {
const globalStatus = await apiClient.getGlobalStatus(); const globalStatus = await apiClient.getGlobalStatus();
@ -497,7 +497,7 @@ export const getDhcpInterfacesRequest = createAction('GET_DHCP_INTERFACES_REQUES
export const getDhcpInterfacesSuccess = createAction('GET_DHCP_INTERFACES_SUCCESS'); export const getDhcpInterfacesSuccess = createAction('GET_DHCP_INTERFACES_SUCCESS');
export const getDhcpInterfacesFailure = createAction('GET_DHCP_INTERFACES_FAILURE'); export const getDhcpInterfacesFailure = createAction('GET_DHCP_INTERFACES_FAILURE');
export const getDhcpInterfaces = () => async (dispatch) => { export const getDhcpInterfaces = () => async (dispatch: any) => {
dispatch(getDhcpInterfacesRequest()); dispatch(getDhcpInterfacesRequest());
try { try {
const interfaces = await apiClient.getDhcpInterfaces(); const interfaces = await apiClient.getDhcpInterfaces();
@ -512,7 +512,7 @@ export const findActiveDhcpRequest = createAction('FIND_ACTIVE_DHCP_REQUEST');
export const findActiveDhcpSuccess = createAction('FIND_ACTIVE_DHCP_SUCCESS'); export const findActiveDhcpSuccess = createAction('FIND_ACTIVE_DHCP_SUCCESS');
export const findActiveDhcpFailure = createAction('FIND_ACTIVE_DHCP_FAILURE'); export const findActiveDhcpFailure = createAction('FIND_ACTIVE_DHCP_FAILURE');
export const findActiveDhcp = (name) => async (dispatch, getState) => { export const findActiveDhcp = (name: any) => async (dispatch: any, getState: any) => {
dispatch(findActiveDhcpRequest()); dispatch(findActiveDhcpRequest());
try { try {
const req = { const req = {
@ -559,12 +559,12 @@ export const findActiveDhcp = (name) => async (dispatch, getState) => {
return; return;
} }
if ((hasV4Interface && v4.other_server.found === STATUS_RESPONSE.YES) if (
|| (hasV6Interface && v6.other_server.found === STATUS_RESPONSE.YES)) { (hasV4Interface && v4.other_server.found === STATUS_RESPONSE.YES) ||
(hasV6Interface && v6.other_server.found === STATUS_RESPONSE.YES)
) {
dispatch(addErrorToast({ error: 'dhcp_found' })); dispatch(addErrorToast({ error: 'dhcp_found' }));
} else if (hasV4Interface && v4.static_ip.static === STATUS_RESPONSE.NO } else if (hasV4Interface && v4.static_ip.static === STATUS_RESPONSE.NO && v4.static_ip.ip && interface_name) {
&& v4.static_ip.ip
&& interface_name) {
const warning = i18next.t('dhcp_dynamic_ip_found', { const warning = i18next.t('dhcp_dynamic_ip_found', {
interfaceName: interface_name, interfaceName: interface_name,
ipAddress: v4.static_ip.ip, ipAddress: v4.static_ip.ip,
@ -587,7 +587,7 @@ export const setDhcpConfigRequest = createAction('SET_DHCP_CONFIG_REQUEST');
export const setDhcpConfigSuccess = createAction('SET_DHCP_CONFIG_SUCCESS'); export const setDhcpConfigSuccess = createAction('SET_DHCP_CONFIG_SUCCESS');
export const setDhcpConfigFailure = createAction('SET_DHCP_CONFIG_FAILURE'); export const setDhcpConfigFailure = createAction('SET_DHCP_CONFIG_FAILURE');
export const setDhcpConfig = (values) => async (dispatch) => { export const setDhcpConfig = (values: any) => async (dispatch: any) => {
dispatch(setDhcpConfigRequest()); dispatch(setDhcpConfigRequest());
try { try {
await apiClient.setDhcpConfig(values); await apiClient.setDhcpConfig(values);
@ -603,7 +603,7 @@ export const toggleDhcpRequest = createAction('TOGGLE_DHCP_REQUEST');
export const toggleDhcpFailure = createAction('TOGGLE_DHCP_FAILURE'); export const toggleDhcpFailure = createAction('TOGGLE_DHCP_FAILURE');
export const toggleDhcpSuccess = createAction('TOGGLE_DHCP_SUCCESS'); export const toggleDhcpSuccess = createAction('TOGGLE_DHCP_SUCCESS');
export const toggleDhcp = (values) => async (dispatch) => { export const toggleDhcp = (values: any) => async (dispatch: any) => {
dispatch(toggleDhcpRequest()); dispatch(toggleDhcpRequest());
let config = { let config = {
...values, ...values,
@ -633,7 +633,7 @@ export const resetDhcpRequest = createAction('RESET_DHCP_REQUEST');
export const resetDhcpSuccess = createAction('RESET_DHCP_SUCCESS'); export const resetDhcpSuccess = createAction('RESET_DHCP_SUCCESS');
export const resetDhcpFailure = createAction('RESET_DHCP_FAILURE'); export const resetDhcpFailure = createAction('RESET_DHCP_FAILURE');
export const resetDhcp = () => async (dispatch) => { export const resetDhcp = () => async (dispatch: any) => {
dispatch(resetDhcpRequest()); dispatch(resetDhcpRequest());
try { try {
const status = await apiClient.resetDhcp(); const status = await apiClient.resetDhcp();
@ -649,7 +649,7 @@ export const resetDhcpLeasesRequest = createAction('RESET_DHCP_LEASES_REQUEST');
export const resetDhcpLeasesSuccess = createAction('RESET_DHCP_LEASES_SUCCESS'); export const resetDhcpLeasesSuccess = createAction('RESET_DHCP_LEASES_SUCCESS');
export const resetDhcpLeasesFailure = createAction('RESET_DHCP_LEASES_FAILURE'); export const resetDhcpLeasesFailure = createAction('RESET_DHCP_LEASES_FAILURE');
export const resetDhcpLeases = () => async (dispatch) => { export const resetDhcpLeases = () => async (dispatch: any) => {
dispatch(resetDhcpLeasesRequest()); dispatch(resetDhcpLeasesRequest());
try { try {
const status = await apiClient.resetDhcpLeases(); const status = await apiClient.resetDhcpLeases();
@ -667,7 +667,7 @@ export const addStaticLeaseRequest = createAction('ADD_STATIC_LEASE_REQUEST');
export const addStaticLeaseFailure = createAction('ADD_STATIC_LEASE_FAILURE'); export const addStaticLeaseFailure = createAction('ADD_STATIC_LEASE_FAILURE');
export const addStaticLeaseSuccess = createAction('ADD_STATIC_LEASE_SUCCESS'); export const addStaticLeaseSuccess = createAction('ADD_STATIC_LEASE_SUCCESS');
export const addStaticLease = (config) => async (dispatch) => { export const addStaticLease = (config: any) => async (dispatch: any) => {
dispatch(addStaticLeaseRequest()); dispatch(addStaticLeaseRequest());
try { try {
const name = config.hostname || config.ip; const name = config.hostname || config.ip;
@ -686,7 +686,7 @@ export const removeStaticLeaseRequest = createAction('REMOVE_STATIC_LEASE_REQUES
export const removeStaticLeaseFailure = createAction('REMOVE_STATIC_LEASE_FAILURE'); export const removeStaticLeaseFailure = createAction('REMOVE_STATIC_LEASE_FAILURE');
export const removeStaticLeaseSuccess = createAction('REMOVE_STATIC_LEASE_SUCCESS'); export const removeStaticLeaseSuccess = createAction('REMOVE_STATIC_LEASE_SUCCESS');
export const removeStaticLease = (config) => async (dispatch) => { export const removeStaticLease = (config: any) => async (dispatch: any) => {
dispatch(removeStaticLeaseRequest()); dispatch(removeStaticLeaseRequest());
try { try {
const name = config.hostname || config.ip; const name = config.hostname || config.ip;
@ -703,7 +703,7 @@ export const updateStaticLeaseRequest = createAction('UPDATE_STATIC_LEASE_REQUES
export const updateStaticLeaseFailure = createAction('UPDATE_STATIC_LEASE_FAILURE'); export const updateStaticLeaseFailure = createAction('UPDATE_STATIC_LEASE_FAILURE');
export const updateStaticLeaseSuccess = createAction('UPDATE_STATIC_LEASE_SUCCESS'); export const updateStaticLeaseSuccess = createAction('UPDATE_STATIC_LEASE_SUCCESS');
export const updateStaticLease = (config) => async (dispatch) => { export const updateStaticLease = (config: any) => async (dispatch: any) => {
dispatch(updateStaticLeaseRequest()); dispatch(updateStaticLeaseRequest());
try { try {
await apiClient.updateStaticLease(config); await apiClient.updateStaticLease(config);
@ -719,9 +719,8 @@ export const updateStaticLease = (config) => async (dispatch) => {
export const removeToast = createAction('REMOVE_TOAST'); export const removeToast = createAction('REMOVE_TOAST');
export const toggleBlocking = ( export const toggleBlocking =
type, domain, baseRule, baseUnblocking, (type: any, domain: any, baseRule?: string, baseUnblocking?: string) => async (dispatch: any, getState: any) => {
) => async (dispatch, getState) => {
const baseBlockingRule = baseRule || `||${domain}^$important`; const baseBlockingRule = baseRule || `||${domain}^$important`;
const baseUnblockingRule = baseUnblocking || `@@${baseBlockingRule}`; const baseUnblockingRule = baseUnblocking || `@@${baseBlockingRule}`;
const { userRules } = getState().filtering; const { userRules } = getState().filtering;
@ -751,10 +750,11 @@ export const toggleBlocking = (
} }
dispatch(getFilteringStatus()); dispatch(getFilteringStatus());
}; };
export const toggleBlockingForClient = (type, domain, client) => { export const toggleBlockingForClient = (type: any, domain: any, client: any) => {
const escapedClientName = client.replace(/'/g, '\\\'') const escapedClientName = client
.replace(/'/g, "\\'")
.replace(/"/g, '\\"') .replace(/"/g, '\\"')
.replace(/,/g, '\\,') .replace(/,/g, '\\,')
.replace(/\|/g, '\\|'); .replace(/\|/g, '\\|');

View File

@ -9,7 +9,7 @@ export const getDefaultAddressesRequest = createAction('GET_DEFAULT_ADDRESSES_RE
export const getDefaultAddressesFailure = createAction('GET_DEFAULT_ADDRESSES_FAILURE'); export const getDefaultAddressesFailure = createAction('GET_DEFAULT_ADDRESSES_FAILURE');
export const getDefaultAddressesSuccess = createAction('GET_DEFAULT_ADDRESSES_SUCCESS'); export const getDefaultAddressesSuccess = createAction('GET_DEFAULT_ADDRESSES_SUCCESS');
export const getDefaultAddresses = () => async (dispatch) => { export const getDefaultAddresses = () => async (dispatch: any) => {
dispatch(getDefaultAddressesRequest()); dispatch(getDefaultAddressesRequest());
try { try {
const addresses = await apiClient.getDefaultAddresses(); const addresses = await apiClient.getDefaultAddresses();
@ -24,13 +24,10 @@ export const setAllSettingsRequest = createAction('SET_ALL_SETTINGS_REQUEST');
export const setAllSettingsFailure = createAction('SET_ALL_SETTINGS_FAILURE'); export const setAllSettingsFailure = createAction('SET_ALL_SETTINGS_FAILURE');
export const setAllSettingsSuccess = createAction('SET_ALL_SETTINGS_SUCCESS'); export const setAllSettingsSuccess = createAction('SET_ALL_SETTINGS_SUCCESS');
export const setAllSettings = (values) => async (dispatch) => { export const setAllSettings = (values: any) => async (dispatch: any) => {
dispatch(setAllSettingsRequest()); dispatch(setAllSettingsRequest());
try { try {
const { const { confirm_password, ...config } = values;
confirm_password,
...config
} = values;
await apiClient.setAllSettings(config); await apiClient.setAllSettings(config);
dispatch(setAllSettingsSuccess()); dispatch(setAllSettingsSuccess());
@ -47,7 +44,7 @@ export const checkConfigRequest = createAction('CHECK_CONFIG_REQUEST');
export const checkConfigFailure = createAction('CHECK_CONFIG_FAILURE'); export const checkConfigFailure = createAction('CHECK_CONFIG_FAILURE');
export const checkConfigSuccess = createAction('CHECK_CONFIG_SUCCESS'); export const checkConfigSuccess = createAction('CHECK_CONFIG_SUCCESS');
export const checkConfig = (values) => async (dispatch) => { export const checkConfig = (values: any) => async (dispatch: any) => {
dispatch(checkConfigRequest()); dispatch(checkConfigRequest());
try { try {
const check = await apiClient.checkConfig(values); const check = await apiClient.checkConfig(values);

View File

@ -8,12 +8,12 @@ export const processLoginRequest = createAction('PROCESS_LOGIN_REQUEST');
export const processLoginFailure = createAction('PROCESS_LOGIN_FAILURE'); export const processLoginFailure = createAction('PROCESS_LOGIN_FAILURE');
export const processLoginSuccess = createAction('PROCESS_LOGIN_SUCCESS'); export const processLoginSuccess = createAction('PROCESS_LOGIN_SUCCESS');
export const processLogin = (values) => async (dispatch) => { export const processLogin = (values: any) => async (dispatch: any) => {
dispatch(processLoginRequest()); dispatch(processLoginRequest());
try { try {
await apiClient.login(values); await apiClient.login(values);
const dashboardUrl = window.location.origin const dashboardUrl =
+ window.location.pathname.replace(HTML_PAGES.LOGIN, HTML_PAGES.MAIN); window.location.origin + window.location.pathname.replace(HTML_PAGES.LOGIN, HTML_PAGES.MAIN);
window.location.replace(dashboardUrl); window.location.replace(dashboardUrl);
dispatch(processLoginSuccess()); dispatch(processLoginSuccess());
} catch (error) { } catch (error) {

View File

@ -1,13 +1,12 @@
import { createAction } from 'redux-actions'; import { createAction } from 'redux-actions';
import apiClient from '../api/Api'; import apiClient from '../api/Api';
import { normalizeLogs } from '../helpers/helpers'; import { normalizeLogs } from '../helpers/helpers';
import { import { DEFAULT_LOGS_FILTER, FORM_NAME, QUERY_LOGS_PAGE_LIMIT } from '../helpers/constants';
DEFAULT_LOGS_FILTER, FORM_NAME, QUERY_LOGS_PAGE_LIMIT,
} from '../helpers/constants';
import { addErrorToast, addSuccessToast } from './toasts'; import { addErrorToast, addSuccessToast } from './toasts';
const getLogsWithParams = async (config) => { const getLogsWithParams = async (config: any) => {
const { older_than, filter, ...values } = config; const { older_than, filter, ...values } = config;
const rawLogs = await apiClient.getQueryLog({ const rawLogs = await apiClient.getQueryLog({
...filter, ...filter,
@ -28,20 +27,20 @@ export const getAdditionalLogsRequest = createAction('GET_ADDITIONAL_LOGS_REQUES
export const getAdditionalLogsFailure = createAction('GET_ADDITIONAL_LOGS_FAILURE'); export const getAdditionalLogsFailure = createAction('GET_ADDITIONAL_LOGS_FAILURE');
export const getAdditionalLogsSuccess = createAction('GET_ADDITIONAL_LOGS_SUCCESS'); export const getAdditionalLogsSuccess = createAction('GET_ADDITIONAL_LOGS_SUCCESS');
const shortPollQueryLogs = async (data, filter, dispatch, getState, total) => { const shortPollQueryLogs = async (data: any, filter: any, dispatch: any, getState: any, total?: any) => {
const { logs, oldest } = data; const { logs, oldest } = data;
const totalData = total || { logs }; const totalData = total || { logs };
const queryForm = getState().form[FORM_NAME.LOGS_FILTER]; const queryForm = getState().form[FORM_NAME.LOGS_FILTER];
const currentQuery = queryForm && queryForm.values.search; const currentQuery = queryForm && queryForm.values.search;
const previousQuery = filter?.search; const previousQuery = filter?.search;
const isQueryTheSame = typeof previousQuery === 'string' const isQueryTheSame =
&& typeof currentQuery === 'string' typeof previousQuery === 'string' && typeof currentQuery === 'string' && previousQuery === currentQuery;
&& previousQuery === currentQuery;
const isShortPollingNeeded = (logs.length < QUERY_LOGS_PAGE_LIMIT const isShortPollingNeeded =
|| totalData.logs.length < QUERY_LOGS_PAGE_LIMIT) (logs.length < QUERY_LOGS_PAGE_LIMIT || totalData.logs.length < QUERY_LOGS_PAGE_LIMIT) &&
&& oldest !== '' && isQueryTheSame; oldest !== '' &&
isQueryTheSame;
if (isShortPollingNeeded) { if (isShortPollingNeeded) {
dispatch(getAdditionalLogsRequest()); dispatch(getAdditionalLogsRequest());
@ -75,22 +74,24 @@ export const getLogsRequest = createAction('GET_LOGS_REQUEST');
export const getLogsFailure = createAction('GET_LOGS_FAILURE'); export const getLogsFailure = createAction('GET_LOGS_FAILURE');
export const getLogsSuccess = createAction('GET_LOGS_SUCCESS'); export const getLogsSuccess = createAction('GET_LOGS_SUCCESS');
export const updateLogs = () => async (dispatch, getState) => { export const updateLogs = () => async (dispatch: any, getState: any) => {
try { try {
const { logs, oldest, older_than } = getState().queryLogs; const { logs, oldest, older_than } = getState().queryLogs;
dispatch(getLogsSuccess({ dispatch(
getLogsSuccess({
logs, logs,
oldest, oldest,
older_than, older_than,
})); }),
);
} catch (error) { } catch (error) {
dispatch(addErrorToast({ error })); dispatch(addErrorToast({ error }));
dispatch(getLogsFailure(error)); dispatch(getLogsFailure(error));
} }
}; };
export const getLogs = () => async (dispatch, getState) => { export const getLogs = () => async (dispatch: any, getState: any) => {
dispatch(getLogsRequest()); dispatch(getLogsRequest());
try { try {
const { isFiltered, filter, oldest } = getState().queryLogs; const { isFiltered, filter, oldest } = getState().queryLogs;
@ -121,26 +122,29 @@ export const setLogsFilterRequest = createAction('SET_LOGS_FILTER_REQUEST');
* @param {string} filter.response_status 'QUERY' field of RESPONSE_FILTER object * @param {string} filter.response_status 'QUERY' field of RESPONSE_FILTER object
* @returns function * @returns function
*/ */
export const setLogsFilter = (filter) => setLogsFilterRequest(filter); export const setLogsFilter = (filter: any) => setLogsFilterRequest(filter);
export const setFilteredLogsRequest = createAction('SET_FILTERED_LOGS_REQUEST'); export const setFilteredLogsRequest = createAction('SET_FILTERED_LOGS_REQUEST');
export const setFilteredLogsFailure = createAction('SET_FILTERED_LOGS_FAILURE'); export const setFilteredLogsFailure = createAction('SET_FILTERED_LOGS_FAILURE');
export const setFilteredLogsSuccess = createAction('SET_FILTERED_LOGS_SUCCESS'); export const setFilteredLogsSuccess = createAction('SET_FILTERED_LOGS_SUCCESS');
export const setFilteredLogs = (filter) => async (dispatch, getState) => { export const setFilteredLogs = (filter?: any) => async (dispatch: any, getState: any) => {
dispatch(setFilteredLogsRequest()); dispatch(setFilteredLogsRequest());
try { try {
const data = await getLogsWithParams({ const data = await getLogsWithParams({
older_than: '', older_than: '',
filter, filter,
}); });
const additionalData = await shortPollQueryLogs(data, filter, dispatch, getState); const additionalData = await shortPollQueryLogs(data, filter, dispatch, getState);
const updatedData = additionalData.logs ? { ...data, ...additionalData } : data; const updatedData = additionalData.logs ? { ...data, ...additionalData } : data;
dispatch(setFilteredLogsSuccess({ dispatch(
setFilteredLogsSuccess({
...updatedData, ...updatedData,
filter, filter,
})); }),
);
} catch (error) { } catch (error) {
dispatch(addErrorToast({ error })); dispatch(addErrorToast({ error }));
dispatch(setFilteredLogsFailure(error)); dispatch(setFilteredLogsFailure(error));
@ -149,7 +153,7 @@ export const setFilteredLogs = (filter) => async (dispatch, getState) => {
export const resetFilteredLogs = () => setFilteredLogs(DEFAULT_LOGS_FILTER); export const resetFilteredLogs = () => setFilteredLogs(DEFAULT_LOGS_FILTER);
export const refreshFilteredLogs = () => async (dispatch, getState) => { export const refreshFilteredLogs = () => async (dispatch: any, getState: any) => {
const { filter } = getState().queryLogs; const { filter } = getState().queryLogs;
await dispatch(setFilteredLogs(filter)); await dispatch(setFilteredLogs(filter));
}; };
@ -158,7 +162,7 @@ export const clearLogsRequest = createAction('CLEAR_LOGS_REQUEST');
export const clearLogsFailure = createAction('CLEAR_LOGS_FAILURE'); export const clearLogsFailure = createAction('CLEAR_LOGS_FAILURE');
export const clearLogsSuccess = createAction('CLEAR_LOGS_SUCCESS'); export const clearLogsSuccess = createAction('CLEAR_LOGS_SUCCESS');
export const clearLogs = () => async (dispatch) => { export const clearLogs = () => async (dispatch: any) => {
dispatch(clearLogsRequest()); dispatch(clearLogsRequest());
try { try {
await apiClient.clearQueryLog(); await apiClient.clearQueryLog();
@ -174,7 +178,7 @@ export const getLogsConfigRequest = createAction('GET_LOGS_CONFIG_REQUEST');
export const getLogsConfigFailure = createAction('GET_LOGS_CONFIG_FAILURE'); export const getLogsConfigFailure = createAction('GET_LOGS_CONFIG_FAILURE');
export const getLogsConfigSuccess = createAction('GET_LOGS_CONFIG_SUCCESS'); export const getLogsConfigSuccess = createAction('GET_LOGS_CONFIG_SUCCESS');
export const getLogsConfig = () => async (dispatch) => { export const getLogsConfig = () => async (dispatch: any) => {
dispatch(getLogsConfigRequest()); dispatch(getLogsConfigRequest());
try { try {
const data = await apiClient.getQueryLogConfig(); const data = await apiClient.getQueryLogConfig();
@ -189,7 +193,7 @@ export const setLogsConfigRequest = createAction('SET_LOGS_CONFIG_REQUEST');
export const setLogsConfigFailure = createAction('SET_LOGS_CONFIG_FAILURE'); export const setLogsConfigFailure = createAction('SET_LOGS_CONFIG_FAILURE');
export const setLogsConfigSuccess = createAction('SET_LOGS_CONFIG_SUCCESS'); export const setLogsConfigSuccess = createAction('SET_LOGS_CONFIG_SUCCESS');
export const setLogsConfig = (config) => async (dispatch) => { export const setLogsConfig = (config: any) => async (dispatch: any) => {
dispatch(setLogsConfigRequest()); dispatch(setLogsConfigRequest());
try { try {
await apiClient.setQueryLogConfig(config); await apiClient.setQueryLogConfig(config);

View File

@ -9,7 +9,7 @@ export const getRewritesListRequest = createAction('GET_REWRITES_LIST_REQUEST');
export const getRewritesListFailure = createAction('GET_REWRITES_LIST_FAILURE'); export const getRewritesListFailure = createAction('GET_REWRITES_LIST_FAILURE');
export const getRewritesListSuccess = createAction('GET_REWRITES_LIST_SUCCESS'); export const getRewritesListSuccess = createAction('GET_REWRITES_LIST_SUCCESS');
export const getRewritesList = () => async (dispatch) => { export const getRewritesList = () => async (dispatch: any) => {
dispatch(getRewritesListRequest()); dispatch(getRewritesListRequest());
try { try {
const data = await apiClient.getRewritesList(); const data = await apiClient.getRewritesList();
@ -24,7 +24,7 @@ export const addRewriteRequest = createAction('ADD_REWRITE_REQUEST');
export const addRewriteFailure = createAction('ADD_REWRITE_FAILURE'); export const addRewriteFailure = createAction('ADD_REWRITE_FAILURE');
export const addRewriteSuccess = createAction('ADD_REWRITE_SUCCESS'); export const addRewriteSuccess = createAction('ADD_REWRITE_SUCCESS');
export const addRewrite = (config) => async (dispatch) => { export const addRewrite = (config: any) => async (dispatch: any) => {
dispatch(addRewriteRequest()); dispatch(addRewriteRequest());
try { try {
await apiClient.addRewrite(config); await apiClient.addRewrite(config);
@ -47,7 +47,7 @@ export const updateRewriteSuccess = createAction('UPDATE_REWRITE_SUCCESS');
* @param {string} config.target - current DNS rewrite value * @param {string} config.target - current DNS rewrite value
* @param {string} config.update - updated DNS rewrite value * @param {string} config.update - updated DNS rewrite value
*/ */
export const updateRewrite = (config) => async (dispatch) => { export const updateRewrite = (config: any) => async (dispatch: any) => {
dispatch(updateRewriteRequest()); dispatch(updateRewriteRequest());
try { try {
await apiClient.updateRewrite(config); await apiClient.updateRewrite(config);
@ -65,7 +65,7 @@ export const deleteRewriteRequest = createAction('DELETE_REWRITE_REQUEST');
export const deleteRewriteFailure = createAction('DELETE_REWRITE_FAILURE'); export const deleteRewriteFailure = createAction('DELETE_REWRITE_FAILURE');
export const deleteRewriteSuccess = createAction('DELETE_REWRITE_SUCCESS'); export const deleteRewriteSuccess = createAction('DELETE_REWRITE_SUCCESS');
export const deleteRewrite = (config) => async (dispatch) => { export const deleteRewrite = (config: any) => async (dispatch: any) => {
dispatch(deleteRewriteRequest()); dispatch(deleteRewriteRequest());
try { try {
await apiClient.deleteRewrite(config); await apiClient.deleteRewrite(config);

View File

@ -6,7 +6,7 @@ export const getBlockedServicesRequest = createAction('GET_BLOCKED_SERVICES_REQU
export const getBlockedServicesFailure = createAction('GET_BLOCKED_SERVICES_FAILURE'); export const getBlockedServicesFailure = createAction('GET_BLOCKED_SERVICES_FAILURE');
export const getBlockedServicesSuccess = createAction('GET_BLOCKED_SERVICES_SUCCESS'); export const getBlockedServicesSuccess = createAction('GET_BLOCKED_SERVICES_SUCCESS');
export const getBlockedServices = () => async (dispatch) => { export const getBlockedServices = () => async (dispatch: any) => {
dispatch(getBlockedServicesRequest()); dispatch(getBlockedServicesRequest());
try { try {
const data = await apiClient.getBlockedServices(); const data = await apiClient.getBlockedServices();
@ -21,7 +21,7 @@ export const getAllBlockedServicesRequest = createAction('GET_ALL_BLOCKED_SERVIC
export const getAllBlockedServicesFailure = createAction('GET_ALL_BLOCKED_SERVICES_FAILURE'); export const getAllBlockedServicesFailure = createAction('GET_ALL_BLOCKED_SERVICES_FAILURE');
export const getAllBlockedServicesSuccess = createAction('GET_ALL_BLOCKED_SERVICES_SUCCESS'); export const getAllBlockedServicesSuccess = createAction('GET_ALL_BLOCKED_SERVICES_SUCCESS');
export const getAllBlockedServices = () => async (dispatch) => { export const getAllBlockedServices = () => async (dispatch: any) => {
dispatch(getAllBlockedServicesRequest()); dispatch(getAllBlockedServicesRequest());
try { try {
const data = await apiClient.getAllBlockedServices(); const data = await apiClient.getAllBlockedServices();
@ -36,7 +36,7 @@ export const updateBlockedServicesRequest = createAction('UPDATE_BLOCKED_SERVICE
export const updateBlockedServicesFailure = createAction('UPDATE_BLOCKED_SERVICES_FAILURE'); export const updateBlockedServicesFailure = createAction('UPDATE_BLOCKED_SERVICES_FAILURE');
export const updateBlockedServicesSuccess = createAction('UPDATE_BLOCKED_SERVICES_SUCCESS'); export const updateBlockedServicesSuccess = createAction('UPDATE_BLOCKED_SERVICES_SUCCESS');
export const updateBlockedServices = (values) => async (dispatch) => { export const updateBlockedServices = (values: any) => async (dispatch: any) => {
dispatch(updateBlockedServicesRequest()); dispatch(updateBlockedServicesRequest());
try { try {
await apiClient.updateBlockedServices(values); await apiClient.updateBlockedServices(values);

View File

@ -1,16 +1,14 @@
import { createAction } from 'redux-actions'; import { createAction } from 'redux-actions';
import apiClient from '../api/Api'; import apiClient from '../api/Api';
import { import { normalizeTopStats, secondsToMilliseconds, getParamsForClientsSearch, addClientInfo } from '../helpers/helpers';
normalizeTopStats, secondsToMilliseconds, getParamsForClientsSearch, addClientInfo,
} from '../helpers/helpers';
import { addErrorToast, addSuccessToast } from './toasts'; import { addErrorToast, addSuccessToast } from './toasts';
export const getStatsConfigRequest = createAction('GET_STATS_CONFIG_REQUEST'); export const getStatsConfigRequest = createAction('GET_STATS_CONFIG_REQUEST');
export const getStatsConfigFailure = createAction('GET_STATS_CONFIG_FAILURE'); export const getStatsConfigFailure = createAction('GET_STATS_CONFIG_FAILURE');
export const getStatsConfigSuccess = createAction('GET_STATS_CONFIG_SUCCESS'); export const getStatsConfigSuccess = createAction('GET_STATS_CONFIG_SUCCESS');
export const getStatsConfig = () => async (dispatch) => { export const getStatsConfig = () => async (dispatch: any) => {
dispatch(getStatsConfigRequest()); dispatch(getStatsConfigRequest());
try { try {
const data = await apiClient.getStatsConfig(); const data = await apiClient.getStatsConfig();
@ -25,7 +23,7 @@ export const setStatsConfigRequest = createAction('SET_STATS_CONFIG_REQUEST');
export const setStatsConfigFailure = createAction('SET_STATS_CONFIG_FAILURE'); export const setStatsConfigFailure = createAction('SET_STATS_CONFIG_FAILURE');
export const setStatsConfigSuccess = createAction('SET_STATS_CONFIG_SUCCESS'); export const setStatsConfigSuccess = createAction('SET_STATS_CONFIG_SUCCESS');
export const setStatsConfig = (config) => async (dispatch) => { export const setStatsConfig = (config: any) => async (dispatch: any) => {
dispatch(setStatsConfigRequest()); dispatch(setStatsConfigRequest());
try { try {
await apiClient.setStatsConfig(config); await apiClient.setStatsConfig(config);
@ -41,11 +39,12 @@ export const getStatsRequest = createAction('GET_STATS_REQUEST');
export const getStatsFailure = createAction('GET_STATS_FAILURE'); export const getStatsFailure = createAction('GET_STATS_FAILURE');
export const getStatsSuccess = createAction('GET_STATS_SUCCESS'); export const getStatsSuccess = createAction('GET_STATS_SUCCESS');
export const getStats = () => async (dispatch) => { export const getStats = () => async (dispatch: any) => {
dispatch(getStatsRequest()); dispatch(getStatsRequest());
try { try {
const stats = await apiClient.getStats(); const stats = await apiClient.getStats();
const normalizedTopClients = normalizeTopStats(stats.top_clients); const normalizedTopClients = normalizeTopStats(stats.top_clients);
const clientsParams = getParamsForClientsSearch(normalizedTopClients, 'name'); const clientsParams = getParamsForClientsSearch(normalizedTopClients, 'name');
const clients = await apiClient.findClients(clientsParams); const clients = await apiClient.findClients(clientsParams);
const topClientsWithInfo = addClientInfo(normalizedTopClients, clients, 'name'); const topClientsWithInfo = addClientInfo(normalizedTopClients, clients, 'name');
@ -71,7 +70,7 @@ export const resetStatsRequest = createAction('RESET_STATS_REQUEST');
export const resetStatsFailure = createAction('RESET_STATS_FAILURE'); export const resetStatsFailure = createAction('RESET_STATS_FAILURE');
export const resetStatsSuccess = createAction('RESET_STATS_SUCCESS'); export const resetStatsSuccess = createAction('RESET_STATS_SUCCESS');
export const resetStats = () => async (dispatch) => { export const resetStats = () => async (dispatch: any) => {
dispatch(getStatsRequest()); dispatch(getStatsRequest());
try { try {
await apiClient.resetStats(); await apiClient.resetStats();

View File

@ -1,17 +1,16 @@
import axios from 'axios'; import axios from 'axios';
import { getPathWithQueryString } from '../helpers/helpers';
import {
QUERY_LOGS_PAGE_LIMIT, HTML_PAGES, R_PATH_LAST_PART, THEMES,
} from '../helpers/constants';
import { BASE_URL } from '../../constants'; import { BASE_URL } from '../../constants';
import { getPathWithQueryString } from '../helpers/helpers';
import { QUERY_LOGS_PAGE_LIMIT, HTML_PAGES, R_PATH_LAST_PART, THEMES } from '../helpers/constants';
import i18n from '../i18n'; import i18n from '../i18n';
import { LANGUAGES } from '../helpers/twosky'; import { LANGUAGES } from '../helpers/twosky';
class Api { class Api {
baseUrl = BASE_URL; baseUrl = BASE_URL;
async makeRequest(path, method = 'POST', config) { async makeRequest(path: any, method = 'POST', config: any = {}) {
const url = `${this.baseUrl}/${path}`; const url = `${this.baseUrl}/${path}`;
const axiosConfig = config || {}; const axiosConfig = config || {};
@ -29,26 +28,26 @@ class Api {
return response.data; return response.data;
} catch (error) { } catch (error) {
const errorPath = url; const errorPath = url;
if (error.response) { if (error.response) {
const { pathname } = document.location; const { pathname } = document.location;
const shouldRedirect = pathname !== HTML_PAGES.LOGIN const shouldRedirect = pathname !== HTML_PAGES.LOGIN && pathname !== HTML_PAGES.INSTALL;
&& pathname !== HTML_PAGES.INSTALL;
if (error.response.status === 403 && shouldRedirect) { if (error.response.status === 403 && shouldRedirect) {
const loginPageUrl = window.location.href const loginPageUrl = window.location.href.replace(R_PATH_LAST_PART, HTML_PAGES.LOGIN);
.replace(R_PATH_LAST_PART, HTML_PAGES.LOGIN);
window.location.replace(loginPageUrl); window.location.replace(loginPageUrl);
return false; return false;
} }
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}`); 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' };
@ -58,10 +57,11 @@ class Api {
getGlobalStatus() { getGlobalStatus() {
const { path, method } = this.GLOBAL_STATUS; const { path, method } = this.GLOBAL_STATUS;
return this.makeRequest(path, method); return this.makeRequest(path, method);
} }
testUpstream(servers) { testUpstream(servers: any) {
const { path, method } = this.GLOBAL_TEST_UPSTREAM_DNS; const { path, method } = this.GLOBAL_TEST_UPSTREAM_DNS;
const config = { const config = {
data: servers, data: servers,
@ -69,7 +69,7 @@ class Api {
return this.makeRequest(path, method, config); return this.makeRequest(path, method, config);
} }
getGlobalVersion(data) { getGlobalVersion(data: any) {
const { path, method } = this.GLOBAL_VERSION; const { path, method } = this.GLOBAL_VERSION;
const config = { const config = {
data, data,
@ -79,6 +79,7 @@ class Api {
getUpdate() { getUpdate() {
const { path, method } = this.GLOBAL_UPDATE; const { path, method } = this.GLOBAL_UPDATE;
return this.makeRequest(path, method); return this.makeRequest(path, method);
} }
@ -101,10 +102,11 @@ class Api {
getFilteringStatus() { getFilteringStatus() {
const { path, method } = this.FILTERING_STATUS; const { path, method } = this.FILTERING_STATUS;
return this.makeRequest(path, method); return this.makeRequest(path, method);
} }
refreshFilters(config) { refreshFilters(config: any) {
const { path, method } = this.FILTERING_REFRESH; const { path, method } = this.FILTERING_REFRESH;
const parameters = { const parameters = {
data: config, data: config,
@ -113,7 +115,7 @@ class Api {
return this.makeRequest(path, method, parameters); return this.makeRequest(path, method, parameters);
} }
addFilter(config) { addFilter(config: any) {
const { path, method } = this.FILTERING_ADD_FILTER; const { path, method } = this.FILTERING_ADD_FILTER;
const parameters = { const parameters = {
data: config, data: config,
@ -122,7 +124,7 @@ class Api {
return this.makeRequest(path, method, parameters); return this.makeRequest(path, method, parameters);
} }
removeFilter(config) { removeFilter(config: any) {
const { path, method } = this.FILTERING_REMOVE_FILTER; const { path, method } = this.FILTERING_REMOVE_FILTER;
const parameters = { const parameters = {
data: config, data: config,
@ -131,7 +133,7 @@ class Api {
return this.makeRequest(path, method, parameters); return this.makeRequest(path, method, parameters);
} }
setRules(rules) { setRules(rules: any) {
const { path, method } = this.FILTERING_SET_RULES; const { path, method } = this.FILTERING_SET_RULES;
const parameters = { const parameters = {
data: rules, data: rules,
@ -139,7 +141,7 @@ class Api {
return this.makeRequest(path, method, parameters); return this.makeRequest(path, method, parameters);
} }
setFiltersConfig(config) { setFiltersConfig(config: any) {
const { path, method } = this.FILTERING_CONFIG; const { path, method } = this.FILTERING_CONFIG;
const parameters = { const parameters = {
data: config, data: config,
@ -147,7 +149,7 @@ class Api {
return this.makeRequest(path, method, parameters); return this.makeRequest(path, method, parameters);
} }
setFilterUrl(config) { setFilterUrl(config: any) {
const { path, method } = this.FILTERING_SET_URL; const { path, method } = this.FILTERING_SET_URL;
const parameters = { const parameters = {
data: config, data: config,
@ -155,9 +157,10 @@ class Api {
return this.makeRequest(path, method, parameters); return this.makeRequest(path, method, parameters);
} }
checkHost(params) { checkHost(params: any) {
const { path, method } = this.FILTERING_CHECK_HOST; const { path, method } = this.FILTERING_CHECK_HOST;
const url = getPathWithQueryString(path, params); const url = getPathWithQueryString(path, params);
return this.makeRequest(url, method); return this.makeRequest(url, method);
} }
@ -170,16 +173,19 @@ class Api {
getParentalStatus() { getParentalStatus() {
const { path, method } = this.PARENTAL_STATUS; const { path, method } = this.PARENTAL_STATUS;
return this.makeRequest(path, method); return this.makeRequest(path, method);
} }
enableParentalControl() { enableParentalControl() {
const { path, method } = this.PARENTAL_ENABLE; const { path, method } = this.PARENTAL_ENABLE;
return this.makeRequest(path, method); return this.makeRequest(path, method);
} }
disableParentalControl() { disableParentalControl() {
const { path, method } = this.PARENTAL_DISABLE; const { path, method } = this.PARENTAL_DISABLE;
return this.makeRequest(path, method); return this.makeRequest(path, method);
} }
@ -192,16 +198,19 @@ class Api {
getSafebrowsingStatus() { getSafebrowsingStatus() {
const { path, method } = this.SAFEBROWSING_STATUS; const { path, method } = this.SAFEBROWSING_STATUS;
return this.makeRequest(path, method); return this.makeRequest(path, method);
} }
enableSafebrowsing() { enableSafebrowsing() {
const { path, method } = this.SAFEBROWSING_ENABLE; const { path, method } = this.SAFEBROWSING_ENABLE;
return this.makeRequest(path, method); return this.makeRequest(path, method);
} }
disableSafebrowsing() { disableSafebrowsing() {
const { path, method } = this.SAFEBROWSING_DISABLE; const { path, method } = this.SAFEBROWSING_DISABLE;
return this.makeRequest(path, method); return this.makeRequest(path, method);
} }
@ -212,6 +221,7 @@ class Api {
getSafesearchStatus() { getSafesearchStatus() {
const { path, method } = this.SAFESEARCH_STATUS; const { path, method } = this.SAFESEARCH_STATUS;
return this.makeRequest(path, method); return this.makeRequest(path, method);
} }
@ -228,7 +238,7 @@ class Api {
* @param {*} data - SafeSearchConfig * @param {*} data - SafeSearchConfig
* @returns 200 ok * @returns 200 ok
*/ */
updateSafesearch(data) { updateSafesearch(data: any) {
const { path, method } = this.SAFESEARCH_UPDATE; const { path, method } = this.SAFESEARCH_UPDATE;
return this.makeRequest(path, method, { data }); return this.makeRequest(path, method, { data });
} }
@ -245,7 +255,7 @@ class Api {
// Language // Language
async changeLanguage(config) { async changeLanguage(config: any) {
const profile = await this.getProfile(); const profile = await this.getProfile();
profile.language = config.language; profile.language = config.language;
@ -254,7 +264,7 @@ class Api {
// Theme // Theme
async changeTheme(config) { async changeTheme(config: any) {
const profile = await this.getProfile(); const profile = await this.getProfile();
profile.theme = config.theme; profile.theme = config.theme;
@ -282,15 +292,17 @@ class Api {
getDhcpStatus() { getDhcpStatus() {
const { path, method } = this.DHCP_STATUS; const { path, method } = this.DHCP_STATUS;
return this.makeRequest(path, method); return this.makeRequest(path, method);
} }
getDhcpInterfaces() { getDhcpInterfaces() {
const { path, method } = this.DHCP_INTERFACES; const { path, method } = this.DHCP_INTERFACES;
return this.makeRequest(path, method); return this.makeRequest(path, method);
} }
setDhcpConfig(config) { setDhcpConfig(config: any) {
const { path, method } = this.DHCP_SET_CONFIG; const { path, method } = this.DHCP_SET_CONFIG;
const parameters = { const parameters = {
data: config, data: config,
@ -298,7 +310,7 @@ class Api {
return this.makeRequest(path, method, parameters); return this.makeRequest(path, method, parameters);
} }
findActiveDhcp(req) { findActiveDhcp(req: any) {
const { path, method } = this.DHCP_FIND_ACTIVE; const { path, method } = this.DHCP_FIND_ACTIVE;
const parameters = { const parameters = {
data: req, data: req,
@ -306,7 +318,7 @@ class Api {
return this.makeRequest(path, method, parameters); return this.makeRequest(path, method, parameters);
} }
addStaticLease(config) { addStaticLease(config: any) {
const { path, method } = this.DHCP_ADD_STATIC_LEASE; const { path, method } = this.DHCP_ADD_STATIC_LEASE;
const parameters = { const parameters = {
data: config, data: config,
@ -314,7 +326,7 @@ class Api {
return this.makeRequest(path, method, parameters); return this.makeRequest(path, method, parameters);
} }
removeStaticLease(config) { removeStaticLease(config: any) {
const { path, method } = this.DHCP_REMOVE_STATIC_LEASE; const { path, method } = this.DHCP_REMOVE_STATIC_LEASE;
const parameters = { const parameters = {
data: config, data: config,
@ -322,7 +334,7 @@ class Api {
return this.makeRequest(path, method, parameters); return this.makeRequest(path, method, parameters);
} }
updateStaticLease(config) { updateStaticLease(config: any) {
const { path, method } = this.DHCP_UPDATE_STATIC_LEASE; const { path, method } = this.DHCP_UPDATE_STATIC_LEASE;
const parameters = { const parameters = {
data: config, data: config,
@ -332,11 +344,13 @@ class Api {
resetDhcp() { resetDhcp() {
const { path, method } = this.DHCP_RESET; const { path, method } = this.DHCP_RESET;
return this.makeRequest(path, method); return this.makeRequest(path, method);
} }
resetDhcpLeases() { resetDhcpLeases() {
const { path, method } = this.DHCP_LEASES_RESET; const { path, method } = this.DHCP_LEASES_RESET;
return this.makeRequest(path, method); return this.makeRequest(path, method);
} }
@ -349,10 +363,11 @@ class Api {
getDefaultAddresses() { getDefaultAddresses() {
const { path, method } = this.INSTALL_GET_ADDRESSES; const { path, method } = this.INSTALL_GET_ADDRESSES;
return this.makeRequest(path, method); return this.makeRequest(path, method);
} }
setAllSettings(config) { setAllSettings(config: any) {
const { path, method } = this.INSTALL_CONFIGURE; const { path, method } = this.INSTALL_CONFIGURE;
const parameters = { const parameters = {
data: config, data: config,
@ -360,7 +375,7 @@ class Api {
return this.makeRequest(path, method, parameters); return this.makeRequest(path, method, parameters);
} }
checkConfig(config) { checkConfig(config: any) {
const { path, method } = this.INSTALL_CHECK_CONFIG; const { path, method } = this.INSTALL_CHECK_CONFIG;
const parameters = { const parameters = {
data: config, data: config,
@ -377,10 +392,11 @@ class Api {
getTlsStatus() { getTlsStatus() {
const { path, method } = this.TLS_STATUS; const { path, method } = this.TLS_STATUS;
return this.makeRequest(path, method); return this.makeRequest(path, method);
} }
setTlsConfig(config) { setTlsConfig(config: any) {
const { path, method } = this.TLS_CONFIG; const { path, method } = this.TLS_CONFIG;
const parameters = { const parameters = {
data: config, data: config,
@ -388,7 +404,7 @@ class Api {
return this.makeRequest(path, method, parameters); return this.makeRequest(path, method, parameters);
} }
validateTlsConfig(config) { validateTlsConfig(config: any) {
const { path, method } = this.TLS_VALIDATE; const { path, method } = this.TLS_VALIDATE;
const parameters = { const parameters = {
data: config, data: config,
@ -409,10 +425,11 @@ class Api {
getClients() { getClients() {
const { path, method } = this.GET_CLIENTS; const { path, method } = this.GET_CLIENTS;
return this.makeRequest(path, method); return this.makeRequest(path, method);
} }
addClient(config) { addClient(config: any) {
const { path, method } = this.ADD_CLIENT; const { path, method } = this.ADD_CLIENT;
const parameters = { const parameters = {
data: config, data: config,
@ -420,7 +437,7 @@ class Api {
return this.makeRequest(path, method, parameters); return this.makeRequest(path, method, parameters);
} }
deleteClient(config) { deleteClient(config: any) {
const { path, method } = this.DELETE_CLIENT; const { path, method } = this.DELETE_CLIENT;
const parameters = { const parameters = {
data: config, data: config,
@ -428,7 +445,7 @@ class Api {
return this.makeRequest(path, method, parameters); return this.makeRequest(path, method, parameters);
} }
updateClient(config) { updateClient(config: any) {
const { path, method } = this.UPDATE_CLIENT; const { path, method } = this.UPDATE_CLIENT;
const parameters = { const parameters = {
data: config, data: config,
@ -436,9 +453,10 @@ class Api {
return this.makeRequest(path, method, parameters); return this.makeRequest(path, method, parameters);
} }
findClients(params) { findClients(params: any) {
const { path, method } = this.FIND_CLIENTS; const { path, method } = this.FIND_CLIENTS;
const url = getPathWithQueryString(path, params); const url = getPathWithQueryString(path, params);
return this.makeRequest(url, method); return this.makeRequest(url, method);
} }
@ -449,10 +467,11 @@ class Api {
getAccessList() { getAccessList() {
const { path, method } = this.ACCESS_LIST; const { path, method } = this.ACCESS_LIST;
return this.makeRequest(path, method); return this.makeRequest(path, method);
} }
setAccessList(config) { setAccessList(config: any) {
const { path, method } = this.ACCESS_SET; const { path, method } = this.ACCESS_SET;
const parameters = { const parameters = {
data: config, data: config,
@ -471,10 +490,11 @@ class Api {
getRewritesList() { getRewritesList() {
const { path, method } = this.REWRITES_LIST; const { path, method } = this.REWRITES_LIST;
return this.makeRequest(path, method); return this.makeRequest(path, method);
} }
addRewrite(config) { addRewrite(config: any) {
const { path, method } = this.REWRITE_ADD; const { path, method } = this.REWRITE_ADD;
const parameters = { const parameters = {
data: config, data: config,
@ -482,7 +502,7 @@ class Api {
return this.makeRequest(path, method, parameters); return this.makeRequest(path, method, parameters);
} }
updateRewrite(config) { updateRewrite(config: any) {
const { path, method } = this.REWRITE_UPDATE; const { path, method } = this.REWRITE_UPDATE;
const parameters = { const parameters = {
data: config, data: config,
@ -490,7 +510,7 @@ class Api {
return this.makeRequest(path, method, parameters); return this.makeRequest(path, method, parameters);
} }
deleteRewrite(config) { deleteRewrite(config: any) {
const { path, method } = this.REWRITE_DELETE; const { path, method } = this.REWRITE_DELETE;
const parameters = { const parameters = {
data: config, data: config,
@ -507,15 +527,17 @@ class Api {
getAllBlockedServices() { getAllBlockedServices() {
const { path, method } = this.BLOCKED_SERVICES_ALL; const { path, method } = this.BLOCKED_SERVICES_ALL;
return this.makeRequest(path, method); return this.makeRequest(path, method);
} }
getBlockedServices() { getBlockedServices() {
const { path, method } = this.BLOCKED_SERVICES_GET; const { path, method } = this.BLOCKED_SERVICES_GET;
return this.makeRequest(path, method); return this.makeRequest(path, method);
} }
updateBlockedServices(config) { updateBlockedServices(config: any) {
const { path, method } = this.BLOCKED_SERVICES_UPDATE; const { path, method } = this.BLOCKED_SERVICES_UPDATE;
const parameters = { const parameters = {
data: config, data: config,
@ -534,15 +556,17 @@ class Api {
getStats() { getStats() {
const { path, method } = this.GET_STATS; const { path, method } = this.GET_STATS;
return this.makeRequest(path, method); return this.makeRequest(path, method);
} }
getStatsConfig() { getStatsConfig() {
const { path, method } = this.GET_STATS_CONFIG; const { path, method } = this.GET_STATS_CONFIG;
return this.makeRequest(path, method); return this.makeRequest(path, method);
} }
setStatsConfig(data) { setStatsConfig(data: any) {
const { path, method } = this.UPDATE_STATS_CONFIG; const { path, method } = this.UPDATE_STATS_CONFIG;
const config = { const config = {
data, data,
@ -552,6 +576,7 @@ class Api {
resetStats() { resetStats() {
const { path, method } = this.STATS_RESET; const { path, method } = this.STATS_RESET;
return this.makeRequest(path, method); return this.makeRequest(path, method);
} }
@ -564,20 +589,22 @@ class Api {
QUERY_LOG_CLEAR = { path: 'querylog_clear', method: 'POST' }; QUERY_LOG_CLEAR = { path: 'querylog_clear', method: 'POST' };
getQueryLog(params) { getQueryLog(params: any) {
const { path, method } = this.GET_QUERY_LOG; const { path, method } = this.GET_QUERY_LOG;
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
params.limit = QUERY_LOGS_PAGE_LIMIT; params.limit = QUERY_LOGS_PAGE_LIMIT;
const url = getPathWithQueryString(path, params); const url = getPathWithQueryString(path, params);
return this.makeRequest(url, method); return this.makeRequest(url, method);
} }
getQueryLogConfig() { getQueryLogConfig() {
const { path, method } = this.GET_QUERY_LOG_CONFIG; const { path, method } = this.GET_QUERY_LOG_CONFIG;
return this.makeRequest(path, method); return this.makeRequest(path, method);
} }
setQueryLogConfig(data) { setQueryLogConfig(data: any) {
const { path, method } = this.UPDATE_QUERY_LOG_CONFIG; const { path, method } = this.UPDATE_QUERY_LOG_CONFIG;
const config = { const config = {
data, data,
@ -587,13 +614,14 @@ class Api {
clearQueryLog() { clearQueryLog() {
const { path, method } = this.QUERY_LOG_CLEAR; const { path, method } = this.QUERY_LOG_CLEAR;
return this.makeRequest(path, method); return this.makeRequest(path, method);
} }
// Login // Login
LOGIN = { path: 'login', method: 'POST' }; LOGIN = { path: 'login', method: 'POST' };
login(data) { login(data: any) {
const { path, method } = this.LOGIN; const { path, method } = this.LOGIN;
const config = { const config = {
data, data,
@ -608,10 +636,11 @@ class Api {
getProfile() { getProfile() {
const { path, method } = this.GET_PROFILE; const { path, method } = this.GET_PROFILE;
return this.makeRequest(path, method); return this.makeRequest(path, method);
} }
setProfile(data) { setProfile(data: any) {
const theme = data.theme ? data.theme : THEMES.auto; const theme = data.theme ? data.theme : THEMES.auto;
const defaultLanguage = i18n.language ? i18n.language : LANGUAGES.en; const defaultLanguage = i18n.language ? i18n.language : LANGUAGES.en;
const language = data.language ? data.language : defaultLanguage; const language = data.language ? data.language : defaultLanguage;
@ -629,10 +658,11 @@ class Api {
getDnsConfig() { getDnsConfig() {
const { path, method } = this.GET_DNS_CONFIG; const { path, method } = this.GET_DNS_CONFIG;
return this.makeRequest(path, method); return this.makeRequest(path, method);
} }
setDnsConfig(data) { setDnsConfig(data: any) {
const { path, method } = this.SET_DNS_CONFIG; const { path, method } = this.SET_DNS_CONFIG;
const config = { const config = {
data, data,
@ -642,7 +672,7 @@ class Api {
SET_PROTECTION = { path: 'protection', method: 'POST' }; SET_PROTECTION = { path: 'protection', method: 'POST' };
setProtection(data) { setProtection(data: any) {
const { enabled, duration } = data; const { enabled, duration } = data;
const { path, method } = this.SET_PROTECTION; const { path, method } = this.SET_PROTECTION;
@ -654,6 +684,7 @@ class Api {
clearCache() { clearCache() {
const { path, method } = this.CLEAR_CACHE; const { path, method } = this.CLEAR_CACHE;
return this.makeRequest(path, method); return this.makeRequest(path, method);
} }
} }

View File

@ -15,8 +15,8 @@
--btn-success-bgcolor: #5eba00; --btn-success-bgcolor: #5eba00;
--form-disabled-bgcolor: #f8f9fa; --form-disabled-bgcolor: #f8f9fa;
--form-disabled-color: #495057; --form-disabled-color: #495057;
--rt-nodata-bgcolor: rgba(255,255,255,0.8); --rt-nodata-bgcolor: rgba(255, 255, 255, 0.8);
--rt-nodata-color: rgba(0,0,0,0.5); --rt-nodata-color: rgba(0, 0, 0, 0.5);
--modal-overlay-bgcolor: rgba(255, 255, 255, 0.75); --modal-overlay-bgcolor: rgba(255, 255, 255, 0.75);
--logs__table-bgcolor: #fff; --logs__table-bgcolor: #fff;
--logs__row--blue-bgcolor: #e5effd; --logs__row--blue-bgcolor: #e5effd;
@ -28,7 +28,7 @@
--gray-d8: #d8d8d8; --gray-d8: #d8d8d8;
--gray-f3: #f3f3f3; --gray-f3: #f3f3f3;
--loading-bg: rgba(255, 255, 255, 0.48); --loading-bg: rgba(255, 255, 255, 0.48);
--font-family-monospace: Monaco, Menlo, "Ubuntu Mono", Consolas, source-code-pro, monospace; --font-family-monospace: Monaco, Menlo, 'Ubuntu Mono', Consolas, source-code-pro, monospace;
--font-size-disable-autozoom: 1rem; --font-size-disable-autozoom: 1rem;
--alert-message-color: #24426c; --alert-message-color: #24426c;
--alert-message-border: #cbdbf2; --alert-message-border: #cbdbf2;
@ -37,7 +37,7 @@
--radio-bg: #ffffff; --radio-bg: #ffffff;
} }
[data-theme="dark"] { [data-theme='dark'] {
--black: #ffffff; --black: #ffffff;
--bgcolor: #131313; --bgcolor: #131313;
--mcolor: #e6e6e6; --mcolor: #e6e6e6;
@ -74,12 +74,14 @@
body { body {
margin: 0; margin: 0;
padding: 0; padding: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Arial, sans-serif; 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 */ /* Disable Auto Zoom in Input - Safari on iPhone https://stackoverflow.com/a/6394497 */
@media screen and (max-width: 767px) { @media screen and (max-width: 767px) {
input, select, textarea { input,
select,
textarea {
font-size: var(--font-size-disable-autozoom); font-size: var(--font-size-disable-autozoom);
} }
} }

View File

@ -1,4 +1,5 @@
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import { HashRouter, Route } from 'react-router-dom'; import { HashRouter, Route } from 'react-router-dom';
import LoadingBar from 'react-redux-loading-bar'; import LoadingBar from 'react-redux-loading-bar';
import { hot } from 'react-hot-loader/root'; import { hot } from 'react-hot-loader/root';
@ -9,8 +10,6 @@ import '../ui/ReactTable.css';
import './index.css'; import './index.css';
import { shallowEqual, useDispatch, useSelector } from 'react-redux'; import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import propTypes from 'prop-types';
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';
@ -19,15 +18,14 @@ import UpdateOverlay from '../ui/UpdateOverlay';
import EncryptionTopline from '../ui/EncryptionTopline'; import EncryptionTopline from '../ui/EncryptionTopline';
import Icons from '../ui/Icons'; import Icons from '../ui/Icons';
import i18n from '../../i18n'; import i18n from '../../i18n';
import Loading from '../ui/Loading'; import Loading from '../ui/Loading';
import { import { FILTERS_URLS, MENU_URLS, SETTINGS_URLS, THEMES } from '../../helpers/constants';
FILTERS_URLS,
MENU_URLS,
SETTINGS_URLS,
THEMES,
} from '../../helpers/constants';
import { getLogsUrlParams, setHtmlLangAttr, setUITheme } from '../../helpers/helpers'; import { getLogsUrlParams, setHtmlLangAttr, setUITheme } from '../../helpers/helpers';
import Header from '../Header'; import Header from '../Header';
import { changeLanguage, getDnsStatus, getTimerStatus } from '../../actions'; import { changeLanguage, getDnsStatus, getTimerStatus } from '../../actions';
import Dashboard from '../../containers/Dashboard'; import Dashboard from '../../containers/Dashboard';
@ -35,15 +33,19 @@ import SetupGuide from '../../containers/SetupGuide';
import Settings from '../../containers/Settings'; import Settings from '../../containers/Settings';
import Dns from '../../containers/Dns'; import Dns from '../../containers/Dns';
import Encryption from '../../containers/Encryption'; import Encryption from '../../containers/Encryption';
import Dhcp from '../Settings/Dhcp'; import Dhcp from '../Settings/Dhcp';
import Clients from '../../containers/Clients'; import Clients from '../../containers/Clients';
import DnsBlocklist from '../../containers/DnsBlocklist'; import DnsBlocklist from '../../containers/DnsBlocklist';
import DnsAllowlist from '../../containers/DnsAllowlist'; import DnsAllowlist from '../../containers/DnsAllowlist';
import DnsRewrites from '../../containers/DnsRewrites'; import DnsRewrites from '../../containers/DnsRewrites';
import CustomRules from '../../containers/CustomRules'; import CustomRules from '../../containers/CustomRules';
import Services from '../Filters/Services'; import Services from '../Filters/Services';
import Logs from '../Logs'; import Logs from '../Logs';
import ProtectionTimer from '../ProtectionTimer'; import ProtectionTimer from '../ProtectionTimer';
import { RootState } from '../../initialState';
const ROUTES = [ const ROUTES = [
{ {
@ -101,26 +103,17 @@ const ROUTES = [
}, },
]; ];
const renderRoute = ({ path, component, exact }, idx) => <Route
key={idx}
exact={exact}
path={path}
component={component}
/>;
const App = () => { const App = () => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const { const { language, isCoreRunning, isUpdateAvailable, processing, theme } = useSelector<
language, RootState,
isCoreRunning, RootState['dashboard']
isUpdateAvailable, >((state) => state.dashboard, shallowEqual);
processing,
theme,
} = useSelector((state) => state.dashboard, shallowEqual);
const { processing: processingEncryption } = useSelector(( const { processing: processingEncryption } = useSelector<RootState, RootState['encryption']>(
state, (state) => state.encryption,
) => state.encryption, shallowEqual); shallowEqual,
);
const updateAvailable = isCoreRunning && isUpdateAvailable; const updateAvailable = isCoreRunning && isUpdateAvailable;
@ -157,7 +150,7 @@ const App = () => {
setLanguage(); setLanguage();
}, [language]); }, [language]);
const handleAutoTheme = (e, accountTheme) => { const handleAutoTheme = (e: any, accountTheme: any) => {
if (accountTheme !== THEMES.auto) { if (accountTheme !== THEMES.auto) {
return; return;
} }
@ -195,35 +188,50 @@ const App = () => {
window.location.reload(); window.location.reload();
}; };
return <HashRouter hashType="noslash"> return (
{updateAvailable && <> <HashRouter hashType="noslash">
{updateAvailable && (
<>
<UpdateTopline /> <UpdateTopline />
<UpdateOverlay /> <UpdateOverlay />
</>} </>
)}
{!processingEncryption && <EncryptionTopline />} {!processingEncryption && <EncryptionTopline />}
<LoadingBar className="loading-bar" updateTime={1000} /> <LoadingBar className="loading-bar" updateTime={1000} />
<Header /> <Header />
<ProtectionTimer /> <ProtectionTimer />
<div className="container container--wrap pb-5 pt-5"> <div className="container container--wrap pb-5 pt-5">
{processing && <Loading />} {processing && <Loading />}
{!isCoreRunning && <div className="row row-cards">
{!isCoreRunning && (
<div className="row row-cards">
<div className="col-lg-12"> <div className="col-lg-12">
<Status reloadPage={reloadPage} message="dns_start" /> <Status reloadPage={reloadPage} message="dns_start" />
<Loading /> <Loading />
</div> </div>
</div>}
{!processing && isCoreRunning && ROUTES.map(renderRoute)}
</div> </div>
<Footer /> )}
<Toasts /> {!processing &&
<Icons /> isCoreRunning &&
</HashRouter>; ROUTES.map((route, index) => (
}; <Route key={index} exact={route.exact} path={route.path} component={route.component} />
))}
</div>
renderRoute.propTypes = { <Footer />
path: propTypes.oneOfType([propTypes.string, propTypes.arrayOf(propTypes.string)]).isRequired,
component: propTypes.element.isRequired, <Toasts />
exact: propTypes.bool,
<Icons />
</HashRouter>
);
}; };
export default hot(App); export default hot(App);

View File

@ -1,25 +1,37 @@
import React from 'react'; import React from 'react';
// @ts-expect-error FIXME: update react-table
import ReactTable from 'react-table'; import ReactTable from 'react-table';
import PropTypes from 'prop-types';
import { withTranslation, Trans } from 'react-i18next'; import { withTranslation, Trans } from 'react-i18next';
import { TFunction } from 'i18next';
import Card from '../ui/Card'; import Card from '../ui/Card';
import Cell from '../ui/Cell'; import Cell from '../ui/Cell';
import DomainCell from './DomainCell'; import DomainCell from './DomainCell';
import { getPercent } from '../../helpers/helpers'; import { getPercent } from '../../helpers/helpers';
import { DASHBOARD_TABLES_DEFAULT_PAGE_SIZE, STATUS_COLORS, TABLES_MIN_ROWS } from '../../helpers/constants'; import { DASHBOARD_TABLES_DEFAULT_PAGE_SIZE, STATUS_COLORS, TABLES_MIN_ROWS } from '../../helpers/constants';
const CountCell = (totalBlocked) => function cell(row) { const CountCell = (totalBlocked: any) =>
function cell(row: any) {
const { value } = row; const { value } = row;
const percent = getPercent(totalBlocked, value); const percent = getPercent(totalBlocked, value);
return <Cell value={value} return <Cell value={value} percent={percent} color={STATUS_COLORS.red} search={row.original.domain} />;
percent={percent} };
color={STATUS_COLORS.red}
search={row.original.domain} interface BlockedDomainsProps {
/>; topBlockedDomains: unknown[];
}; blockedFiltering: number;
replacedSafebrowsing: number;
replacedSafesearch: number;
replacedParental: number;
refreshButton: React.ReactNode;
subtitle: string;
t: TFunction;
}
const BlockedDomains = ({ const BlockedDomains = ({
t, t,
@ -30,20 +42,13 @@ const BlockedDomains = ({
replacedSafebrowsing, replacedSafebrowsing,
replacedParental, replacedParental,
replacedSafesearch, replacedSafesearch,
}) => { }: BlockedDomainsProps) => {
const totalBlocked = ( const totalBlocked = blockedFiltering + replacedSafebrowsing + replacedParental + replacedSafesearch;
blockedFiltering + replacedSafebrowsing + replacedParental + replacedSafesearch
);
return ( return (
<Card <Card title={t('top_blocked_domains')} subtitle={subtitle} bodyType="card-table" refresh={refreshButton}>
title={t('top_blocked_domains')}
subtitle={subtitle}
bodyType="card-table"
refresh={refreshButton}
>
<ReactTable <ReactTable
data={topBlockedDomains.map(({ name: domain, count }) => ({ data={topBlockedDomains.map(({ name: domain, count }: any) => ({
domain, domain,
count, count,
}))} }))}
@ -70,15 +75,4 @@ const BlockedDomains = ({
); );
}; };
BlockedDomains.propTypes = {
topBlockedDomains: PropTypes.array.isRequired,
blockedFiltering: PropTypes.number.isRequired,
replacedSafebrowsing: PropTypes.number.isRequired,
replacedSafesearch: PropTypes.number.isRequired,
replacedParental: PropTypes.number.isRequired,
refreshButton: PropTypes.node.isRequired,
subtitle: PropTypes.string.isRequired,
t: PropTypes.func.isRequired,
};
export default withTranslation()(BlockedDomains); export default withTranslation()(BlockedDomains);

View File

@ -1,10 +1,12 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
// @ts-expect-error FIXME: update react-table
import ReactTable from 'react-table'; import ReactTable from 'react-table';
import PropTypes from 'prop-types';
import { Trans, useTranslation } from 'react-i18next'; import { Trans, useTranslation } from 'react-i18next';
import { shallowEqual, useDispatch, useSelector } from 'react-redux'; import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import classNames from 'classnames'; import classNames from 'classnames';
import Card from '../ui/Card'; import Card from '../ui/Card';
import Cell from '../ui/Cell'; import Cell from '../ui/Cell';
@ -16,11 +18,14 @@ import {
TABLES_MIN_ROWS, TABLES_MIN_ROWS,
} from '../../helpers/constants'; } from '../../helpers/constants';
import { toggleClientBlock } from '../../actions/access'; import { toggleClientBlock } from '../../actions/access';
import { renderFormattedClientCell } from '../../helpers/renderFormattedClientCell'; import { renderFormattedClientCell } from '../../helpers/renderFormattedClientCell';
import { getStats } from '../../actions/stats'; import { getStats } from '../../actions/stats';
import IconTooltip from '../Logs/Cells/IconTooltip';
const getClientsPercentColor = (percent) => { import IconTooltip from '../Logs/Cells/IconTooltip';
import { RootState } from '../../initialState';
const getClientsPercentColor = (percent: any) => {
if (percent > 50) { if (percent > 50) {
return STATUS_COLORS.green; return STATUS_COLORS.green;
} }
@ -30,9 +35,13 @@ const getClientsPercentColor = (percent) => {
return STATUS_COLORS.red; return STATUS_COLORS.red;
}; };
const CountCell = (row) => { const CountCell = (row: any) => {
const { value, original: { ip } } = row; const {
const numDnsQueries = useSelector((state) => state.stats.numDnsQueries, shallowEqual); value,
original: { ip },
} = row;
const numDnsQueries = useSelector<RootState>((state) => state.stats.numDnsQueries, shallowEqual);
const percent = getPercent(numDnsQueries, value); const percent = getPercent(numDnsQueries, value);
const percentColor = getClientsPercentColor(percent); const percentColor = getClientsPercentColor(percent);
@ -40,22 +49,29 @@ const CountCell = (row) => {
return <Cell value={value} percent={percent} color={percentColor} search={ip} />; return <Cell value={value} percent={percent} color={percentColor} search={ip} />;
}; };
const renderBlockingButton = (ip, disallowed, disallowed_rule) => { const renderBlockingButton = (ip: any, disallowed: any, disallowed_rule: any) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const { t } = useTranslation(); const { t } = useTranslation();
const processingSet = useSelector((state) => state.access.processingSet);
const allowedСlients = useSelector((state) => state.access.allowed_clients, shallowEqual); const processingSet = useSelector<RootState, RootState['access']['processingSet']>(
(state) => state.access.processingSet,
);
const allowedClients = useSelector<RootState, RootState['access']['allowed_clients']>(
(state) => state.access.allowed_clients,
shallowEqual,
);
const [isOptionsOpened, setOptionsOpened] = useState(false); const [isOptionsOpened, setOptionsOpened] = useState(false);
const toggleClientStatus = async (ip, disallowed, disallowed_rule) => { const toggleClientStatus = async (ip: any, disallowed: any, disallowed_rule: any) => {
let confirmMessage; let confirmMessage;
if (disallowed) { if (disallowed) {
confirmMessage = t('client_confirm_unblock', { ip: disallowed_rule || ip }); confirmMessage = t('client_confirm_unblock', { ip: disallowed_rule || ip });
} else { } else {
confirmMessage = `${t('adg_will_drop_dns_queries')} ${t('client_confirm_block', { ip })}`; confirmMessage = `${t('adg_will_drop_dns_queries')} ${t('client_confirm_block', { ip })}`;
if (allowedСlients.length > 0) { if (allowedClients.length > 0) {
confirmMessage = confirmMessage.concat(`\n\n${t('filter_allowlist', { disallowed_rule })}`); confirmMessage = confirmMessage.concat(`\n\n${t('filter_allowlist', { disallowed_rule })}`);
} }
} }
@ -73,15 +89,11 @@ const renderBlockingButton = (ip, disallowed, disallowed_rule) => {
const text = disallowed ? BLOCK_ACTIONS.UNBLOCK : BLOCK_ACTIONS.BLOCK; const text = disallowed ? BLOCK_ACTIONS.UNBLOCK : BLOCK_ACTIONS.BLOCK;
const lastRuleInAllowlist = !disallowed && allowedСlients === disallowed_rule; const lastRuleInAllowlist = !disallowed && allowedClients === disallowed_rule;
const disabled = processingSet || lastRuleInAllowlist; const disabled = processingSet || lastRuleInAllowlist;
return ( return (
<div className="table__action"> <div className="table__action">
<button <button type="button" className="btn btn-icon btn-sm px-0" onClick={() => setOptionsOpened(true)}>
type="button"
className="btn btn-icon btn-sm px-0"
onClick={() => setOptionsOpened(true)}
>
<svg className="icon24 icon--lightgray button-action__icon"> <svg className="icon24 icon--lightgray button-action__icon">
<use xlinkHref="#bullets" /> <use xlinkHref="#bullets" />
</svg> </svg>
@ -92,16 +104,18 @@ const renderBlockingButton = (ip, disallowed, disallowed_rule) => {
tooltipClass="button-action--arrow-option-container" tooltipClass="button-action--arrow-option-container"
xlinkHref="bullets" xlinkHref="bullets"
triggerClass="btn btn-icon btn-sm px-0 button-action__hidden-trigger" triggerClass="btn btn-icon btn-sm px-0 button-action__hidden-trigger"
content={( content={
<button <button
className={classNames('button-action--arrow-option px-4 py-1', disallowed ? 'bg--green' : 'bg--danger')} className={classNames(
'button-action--arrow-option px-4 py-1',
disallowed ? 'bg--green' : 'bg--danger',
)}
onClick={onClick} onClick={onClick}
disabled={disabled} disabled={disabled}
title={lastRuleInAllowlist ? t('last_rule_in_allowlist', { disallowed_rule }) : ''} title={lastRuleInAllowlist ? t('last_rule_in_allowlist', { disallowed_rule }) : ''}>
>
<Trans>{text}</Trans> <Trans>{text}</Trans>
</button> </button>
)} }
placement="bottom-end" placement="bottom-end"
trigger="click" trigger="click"
onVisibilityChange={setOptionsOpened} onVisibilityChange={setOptionsOpened}
@ -113,35 +127,42 @@ const renderBlockingButton = (ip, disallowed, disallowed_rule) => {
); );
}; };
const ClientCell = (row) => { const ClientCell = (row: any) => {
const { value, original: { info, info: { disallowed, disallowed_rule } } } = row; const {
value,
original: {
info,
info: { disallowed, disallowed_rule },
},
} = row;
return <> return (
<>
<div className="logs__row logs__row--overflow logs__row--column d-flex align-items-center"> <div className="logs__row logs__row--overflow logs__row--column d-flex align-items-center">
{renderFormattedClientCell(value, info, true)} {renderFormattedClientCell(value, info, true)}
{renderBlockingButton(value, disallowed, disallowed_rule)} {renderBlockingButton(value, disallowed, disallowed_rule)}
</div> </div>
</>; </>
);
}; };
const Clients = ({ interface ClientsProps {
refreshButton, refreshButton: React.ReactNode;
subtitle, subtitle: string;
}) => { }
const Clients = ({ refreshButton, subtitle }: ClientsProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const topClients = useSelector((state) => state.stats.topClients, shallowEqual);
const topClients = useSelector<RootState, RootState['stats']['topClients']>(
(state) => state.stats.topClients,
shallowEqual,
);
return ( return (
<Card <Card title={t('top_clients')} subtitle={subtitle} bodyType="card-table" refresh={refreshButton}>
title={t('top_clients')}
subtitle={subtitle}
bodyType="card-table"
refresh={refreshButton}
>
<ReactTable <ReactTable
data={topClients.map(({ data={topClients.map(({ name: ip, count, info, blocked }: any) => ({
name: ip, count, info, blocked,
}) => ({
ip, ip,
count, count,
info, info,
@ -167,12 +188,14 @@ const Clients = ({
minRows={TABLES_MIN_ROWS} minRows={TABLES_MIN_ROWS}
defaultPageSize={DASHBOARD_TABLES_DEFAULT_PAGE_SIZE} defaultPageSize={DASHBOARD_TABLES_DEFAULT_PAGE_SIZE}
className="-highlight card-table-overflow--limited clients__table" className="-highlight card-table-overflow--limited clients__table"
getTrProps={(_state, rowInfo) => { getTrProps={(_state: any, rowInfo: any) => {
if (!rowInfo) { if (!rowInfo) {
return {}; return {};
} }
const { info: { disallowed } } = rowInfo.original; const {
info: { disallowed },
} = rowInfo.original;
return disallowed ? { className: 'logs__row--red' } : {}; return disallowed ? { className: 'logs__row--red' } : {};
}} }}
@ -181,9 +204,4 @@ const Clients = ({
); );
}; };
Clients.propTypes = {
refreshButton: PropTypes.node.isRequired,
subtitle: PropTypes.string.isRequired,
};
export default Clients; export default Clients;

View File

@ -1,41 +1,52 @@
import React from 'react'; import React from 'react';
import propTypes from 'prop-types';
import { Trans, useTranslation } from 'react-i18next'; import { Trans, useTranslation } from 'react-i18next';
import round from 'lodash/round'; import round from 'lodash/round';
import { shallowEqual, useSelector } from 'react-redux'; import { shallowEqual, useSelector } from 'react-redux';
import Card from '../ui/Card'; import Card from '../ui/Card';
import { formatNumber, msToDays, msToHours } from '../../helpers/helpers'; import { formatNumber, msToDays, msToHours } from '../../helpers/helpers';
import LogsSearchLink from '../ui/LogsSearchLink'; import LogsSearchLink from '../ui/LogsSearchLink';
import { RESPONSE_FILTER, TIME_UNITS } from '../../helpers/constants'; import { RESPONSE_FILTER, TIME_UNITS } from '../../helpers/constants';
import Tooltip from '../ui/Tooltip';
const Row = ({ import Tooltip from '../ui/Tooltip';
label, count, response_status, tooltipTitle, translationComponents, import { RootState } from '../../initialState';
}) => {
const content = response_status interface RowProps {
? <LogsSearchLink response_status={response_status}>{formatNumber(count)}</LogsSearchLink> label: string;
: count; count: string;
response_status?: string;
tooltipTitle: string;
translationComponents?: React.ReactElement[];
}
const Row = ({ label, count, response_status, tooltipTitle, translationComponents }: RowProps) => {
const content = response_status ? (
<LogsSearchLink response_status={response_status}>{formatNumber(count)}</LogsSearchLink>
) : (
count
);
return ( return (
<div className="counters__row" key={label}> <div className="counters__row" key={label}>
<div className="counters__column"> <div className="counters__column">
<span className="counters__title"> <span className="counters__title">
<Trans components={translationComponents}> <Trans components={translationComponents}>{label}</Trans>
{label}
</Trans>
</span> </span>
<span className="counters__tooltip"> <span className="counters__tooltip">
<Tooltip <Tooltip
content={tooltipTitle} content={tooltipTitle}
placement="top" placement="top"
className="tooltip-container tooltip-custom--narrow text-center" className="tooltip-container tooltip-custom--narrow text-center">
>
<svg className="icons icon--20 icon--lightgray ml-2"> <svg className="icons icon--20 icon--lightgray ml-2">
<use xlinkHref="#question" /> <use xlinkHref="#question" />
</svg> </svg>
</Tooltip> </Tooltip>
</span> </span>
</div> </div>
<div className="counters__column counters__column--value"> <div className="counters__column counters__column--value">
<strong>{content}</strong> <strong>{content}</strong>
</div> </div>
@ -43,7 +54,12 @@ const Row = ({
); );
}; };
const Counters = ({ refreshButton, subtitle }) => { interface CountersProps {
refreshButton: React.ReactNode;
subtitle: string;
}
const Counters = ({ refreshButton, subtitle }: CountersProps) => {
const { const {
interval, interval,
numDnsQueries, numDnsQueries,
@ -53,77 +69,67 @@ const Counters = ({ refreshButton, subtitle }) => {
numReplacedSafesearch, numReplacedSafesearch,
avgProcessingTime, avgProcessingTime,
timeUnits, timeUnits,
} = useSelector((state) => state.stats, shallowEqual); } = useSelector<RootState, RootState['stats']>((state) => state.stats, shallowEqual);
const { t } = useTranslation(); const { t } = useTranslation();
const dnsQueryTooltip = timeUnits === TIME_UNITS.HOURS const dnsQueryTooltip =
timeUnits === TIME_UNITS.HOURS
? t('number_of_dns_query_hours', { count: msToHours(interval) }) ? t('number_of_dns_query_hours', { count: msToHours(interval) })
: t('number_of_dns_query_days', { count: msToDays(interval) }); : t('number_of_dns_query_days', { count: msToDays(interval) });
const rows = [ const rows = [
{ {
label: 'dns_query', label: 'dns_query',
count: numDnsQueries, count: numDnsQueries.toString(),
tooltipTitle: dnsQueryTooltip, tooltipTitle: dnsQueryTooltip,
response_status: RESPONSE_FILTER.ALL.QUERY, response_status: RESPONSE_FILTER.ALL.QUERY,
}, },
{ {
label: 'blocked_by', label: 'blocked_by',
count: numBlockedFiltering, count: numBlockedFiltering.toString(),
tooltipTitle: 'number_of_dns_query_blocked_24_hours', tooltipTitle: 'number_of_dns_query_blocked_24_hours',
response_status: RESPONSE_FILTER.BLOCKED.QUERY, response_status: RESPONSE_FILTER.BLOCKED.QUERY,
translationComponents: [<a href="#filters" key="0">link</a>],
translationComponents: [
<a href="#filters" key="0">
link
</a>,
],
}, },
{ {
label: 'stats_malware_phishing', label: 'stats_malware_phishing',
count: numReplacedSafebrowsing, count: numReplacedSafebrowsing.toString(),
tooltipTitle: 'number_of_dns_query_blocked_24_hours_by_sec', tooltipTitle: 'number_of_dns_query_blocked_24_hours_by_sec',
response_status: RESPONSE_FILTER.BLOCKED_THREATS.QUERY, response_status: RESPONSE_FILTER.BLOCKED_THREATS.QUERY,
}, },
{ {
label: 'stats_adult', label: 'stats_adult',
count: numReplacedParental, count: numReplacedParental.toString(),
tooltipTitle: 'number_of_dns_query_blocked_24_hours_adult', tooltipTitle: 'number_of_dns_query_blocked_24_hours_adult',
response_status: RESPONSE_FILTER.BLOCKED_ADULT_WEBSITES.QUERY, response_status: RESPONSE_FILTER.BLOCKED_ADULT_WEBSITES.QUERY,
}, },
{ {
label: 'enforced_save_search', label: 'enforced_save_search',
count: numReplacedSafesearch, count: numReplacedSafesearch.toString(),
tooltipTitle: 'number_of_dns_query_to_safe_search', tooltipTitle: 'number_of_dns_query_to_safe_search',
response_status: RESPONSE_FILTER.SAFE_SEARCH.QUERY, response_status: RESPONSE_FILTER.SAFE_SEARCH.QUERY,
}, },
{ {
label: 'average_processing_time', label: 'average_processing_time',
count: avgProcessingTime ? `${round(avgProcessingTime)} ms` : 0, count: avgProcessingTime ? `${round(avgProcessingTime)} ms` : '0',
tooltipTitle: 'average_processing_time_hint', tooltipTitle: 'average_processing_time_hint',
}, },
]; ];
return ( return (
<Card <Card title={t('general_statistics')} subtitle={subtitle} bodyType="card-table" refresh={refreshButton}>
title={t('general_statistics')}
subtitle={subtitle}
bodyType="card-table"
refresh={refreshButton}
>
<div className="counters"> <div className="counters">
{rows.map(Row)} {rows.map((row, index) => {
return <Row {...row} key={index} />;
})}
</div> </div>
</Card> </Card>
); );
}; };
Row.propTypes = {
label: propTypes.string.isRequired,
count: propTypes.string.isRequired,
response_status: propTypes.string,
tooltipTitle: propTypes.string.isRequired,
translationComponents: propTypes.arrayOf(propTypes.element),
};
Counters.propTypes = {
refreshButton: propTypes.node.isRequired,
subtitle: propTypes.string.isRequired,
};
export default Counters; export default Counters;

View File

@ -1,77 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Trans } from 'react-i18next';
import { getSourceData, getTrackerData } from '../../helpers/trackers/trackers';
import Tooltip from '../ui/Tooltip';
import { captitalizeWords } from '../../helpers/helpers';
const renderLabel = (value) => <strong><Trans>{value}</Trans></strong>;
const renderLink = ({ url, name }) => <a
className="tooltip-custom__content-link"
target="_blank"
rel="noopener noreferrer"
href={url}
>
<strong>{name}</strong>
</a>;
const getTrackerInfo = (trackerData) => [{
key: 'name_table_header',
value: trackerData,
render: renderLink,
},
{
key: 'category_label',
value: captitalizeWords(trackerData.category),
render: renderLabel,
},
{
key: 'source_label',
value: getSourceData(trackerData),
render: renderLink,
}];
const DomainCell = ({ value }) => {
const trackerData = getTrackerData(value);
const content = trackerData && <div className="popover__list">
<div className="tooltip-custom__content-title mb-1">
<Trans>found_in_known_domain_db</Trans>
</div>
{getTrackerInfo(trackerData)
.map(({ key, value, render }) => <div
key={key}
className="tooltip-custom__content-item"
>
<Trans>{key}</Trans>: {render(value)}
</div>)}
</div>;
return (
<div className="logs__row">
<div className="logs__text" title={value}>
{value}
</div>
{trackerData
&& <Tooltip content={content} placement="top"
className="tooltip-container tooltip-custom--wide">
<svg className="icons icon--24 icon--green ml-1">
<use xlinkHref="#privacy" />
</svg>
</Tooltip>}
</div>
);
};
DomainCell.propTypes = {
value: PropTypes.string.isRequired,
};
renderLink.propTypes = {
url: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
};
export default DomainCell;

View File

@ -0,0 +1,81 @@
import React from 'react';
import { Trans } from 'react-i18next';
import { getSourceData, getTrackerData } from '../../helpers/trackers/trackers';
import Tooltip from '../ui/Tooltip';
import { captitalizeWords } from '../../helpers/helpers';
const renderLabel = (value: any) => (
<strong>
<Trans>{value}</Trans>
</strong>
);
interface renderLinkProps {
url: string;
name: string;
}
const renderLink = ({ url, name }: renderLinkProps) => (
<a className="tooltip-custom__content-link" target="_blank" rel="noopener noreferrer" href={url}>
<strong>{name}</strong>
</a>
);
const getTrackerInfo = (trackerData: any) => [
{
key: 'name_table_header',
value: trackerData,
render: renderLink,
},
{
key: 'category_label',
value: captitalizeWords(trackerData.category),
render: renderLabel,
},
{
key: 'source_label',
value: getSourceData(trackerData),
render: renderLink,
},
];
interface DomainCellProps {
value: string;
}
const DomainCell = ({ value }: DomainCellProps) => {
const trackerData = getTrackerData(value);
const content = trackerData && (
<div className="popover__list">
<div className="tooltip-custom__content-title mb-1">
<Trans>found_in_known_domain_db</Trans>
</div>
{getTrackerInfo(trackerData).map(({ key, value, render }) => (
<div key={key} className="tooltip-custom__content-item">
<Trans>{key}</Trans>: {render(value)}
</div>
))}
</div>
);
return (
<div className="logs__row">
<div className="logs__text" title={value}>
{value}
</div>
{trackerData && (
<Tooltip content={content} placement="top" className="tooltip-container tooltip-custom--wide">
<svg className="icons icon--24 icon--green ml-1">
<use xlinkHref="#privacy" />
</svg>
</Tooltip>
)}
</div>
);
};
export default DomainCell;

View File

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
// @ts-expect-error FIXME: update react-table
import ReactTable from 'react-table'; import ReactTable from 'react-table';
import PropTypes from 'prop-types';
import { withTranslation, Trans } from 'react-i18next'; import { withTranslation, Trans } from 'react-i18next';
import Card from '../ui/Card'; import Card from '../ui/Card';
@ -8,9 +9,10 @@ import Cell from '../ui/Cell';
import DomainCell from './DomainCell'; import DomainCell from './DomainCell';
import { DASHBOARD_TABLES_DEFAULT_PAGE_SIZE, STATUS_COLORS, TABLES_MIN_ROWS } from '../../helpers/constants'; import { DASHBOARD_TABLES_DEFAULT_PAGE_SIZE, STATUS_COLORS, TABLES_MIN_ROWS } from '../../helpers/constants';
import { getPercent } from '../../helpers/helpers'; import { getPercent } from '../../helpers/helpers';
const getQueriedPercentColor = (percent) => { const getQueriedPercentColor = (percent: any) => {
if (percent > 10) { if (percent > 10) {
return STATUS_COLORS.red; return STATUS_COLORS.red;
} }
@ -20,26 +22,27 @@ const getQueriedPercentColor = (percent) => {
return STATUS_COLORS.green; return STATUS_COLORS.green;
}; };
const countCell = (dnsQueries) => function cell(row) { const countCell = (dnsQueries: any) =>
function cell(row: any) {
const { value } = row; const { value } = row;
const percent = getPercent(dnsQueries, value); const percent = getPercent(dnsQueries, value);
const percentColor = getQueriedPercentColor(percent); const percentColor = getQueriedPercentColor(percent);
return <Cell value={value} percent={percent} color={percentColor} return <Cell value={value} percent={percent} color={percentColor} search={row.original.domain} />;
search={row.original.domain} />; };
};
const QueriedDomains = ({ interface QueriedDomainsProps {
t, refreshButton, topQueriedDomains, subtitle, dnsQueries, topQueriedDomains: unknown[];
}) => ( dnsQueries: number;
<Card refreshButton: React.ReactNode;
title={t('stats_query_domain')} subtitle: string;
subtitle={subtitle} t: (...args: unknown[]) => string;
bodyType="card-table" }
refresh={refreshButton}
> const QueriedDomains = ({ t, refreshButton, topQueriedDomains, subtitle, dnsQueries }: QueriedDomainsProps) => (
<Card title={t('stats_query_domain')} subtitle={subtitle} bodyType="card-table" refresh={refreshButton}>
<ReactTable <ReactTable
data={topQueriedDomains.map(({ name: domain, count }) => ({ data={topQueriedDomains.map(({ name: domain, count }: any) => ({
domain, domain,
count, count,
}))} }))}
@ -65,12 +68,4 @@ const QueriedDomains = ({
</Card> </Card>
); );
QueriedDomains.propTypes = {
topQueriedDomains: PropTypes.array.isRequired,
dnsQueries: PropTypes.number.isRequired,
refreshButton: PropTypes.node.isRequired,
subtitle: PropTypes.string.isRequired,
t: PropTypes.func.isRequired,
};
export default withTranslation()(QueriedDomains); export default withTranslation()(QueriedDomains);

View File

@ -1,15 +1,27 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { withTranslation, Trans } from 'react-i18next'; import { withTranslation, Trans } from 'react-i18next';
import StatsCard from './StatsCard'; import StatsCard from './StatsCard';
import { getPercent, normalizeHistory } from '../../helpers/helpers'; import { getPercent, normalizeHistory } from '../../helpers/helpers';
import { RESPONSE_FILTER } from '../../helpers/constants'; import { RESPONSE_FILTER } from '../../helpers/constants';
const getNormalizedHistory = (data, interval, id) => [ const getNormalizedHistory = (data: any, interval: any, id: any) => [{ data: normalizeHistory(data), id }];
{ data: normalizeHistory(data, interval), id },
]; interface StatisticsProps {
interval: number;
dnsQueries: number[];
blockedFiltering: unknown[];
replacedSafebrowsing: unknown[];
replacedParental: unknown[];
numDnsQueries: number;
numBlockedFiltering: number;
numReplacedSafebrowsing: number;
numReplacedParental: number;
refreshButton: React.ReactNode;
}
const Statistics = ({ const Statistics = ({
interval, interval,
@ -21,61 +33,68 @@ const Statistics = ({
numBlockedFiltering, numBlockedFiltering,
numReplacedSafebrowsing, numReplacedSafebrowsing,
numReplacedParental, numReplacedParental,
}) => ( }: StatisticsProps) => (
<div className="row"> <div className="row">
<div className="col-sm-6 col-lg-3"> <div className="col-sm-6 col-lg-3">
<StatsCard <StatsCard
total={numDnsQueries} total={numDnsQueries}
lineData={getNormalizedHistory(dnsQueries, interval, 'dnsQuery')} lineData={getNormalizedHistory(dnsQueries, interval, 'dnsQuery')}
title={<Link to="logs"><Trans>dns_query</Trans></Link>} title={
<Link to="logs">
<Trans>dns_query</Trans>
</Link>
}
color="blue" color="blue"
/> />
</div> </div>
<div className="col-sm-6 col-lg-3"> <div className="col-sm-6 col-lg-3">
<StatsCard <StatsCard
total={numBlockedFiltering} total={numBlockedFiltering}
lineData={getNormalizedHistory(blockedFiltering, interval, 'blockedFiltering')} lineData={getNormalizedHistory(blockedFiltering, interval, 'blockedFiltering')}
percent={getPercent(numDnsQueries, numBlockedFiltering)} percent={getPercent(numDnsQueries, numBlockedFiltering)}
title={<Trans components={[<Link to={`logs?response_status=${RESPONSE_FILTER.BLOCKED.QUERY}`} key="0">link</Link>]}>blocked_by</Trans>} title={
<Trans
components={[
<Link to={`logs?response_status=${RESPONSE_FILTER.BLOCKED.QUERY}`} key="0">
link
</Link>,
]}>
blocked_by
</Trans>
}
color="red" color="red"
/> />
</div> </div>
<div className="col-sm-6 col-lg-3"> <div className="col-sm-6 col-lg-3">
<StatsCard <StatsCard
total={numReplacedSafebrowsing} total={numReplacedSafebrowsing}
lineData={getNormalizedHistory( lineData={getNormalizedHistory(replacedSafebrowsing, interval, 'replacedSafebrowsing')}
replacedSafebrowsing,
interval,
'replacedSafebrowsing',
)}
percent={getPercent(numDnsQueries, numReplacedSafebrowsing)} percent={getPercent(numDnsQueries, numReplacedSafebrowsing)}
title={<Link to={`logs?response_status=${RESPONSE_FILTER.BLOCKED_THREATS.QUERY}`}><Trans>stats_malware_phishing</Trans></Link>} title={
<Link to={`logs?response_status=${RESPONSE_FILTER.BLOCKED_THREATS.QUERY}`}>
<Trans>stats_malware_phishing</Trans>
</Link>
}
color="green" color="green"
/> />
</div> </div>
<div className="col-sm-6 col-lg-3"> <div className="col-sm-6 col-lg-3">
<StatsCard <StatsCard
total={numReplacedParental} total={numReplacedParental}
lineData={getNormalizedHistory(replacedParental, interval, 'replacedParental')} lineData={getNormalizedHistory(replacedParental, interval, 'replacedParental')}
percent={getPercent(numDnsQueries, numReplacedParental)} percent={getPercent(numDnsQueries, numReplacedParental)}
title={<Link to={`logs?response_status=${RESPONSE_FILTER.BLOCKED_ADULT_WEBSITES.QUERY}`}><Trans>stats_adult</Trans></Link>} title={
<Link to={`logs?response_status=${RESPONSE_FILTER.BLOCKED_ADULT_WEBSITES.QUERY}`}>
<Trans>stats_adult</Trans>
</Link>
}
color="yellow" color="yellow"
/> />
</div> </div>
</div> </div>
); );
Statistics.propTypes = {
interval: PropTypes.number.isRequired,
dnsQueries: PropTypes.array.isRequired,
blockedFiltering: PropTypes.array.isRequired,
replacedSafebrowsing: PropTypes.array.isRequired,
replacedParental: PropTypes.array.isRequired,
numDnsQueries: PropTypes.number.isRequired,
numBlockedFiltering: PropTypes.number.isRequired,
numReplacedSafebrowsing: PropTypes.number.isRequired,
numReplacedParental: PropTypes.number.isRequired,
refreshButton: PropTypes.node.isRequired,
};
export default withTranslation()(Statistics); export default withTranslation()(Statistics);

View File

@ -1,38 +1,34 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import { STATUS_COLORS } from '../../helpers/constants'; import { STATUS_COLORS } from '../../helpers/constants';
import { formatNumber } from '../../helpers/helpers'; import { formatNumber } from '../../helpers/helpers';
import Card from '../ui/Card'; import Card from '../ui/Card';
import Line from '../ui/Line'; import Line from '../ui/Line';
const StatsCard = ({ interface StatsCardProps {
total, lineData, percent, title, color, total: number;
}) => ( lineData: unknown[];
title: object;
color: string;
percent?: number;
}
const StatsCard = ({ total, lineData, percent, title, color }: StatsCardProps) => (
<Card type="card--full" bodyType="card-wrap"> <Card type="card--full" bodyType="card-wrap">
<div className="card-body-stats"> <div className="card-body-stats">
<div className={`card-value card-value-stats text-${color}`}> <div className={`card-value card-value-stats text-${color}`}>{formatNumber(total)}</div>
{formatNumber(total)}
</div>
<div className="card-title-stats">{title}</div> <div className="card-title-stats">{title}</div>
</div> </div>
{percent >= 0 && ( {percent >= 0 && <div className={`card-value card-value-percent text-${color}`}>{percent}</div>}
<div className={`card-value card-value-percent text-${color}`}>
{percent}
</div>
)}
<div className="card-chart-bg"> <div className="card-chart-bg">
<Line data={lineData} color={STATUS_COLORS[color]} /> <Line data={lineData} color={STATUS_COLORS[color]} />
</div> </div>
</Card> </Card>
); );
StatsCard.propTypes = {
total: PropTypes.number.isRequired,
lineData: PropTypes.array.isRequired,
title: PropTypes.object.isRequired,
color: PropTypes.string.isRequired,
percent: PropTypes.number,
};
export default StatsCard; export default StatsCard;

View File

@ -1,50 +1,47 @@
import React from 'react'; import React from 'react';
// @ts-expect-error FIXME: update react-table
import ReactTable from 'react-table'; import ReactTable from 'react-table';
import PropTypes from 'prop-types';
import round from 'lodash/round'; import round from 'lodash/round';
import { withTranslation, Trans } from 'react-i18next'; import { withTranslation, Trans } from 'react-i18next';
import { TFunction } from 'i18next';
import Card from '../ui/Card'; import Card from '../ui/Card';
import DomainCell from './DomainCell'; import DomainCell from './DomainCell';
import { DASHBOARD_TABLES_DEFAULT_PAGE_SIZE, TABLES_MIN_ROWS } from '../../helpers/constants'; import { DASHBOARD_TABLES_DEFAULT_PAGE_SIZE, TABLES_MIN_ROWS } from '../../helpers/constants';
const TimeCell = ({ value }) => { interface TimeCellProps {
value?: string | number;
}
const TimeCell = ({ value }: TimeCellProps) => {
if (!value) { if (!value) {
return ''; return '';
} }
const valueInMilliseconds = round(value * 1000); const valueInMilliseconds = round(Number(value) * 1000);
return ( return (
<div className="logs__row o-hidden"> <div className="logs__row o-hidden">
<span className="logs__text logs__text--full" title={valueInMilliseconds}> <span className="logs__text logs__text--full" title={valueInMilliseconds.toString()}>
{valueInMilliseconds}&nbsp;ms {valueInMilliseconds}&nbsp;ms
</span> </span>
</div> </div>
); );
}; };
TimeCell.propTypes = { interface UpstreamAvgTimeProps {
value: PropTypes.oneOfType([ topUpstreamsAvgTime: { name: string; count: number }[];
PropTypes.string, refreshButton: React.ReactNode;
PropTypes.number, subtitle: string;
]), t: TFunction;
}; }
const UpstreamAvgTime = ({ const UpstreamAvgTime = ({ t, refreshButton, topUpstreamsAvgTime, subtitle }: UpstreamAvgTimeProps) => (
t, <Card title={t('average_upstream_response_time')} subtitle={subtitle} bodyType="card-table" refresh={refreshButton}>
refreshButton,
topUpstreamsAvgTime,
subtitle,
}) => (
<Card
title={t('average_upstream_response_time')}
subtitle={subtitle}
bodyType="card-table"
refresh={refreshButton}
>
<ReactTable <ReactTable
data={topUpstreamsAvgTime.map(({ name: domain, count }) => ({ data={topUpstreamsAvgTime.map(({ name: domain, count }: { name: string; count: number }) => ({
domain, domain,
count, count,
}))} }))}
@ -70,11 +67,4 @@ const UpstreamAvgTime = ({
</Card> </Card>
); );
UpstreamAvgTime.propTypes = {
topUpstreamsAvgTime: PropTypes.array.isRequired,
refreshButton: PropTypes.node.isRequired,
subtitle: PropTypes.string.isRequired,
t: PropTypes.func.isRequired,
};
export default withTranslation()(UpstreamAvgTime); export default withTranslation()(UpstreamAvgTime);

View File

@ -1,51 +1,47 @@
import React from 'react'; import React from 'react';
// @ts-expect-error FIXME: update react-table
import ReactTable from 'react-table'; import ReactTable from 'react-table';
import PropTypes from 'prop-types';
import { withTranslation, Trans } from 'react-i18next'; import { withTranslation, Trans } from 'react-i18next';
import { TFunction } from 'i18next';
import Card from '../ui/Card'; import Card from '../ui/Card';
import Cell from '../ui/Cell'; import Cell from '../ui/Cell';
import DomainCell from './DomainCell'; import DomainCell from './DomainCell';
import { getPercent } from '../../helpers/helpers'; import { getPercent } from '../../helpers/helpers';
import { DASHBOARD_TABLES_DEFAULT_PAGE_SIZE, STATUS_COLORS, TABLES_MIN_ROWS } from '../../helpers/constants'; import { DASHBOARD_TABLES_DEFAULT_PAGE_SIZE, STATUS_COLORS, TABLES_MIN_ROWS } from '../../helpers/constants';
const CountCell = (totalBlocked) => ( const CountCell = (totalBlocked: any) =>
function cell(row) { function cell(row: any) {
const { value } = row; const { value } = row;
const percent = getPercent(totalBlocked, value); const percent = getPercent(totalBlocked, value);
return ( return <Cell value={value} percent={percent} color={STATUS_COLORS.green} />;
<Cell };
value={value}
percent={percent}
color={STATUS_COLORS.green}
/>
);
}
);
const getTotalUpstreamRequests = (stats) => { const getTotalUpstreamRequests = (stats: any) => {
let total = 0; let total = 0;
stats.forEach(({ count }) => { total += count; }); stats.forEach(({ count }: any) => {
total += count;
});
return total; return total;
}; };
const UpstreamResponses = ({ interface UpstreamResponsesProps {
t, topUpstreamsResponses: { name: string; count: number }[];
refreshButton, refreshButton: React.ReactNode;
topUpstreamsResponses, subtitle: string;
subtitle, t: TFunction;
}) => ( }
<Card
title={t('top_upstreams')} const UpstreamResponses = ({ t, refreshButton, topUpstreamsResponses, subtitle }: UpstreamResponsesProps) => (
subtitle={subtitle} <Card title={t('top_upstreams')} subtitle={subtitle} bodyType="card-table" refresh={refreshButton}>
bodyType="card-table"
refresh={refreshButton}
>
<ReactTable <ReactTable
data={topUpstreamsResponses.map(({ name: domain, count }) => ({ data={topUpstreamsResponses.map(({ name: domain, count }: { name: string; count: number }) => ({
domain, domain,
count, count,
}))} }))}
@ -71,11 +67,4 @@ const UpstreamResponses = ({
</Card> </Card>
); );
UpstreamResponses.propTypes = {
topUpstreamsResponses: PropTypes.array.isRequired,
refreshButton: PropTypes.node.isRequired,
subtitle: PropTypes.string.isRequired,
t: PropTypes.func.isRequired,
};
export default withTranslation()(UpstreamResponses); export default withTranslation()(UpstreamResponses);

View File

@ -1,276 +0,0 @@
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import { HashLink as Link } from 'react-router-hash-link';
import { Trans, useTranslation } from 'react-i18next';
import classNames from 'classnames';
import Statistics from './Statistics';
import Counters from './Counters';
import Clients from './Clients';
import QueriedDomains from './QueriedDomains';
import BlockedDomains from './BlockedDomains';
import {
DISABLE_PROTECTION_TIMINGS,
ONE_SECOND_IN_MS,
SETTINGS_URLS,
TIME_UNITS,
} from '../../helpers/constants';
import {
msToSeconds,
msToMinutes,
msToHours,
msToDays,
} from '../../helpers/helpers';
import PageTitle from '../ui/PageTitle';
import Loading from '../ui/Loading';
import './Dashboard.css';
import Dropdown from '../ui/Dropdown';
import UpstreamResponses from './UpstreamResponses';
import UpstreamAvgTime from './UpstreamAvgTime';
const Dashboard = ({
getAccessList,
getStats,
getStatsConfig,
dashboard,
dashboard: { protectionEnabled, processingProtection, protectionDisabledDuration },
toggleProtection,
stats,
access,
}) => {
const { t } = useTranslation();
const getAllStats = () => {
getAccessList();
getStats();
getStatsConfig();
};
useEffect(() => {
getAllStats();
}, []);
const getSubtitle = () => {
if (!stats.enabled) {
return t('stats_disabled_short');
}
const msIn7Days = 604800000;
if (stats.timeUnits === TIME_UNITS.HOURS && stats.interval === msIn7Days) {
return t('for_last_days', { count: msToDays(stats.interval) });
}
return stats.timeUnits === TIME_UNITS.HOURS
? t('for_last_hours', { count: msToHours(stats.interval) })
: t('for_last_days', { count: msToDays(stats.interval) });
};
const buttonClass = classNames('btn btn-sm dashboard-protection-button', {
'btn-gray': protectionEnabled,
'btn-success': !protectionEnabled,
});
const refreshButton = <button
type="button"
className="btn btn-icon btn-outline-primary btn-sm"
title={t('refresh_btn')}
onClick={() => getAllStats()}
>
<svg className="icons icon12">
<use xlinkHref="#refresh" />
</svg>
</button>;
const statsProcessing = stats.processingStats
|| stats.processingGetConfig
|| access.processing;
const subtitle = getSubtitle();
const DISABLE_PROTECTION_ITEMS = [
{
text: t('disable_for_seconds', { count: msToSeconds(DISABLE_PROTECTION_TIMINGS.HALF_MINUTE) }),
disableTime: DISABLE_PROTECTION_TIMINGS.HALF_MINUTE,
},
{
text: t('disable_for_minutes', { count: msToMinutes(DISABLE_PROTECTION_TIMINGS.MINUTE) }),
disableTime: DISABLE_PROTECTION_TIMINGS.MINUTE,
},
{
text: t('disable_for_minutes', { count: msToMinutes(DISABLE_PROTECTION_TIMINGS.TEN_MINUTES) }),
disableTime: DISABLE_PROTECTION_TIMINGS.TEN_MINUTES,
},
{
text: t('disable_for_hours', { count: msToHours(DISABLE_PROTECTION_TIMINGS.HOUR) }),
disableTime: DISABLE_PROTECTION_TIMINGS.HOUR,
},
{
text: t('disable_until_tomorrow'),
disableTime: DISABLE_PROTECTION_TIMINGS.TOMORROW,
},
];
const getDisableProtectionItems = () => (
Object.values(DISABLE_PROTECTION_ITEMS)
.map((item, index) => (
<div
key={`disable_timings_${index}`}
className="dropdown-item"
onClick={() => {
toggleProtection(protectionEnabled, item.disableTime - ONE_SECOND_IN_MS);
}}
>
{item.text}
</div>
))
);
const getRemaningTimeText = (milliseconds) => {
if (!milliseconds) {
return '';
}
const date = new Date(milliseconds);
const hh = date.getUTCHours();
const mm = `0${date.getUTCMinutes()}`.slice(-2);
const ss = `0${date.getUTCSeconds()}`.slice(-2);
const formattedHH = `0${hh}`.slice(-2);
return hh ? `${formattedHH}:${mm}:${ss}` : `${mm}:${ss}`;
};
const getProtectionBtnText = (status) => (status ? t('disable_protection') : t('enable_protection'));
return <>
<PageTitle title={t('dashboard')} containerClass="page-title--dashboard">
<div className="page-title__protection">
<button
type="button"
className={buttonClass}
onClick={() => {
toggleProtection(protectionEnabled);
}}
disabled={processingProtection}
>
{protectionDisabledDuration
? `${t('enable_protection_timer')} ${getRemaningTimeText(protectionDisabledDuration)}`
: getProtectionBtnText(protectionEnabled)
}
</button>
{protectionEnabled && <Dropdown
label=""
baseClassName="dropdown-protection"
icon="arrow-down"
controlClassName="dropdown-protection__toggle"
menuClassName="dropdown-menu dropdown-menu-arrow dropdown-menu--protection"
>
{getDisableProtectionItems()}
</Dropdown>}
</div>
<button
type="button"
className="btn btn-outline-primary btn-sm"
onClick={getAllStats}
>
<Trans>refresh_statics</Trans>
</button>
</PageTitle>
{statsProcessing && <Loading />}
{!statsProcessing && <div className="row row-cards dashboard">
<div className="col-lg-12">
{stats.interval === 0 && (
<div className="alert alert-warning" role="alert">
<Trans components={[
<Link
to={`${SETTINGS_URLS.settings}#stats-config`}
key="0"
>
link
</Link>,
]}>
stats_disabled
</Trans>
</div>
)}
<Statistics
interval={msToDays(stats.interval)}
dnsQueries={stats.dnsQueries}
blockedFiltering={stats.blockedFiltering}
replacedSafebrowsing={stats.replacedSafebrowsing}
replacedParental={stats.replacedParental}
numDnsQueries={stats.numDnsQueries}
numBlockedFiltering={stats.numBlockedFiltering}
numReplacedSafebrowsing={stats.numReplacedSafebrowsing}
numReplacedParental={stats.numReplacedParental}
refreshButton={refreshButton}
/>
</div>
<div className="col-lg-6">
<Counters
subtitle={subtitle}
refreshButton={refreshButton}
/>
</div>
<div className="col-lg-6">
<Clients
subtitle={subtitle}
dnsQueries={stats.numDnsQueries}
topClients={stats.topClients}
clients={dashboard.clients}
autoClients={dashboard.autoClients}
refreshButton={refreshButton}
processingAccessSet={access.processingSet}
disallowedClients={access.disallowed_clients}
/>
</div>
<div className="col-lg-6">
<QueriedDomains
subtitle={subtitle}
dnsQueries={stats.numDnsQueries}
topQueriedDomains={stats.topQueriedDomains}
refreshButton={refreshButton}
/>
</div>
<div className="col-lg-6">
<BlockedDomains
subtitle={subtitle}
topBlockedDomains={stats.topBlockedDomains}
blockedFiltering={stats.numBlockedFiltering}
replacedSafebrowsing={stats.numReplacedSafebrowsing}
replacedSafesearch={stats.numReplacedSafesearch}
replacedParental={stats.numReplacedParental}
refreshButton={refreshButton}
/>
</div>
<div className="col-lg-6">
<UpstreamResponses
subtitle={subtitle}
topUpstreamsResponses={stats.topUpstreamsResponses}
refreshButton={refreshButton}
/>
</div>
<div className="col-lg-6">
<UpstreamAvgTime
subtitle={subtitle}
topUpstreamsAvgTime={stats.topUpstreamsAvgTime}
refreshButton={refreshButton}
/>
</div>
</div>}
</>;
};
Dashboard.propTypes = {
dashboard: PropTypes.object.isRequired,
stats: PropTypes.object.isRequired,
access: PropTypes.object.isRequired,
getStats: PropTypes.func.isRequired,
getStatsConfig: PropTypes.func.isRequired,
toggleProtection: PropTypes.func.isRequired,
getClients: PropTypes.func.isRequired,
getAccessList: PropTypes.func.isRequired,
};
export default Dashboard;

View File

@ -0,0 +1,260 @@
import React, { useEffect } from 'react';
import { HashLink as Link } from 'react-router-hash-link';
import { Trans, useTranslation } from 'react-i18next';
import classNames from 'classnames';
import Statistics from './Statistics';
import Counters from './Counters';
import Clients from './Clients';
import QueriedDomains from './QueriedDomains';
import BlockedDomains from './BlockedDomains';
import { DISABLE_PROTECTION_TIMINGS, ONE_SECOND_IN_MS, SETTINGS_URLS, TIME_UNITS } from '../../helpers/constants';
import { msToSeconds, msToMinutes, msToHours, msToDays } from '../../helpers/helpers';
import PageTitle from '../ui/PageTitle';
import Loading from '../ui/Loading';
import './Dashboard.css';
import Dropdown from '../ui/Dropdown';
import UpstreamResponses from './UpstreamResponses';
import UpstreamAvgTime from './UpstreamAvgTime';
import { AccessData, DashboardData, StatsData } from '../../initialState';
interface DashboardProps {
dashboard: DashboardData;
stats: StatsData;
access: AccessData;
getStats: (...args: unknown[]) => unknown;
getStatsConfig: (...args: unknown[]) => unknown;
toggleProtection: (...args: unknown[]) => unknown;
getClients: (...args: unknown[]) => unknown;
getAccessList: () => (dispatch: any) => void;
}
const Dashboard = ({
getAccessList,
getStats,
getStatsConfig,
dashboard: { protectionEnabled, processingProtection, protectionDisabledDuration },
toggleProtection,
stats,
access,
}: DashboardProps) => {
const { t } = useTranslation();
const getAllStats = () => {
getAccessList();
getStats();
getStatsConfig();
};
useEffect(() => {
getAllStats();
}, []);
const getSubtitle = () => {
if (!stats.enabled) {
return t('stats_disabled_short');
}
const msIn7Days = 604800000;
if (stats.timeUnits === TIME_UNITS.HOURS && stats.interval === msIn7Days) {
return t('for_last_days', { count: msToDays(stats.interval) });
}
return stats.timeUnits === TIME_UNITS.HOURS
? t('for_last_hours', { count: msToHours(stats.interval) })
: t('for_last_days', { count: msToDays(stats.interval) });
};
const buttonClass = classNames('btn btn-sm dashboard-protection-button', {
'btn-gray': protectionEnabled,
'btn-success': !protectionEnabled,
});
const refreshButton = (
<button
type="button"
className="btn btn-icon btn-outline-primary btn-sm"
title={t('refresh_btn')}
onClick={() => getAllStats()}>
<svg className="icons icon12">
<use xlinkHref="#refresh" />
</svg>
</button>
);
const statsProcessing = stats.processingStats || stats.processingGetConfig || access.processing;
const subtitle = getSubtitle();
const DISABLE_PROTECTION_ITEMS = [
{
text: t('disable_for_seconds', { count: msToSeconds(DISABLE_PROTECTION_TIMINGS.HALF_MINUTE) }),
disableTime: DISABLE_PROTECTION_TIMINGS.HALF_MINUTE,
},
{
text: t('disable_for_minutes', { count: msToMinutes(DISABLE_PROTECTION_TIMINGS.MINUTE) }),
disableTime: DISABLE_PROTECTION_TIMINGS.MINUTE,
},
{
text: t('disable_for_minutes', { count: msToMinutes(DISABLE_PROTECTION_TIMINGS.TEN_MINUTES) }),
disableTime: DISABLE_PROTECTION_TIMINGS.TEN_MINUTES,
},
{
text: t('disable_for_hours', { count: msToHours(DISABLE_PROTECTION_TIMINGS.HOUR) }),
disableTime: DISABLE_PROTECTION_TIMINGS.HOUR,
},
{
text: t('disable_until_tomorrow'),
disableTime: DISABLE_PROTECTION_TIMINGS.TOMORROW,
},
];
const getDisableProtectionItems = () =>
Object.values(DISABLE_PROTECTION_ITEMS).map((item: any, index: any) => (
<div
key={`disable_timings_${index}`}
className="dropdown-item"
onClick={() => {
toggleProtection(protectionEnabled, item.disableTime - ONE_SECOND_IN_MS);
}}>
{item.text}
</div>
));
const getRemaningTimeText = (milliseconds: any) => {
if (!milliseconds) {
return '';
}
const date = new Date(milliseconds);
const hh = date.getUTCHours();
const mm = `0${date.getUTCMinutes()}`.slice(-2);
const ss = `0${date.getUTCSeconds()}`.slice(-2);
const formattedHH = `0${hh}`.slice(-2);
return hh ? `${formattedHH}:${mm}:${ss}` : `${mm}:${ss}`;
};
const getProtectionBtnText = (status: any) => (status ? t('disable_protection') : t('enable_protection'));
return (
<>
<PageTitle title={t('dashboard')} containerClass="page-title--dashboard">
<div className="page-title__protection">
<button
type="button"
className={buttonClass}
onClick={() => {
toggleProtection(protectionEnabled);
}}
disabled={processingProtection}>
{protectionDisabledDuration
? `${t('enable_protection_timer')} ${getRemaningTimeText(protectionDisabledDuration)}`
: getProtectionBtnText(protectionEnabled)}
</button>
{protectionEnabled && (
<Dropdown
label=""
baseClassName="dropdown-protection"
icon="arrow-down"
controlClassName="dropdown-protection__toggle"
menuClassName="dropdown-menu dropdown-menu-arrow dropdown-menu--protection">
{getDisableProtectionItems()}
</Dropdown>
)}
</div>
<button type="button" className="btn btn-outline-primary btn-sm" onClick={getAllStats}>
<Trans>refresh_statics</Trans>
</button>
</PageTitle>
{statsProcessing && <Loading />}
{!statsProcessing && (
<div className="row row-cards dashboard">
<div className="col-lg-12">
{stats.interval === 0 && (
<div className="alert alert-warning" role="alert">
<Trans
components={[
<Link to={`${SETTINGS_URLS.settings}#stats-config`} key="0">
link
</Link>,
]}>
stats_disabled
</Trans>
</div>
)}
<Statistics
interval={msToDays(stats.interval)}
dnsQueries={stats.dnsQueries}
blockedFiltering={stats.blockedFiltering}
replacedSafebrowsing={stats.replacedSafebrowsing}
replacedParental={stats.replacedParental}
numDnsQueries={stats.numDnsQueries}
numBlockedFiltering={stats.numBlockedFiltering}
numReplacedSafebrowsing={stats.numReplacedSafebrowsing}
numReplacedParental={stats.numReplacedParental}
refreshButton={refreshButton}
/>
</div>
<div className="col-lg-6">
<Counters subtitle={subtitle} refreshButton={refreshButton} />
</div>
<div className="col-lg-6">
<Clients subtitle={subtitle} refreshButton={refreshButton} />
</div>
<div className="col-lg-6">
<QueriedDomains
subtitle={subtitle}
dnsQueries={stats.numDnsQueries}
topQueriedDomains={stats.topQueriedDomains}
refreshButton={refreshButton}
/>
</div>
<div className="col-lg-6">
<BlockedDomains
subtitle={subtitle}
topBlockedDomains={stats.topBlockedDomains}
blockedFiltering={stats.numBlockedFiltering}
replacedSafebrowsing={stats.numReplacedSafebrowsing}
replacedSafesearch={stats.numReplacedSafesearch}
replacedParental={stats.numReplacedParental}
refreshButton={refreshButton}
/>
</div>
<div className="col-lg-6">
<UpstreamResponses
subtitle={subtitle}
topUpstreamsResponses={stats.topUpstreamsResponses}
refreshButton={refreshButton}
/>
</div>
<div className="col-lg-6">
<UpstreamAvgTime
subtitle={subtitle}
topUpstreamsAvgTime={stats.topUpstreamsAvgTime}
refreshButton={refreshButton}
/>
</div>
</div>
)}
</>
);
};
export default Dashboard;

View File

@ -1,32 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { withTranslation, Trans } from 'react-i18next';
const Actions = ({
handleAdd, handleRefresh, processingRefreshFilters, whitelist,
}) => <div className="card-actions">
<button
className="btn btn-success btn-standard mr-2 btn-large mb-2"
type="submit"
onClick={handleAdd}
>
{whitelist ? <Trans>add_allowlist</Trans> : <Trans>add_blocklist</Trans>}
</button>
<button
className="btn btn-primary btn-standard mb-2"
type="submit"
onClick={handleRefresh}
disabled={processingRefreshFilters}
>
<Trans>check_updates_btn</Trans>
</button>
</div>;
Actions.propTypes = {
handleAdd: PropTypes.func.isRequired,
handleRefresh: PropTypes.func.isRequired,
processingRefreshFilters: PropTypes.bool.isRequired,
whitelist: PropTypes.bool,
};
export default withTranslation()(Actions);

View File

@ -0,0 +1,27 @@
import React from 'react';
import { withTranslation, Trans } from 'react-i18next';
interface ActionsProps {
handleAdd: (...args: unknown[]) => unknown;
handleRefresh: (...args: unknown[]) => unknown;
processingRefreshFilters: boolean;
whitelist?: boolean;
}
const Actions = ({ handleAdd, handleRefresh, processingRefreshFilters, whitelist }: ActionsProps) => (
<div className="card-actions">
<button className="btn btn-success btn-standard mr-2 btn-large mb-2" type="submit" onClick={handleAdd}>
{whitelist ? <Trans>add_allowlist</Trans> : <Trans>add_blocklist</Trans>}
</button>
<button
className="btn btn-primary btn-standard mb-2"
type="submit"
onClick={handleRefresh}
disabled={processingRefreshFilters}>
<Trans>check_updates_btn</Trans>
</button>
</div>
);
export default withTranslation()(Actions);

View File

@ -15,10 +15,12 @@ import {
getRulesToFilterList, getRulesToFilterList,
} from '../../../helpers/helpers'; } from '../../../helpers/helpers';
import { BLOCK_ACTIONS, FILTERED, FILTERED_STATUS } from '../../../helpers/constants'; import { BLOCK_ACTIONS, FILTERED, FILTERED_STATUS } from '../../../helpers/constants';
import { toggleBlocking } from '../../../actions';
const renderBlockingButton = (isFiltered, domain) => { import { toggleBlocking } from '../../../actions';
const processingRules = useSelector((state) => state.filtering.processingRules); import { RootState } from '../../../initialState';
const renderBlockingButton = (isFiltered: any, domain: any) => {
const processingRules = useSelector((state: RootState) => state.filtering.processingRules);
const dispatch = useDispatch(); const dispatch = useDispatch();
const { t } = useTranslation(); const { t } = useTranslation();
@ -28,28 +30,32 @@ const renderBlockingButton = (isFiltered, domain) => {
await dispatch(toggleBlocking(buttonType, domain)); await dispatch(toggleBlocking(buttonType, domain));
}; };
const buttonClass = classNames('mt-3 button-action button-action--main button-action--active button-action--small', { const buttonClass = classNames(
'mt-3 button-action button-action--main button-action--active button-action--small',
{
'button-action--unblock': isFiltered, 'button-action--unblock': isFiltered,
}); },
);
return <button type="button" return (
className={buttonClass} <button type="button" className={buttonClass} onClick={onClick} disabled={processingRules}>
onClick={onClick}
disabled={processingRules}
>
{t(buttonType)} {t(buttonType)}
</button>; </button>
);
}; };
const getTitle = () => { const getTitle = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const filters = useSelector((state) => state.filtering.filters, shallowEqual); const filters = useSelector((state: RootState) => state.filtering.filters, shallowEqual);
const whitelistFilters = useSelector((state) => state.filtering.whitelistFilters, shallowEqual);
const rules = useSelector((state) => state.filtering.check.rules, shallowEqual);
const reason = useSelector((state) => state.filtering.check.reason);
const getReasonFiltered = (reason) => { const whitelistFilters = useSelector((state: RootState) => state.filtering.whitelistFilters, shallowEqual);
const rules = useSelector((state: RootState) => state.filtering.check.rules, shallowEqual);
const reason = useSelector((state: RootState) => state.filtering.check.reason);
const getReasonFiltered = (reason: any) => {
const filterKey = reason.replace(FILTERED, ''); const filterKey = reason.replace(FILTERED, '');
return i18next.t('query_log_filtered', { filter: filterKey }); return i18next.t('query_log_filtered', { filter: filterKey });
}; };
@ -71,24 +77,23 @@ const getTitle = () => {
return REASON_TO_TITLE_MAP[reason]; return REASON_TO_TITLE_MAP[reason];
} }
return <> return (
<>
<div>{t('check_reason', { reason })}</div> <div>{t('check_reason', { reason })}</div>
<div> <div>
{t('rule_label')}: {t('rule_label')}: &nbsp;
&nbsp;
{ruleAndFilterNames} {ruleAndFilterNames}
</div> </div>
</>; </>
);
}; };
const Info = () => { const Info = () => {
const { const { hostname, reason, service_name, cname, ip_addrs } = useSelector(
hostname, (state: RootState) => state.filtering.check,
reason, shallowEqual,
service_name, );
cname,
ip_addrs,
} = useSelector((state) => state.filtering.check, shallowEqual);
const { t } = useTranslation(); const { t } = useTranslation();
const title = getTitle(); const title = getTitle();
@ -99,23 +104,29 @@ const Info = () => {
'logs__row--green': checkWhiteList(reason), 'logs__row--green': checkWhiteList(reason),
}); });
const onlyFiltered = checkSafeSearch(reason) const onlyFiltered = checkSafeSearch(reason) || checkSafeBrowsing(reason) || checkParental(reason);
|| checkSafeBrowsing(reason)
|| checkParental(reason);
const isFiltered = checkFiltered(reason); const isFiltered = checkFiltered(reason);
return <div className={className}> return (
<div><strong>{hostname}</strong></div> <div className={className}>
<div>
<strong>{hostname}</strong>
</div>
<div>{title}</div> <div>{title}</div>
{!onlyFiltered {!onlyFiltered && (
&& <> <>
{service_name && <div>{t('check_service', { service: service_name })}</div>} {service_name && <div>{t('check_service', { service: service_name })}</div>}
{cname && <div>{t('check_cname', { cname })}</div>} {cname && <div>{t('check_cname', { cname })}</div>}
{ip_addrs && <div>{t('check_ip', { ip: ip_addrs.join(', ') })}</div>} {ip_addrs && <div>{t('check_ip', { ip: ip_addrs.join(', ') })}</div>}
{renderBlockingButton(isFiltered, hostname)} {renderBlockingButton(isFiltered, hostname)}
</>} </>
</div>; )}
</div>
);
}; };
export default Info; export default Info;

View File

@ -1,66 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import { Field, reduxForm } from 'redux-form';
import { useSelector } from 'react-redux';
import Card from '../../ui/Card';
import { renderInputField } from '../../../helpers/form';
import Info from './Info';
import { FORM_NAME } from '../../../helpers/constants';
const Check = (props) => {
const {
pristine,
invalid,
handleSubmit,
} = props;
const { t } = useTranslation();
const processingCheck = useSelector((state) => state.filtering.processingCheck);
const hostname = useSelector((state) => state.filtering.check.hostname);
return <Card
title={t('check_title')}
subtitle={t('check_desc')}
>
<form onSubmit={handleSubmit}>
<div className="row">
<div className="col-12 col-md-6">
<div className="input-group">
<Field
id="name"
name="name"
component={renderInputField}
type="text"
className="form-control"
placeholder={t('form_enter_host')}
/>
<span className="input-group-append">
<button
className="btn btn-success btn-standard btn-large"
type="submit"
onClick={handleSubmit}
disabled={pristine || invalid || processingCheck}
>
{t('check')}
</button>
</span>
</div>
{hostname && <>
<hr />
<Info />
</>}
</div>
</div>
</form>
</Card>;
};
Check.propTypes = {
handleSubmit: PropTypes.func.isRequired,
pristine: PropTypes.bool.isRequired,
invalid: PropTypes.bool.isRequired,
};
export default reduxForm({ form: FORM_NAME.DOMAIN_CHECK })(Check);

View File

@ -0,0 +1,70 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { Field, reduxForm } from 'redux-form';
import { useSelector } from 'react-redux';
import Card from '../../ui/Card';
import { renderInputField } from '../../../helpers/form';
import Info from './Info';
import { FORM_NAME } from '../../../helpers/constants';
import { RootState } from '../../../initialState';
interface CheckProps {
handleSubmit: (...args: unknown[]) => string;
pristine: boolean;
invalid: boolean;
}
const Check = (props: CheckProps) => {
const { pristine, invalid, handleSubmit } = props;
const { t } = useTranslation();
const processingCheck = useSelector((state: RootState) => state.filtering.processingCheck);
const hostname = useSelector((state: RootState) => state.filtering.check.hostname);
return (
<Card title={t('check_title')} subtitle={t('check_desc')}>
<form onSubmit={handleSubmit}>
<div className="row">
<div className="col-12 col-md-6">
<div className="input-group">
<Field
id="name"
name="name"
component={renderInputField}
type="text"
className="form-control"
placeholder={t('form_enter_host')}
/>
<span className="input-group-append">
<button
className="btn btn-success btn-standard btn-large"
type="submit"
onClick={handleSubmit}
disabled={pristine || invalid || processingCheck}>
{t('check')}
</button>
</span>
</div>
{hostname && (
<>
<hr />
<Info />
</>
)}
</div>
</div>
</form>
</Card>
);
};
export default reduxForm({ form: FORM_NAME.DOMAIN_CHECK })(Check);

View File

@ -1,32 +1,46 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Trans, withTranslation } from 'react-i18next'; import { Trans, withTranslation } from 'react-i18next';
import Card from '../ui/Card'; import Card from '../ui/Card';
import PageTitle from '../ui/PageTitle'; import PageTitle from '../ui/PageTitle';
import Examples from './Examples'; import Examples from './Examples';
import Check from './Check'; import Check from './Check';
import { getTextareaCommentsHighlight, syncScroll } from '../../helpers/highlightTextareaComments'; import { getTextareaCommentsHighlight, syncScroll } from '../../helpers/highlightTextareaComments';
import { COMMENT_LINE_DEFAULT_TOKEN } from '../../helpers/constants'; import { COMMENT_LINE_DEFAULT_TOKEN } from '../../helpers/constants';
import '../ui/texareaCommentsHighlight.css'; import '../ui/texareaCommentsHighlight.css';
import { FilteringData } from '../../initialState';
class CustomRules extends Component { interface CustomRulesProps {
filtering: FilteringData;
setRules: (...args: unknown[]) => unknown;
checkHost: (...args: unknown[]) => string;
getFilteringStatus: (...args: unknown[]) => unknown;
handleRulesChange: (...args: unknown[]) => unknown;
t: (...args: unknown[]) => string;
}
class CustomRules extends Component<CustomRulesProps> {
ref = React.createRef(); ref = React.createRef();
componentDidMount() { componentDidMount() {
this.props.getFilteringStatus(); this.props.getFilteringStatus();
} }
handleChange = (e) => { handleChange = (e: any) => {
const { value } = e.currentTarget; const { value } = e.currentTarget;
this.handleRulesChange(value); this.handleRulesChange(value);
}; };
handleSubmit = (e) => { handleSubmit = (e: any) => {
e.preventDefault(); e.preventDefault();
this.handleRulesSubmit(); this.handleRulesSubmit();
}; };
handleRulesChange = (value) => { handleRulesChange = (value: any) => {
this.props.handleRulesChange({ userRules: value }); this.props.handleRulesChange({ userRules: value });
}; };
@ -34,23 +48,22 @@ class CustomRules extends Component {
this.props.setRules(this.props.filtering.userRules); this.props.setRules(this.props.filtering.userRules);
}; };
handleCheck = (values) => { handleCheck = (values: any) => {
this.props.checkHost(values); this.props.checkHost(values);
}; };
onScroll = (e) => syncScroll(e, this.ref) onScroll = (e: any) => syncScroll(e, this.ref);
render() { render() {
const { const {
t, t,
filtering: { filtering: { userRules },
userRules,
},
} = this.props; } = this.props;
return ( return (
<> <>
<PageTitle title={t('custom_filtering_rules')} /> <PageTitle title={t('custom_filtering_rules')} />
<Card subtitle={t('custom_filter_rules_hint')}> <Card subtitle={t('custom_filter_rules_hint')}>
<form onSubmit={this.handleSubmit}> <form onSubmit={this.handleSubmit}>
<div className="text-edit-container mb-4"> <div className="text-edit-container mb-4">
@ -60,39 +73,31 @@ class CustomRules extends Component {
onChange={this.handleChange} onChange={this.handleChange}
onScroll={this.onScroll} onScroll={this.onScroll}
/> />
{getTextareaCommentsHighlight( {getTextareaCommentsHighlight(this.ref, userRules, [
this.ref, COMMENT_LINE_DEFAULT_TOKEN,
userRules, '!',
undefined, ])}
[COMMENT_LINE_DEFAULT_TOKEN, '!'],
)}
</div> </div>
<div className="card-actions"> <div className="card-actions">
<button <button
className="btn btn-success btn-standard btn-large" className="btn btn-success btn-standard btn-large"
type="submit" type="submit"
onClick={this.handleSubmit} onClick={this.handleSubmit}>
>
<Trans>apply_btn</Trans> <Trans>apply_btn</Trans>
</button> </button>
</div> </div>
</form> </form>
<hr /> <hr />
<Examples /> <Examples />
</Card> </Card>
<Check onSubmit={this.handleCheck} /> <Check onSubmit={this.handleCheck} />
</> </>
); );
} }
} }
CustomRules.propTypes = {
filtering: PropTypes.object.isRequired,
setRules: PropTypes.func.isRequired,
checkHost: PropTypes.func.isRequired,
getFilteringStatus: PropTypes.func.isRequired,
handleRulesChange: PropTypes.func.isRequired,
t: PropTypes.func.isRequired,
};
export default withTranslation()(CustomRules); export default withTranslation()(CustomRules);

View File

@ -1,5 +1,4 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { withTranslation } from 'react-i18next'; import { withTranslation } from 'react-i18next';
import PageTitle from '../ui/PageTitle'; import PageTitle from '../ui/PageTitle';
@ -9,15 +8,41 @@ import Actions from './Actions';
import Table from './Table'; import Table from './Table';
import { MODAL_TYPE } from '../../helpers/constants'; import { MODAL_TYPE } from '../../helpers/constants';
import { getCurrentFilter } from '../../helpers/helpers'; import { getCurrentFilter } from '../../helpers/helpers';
class DnsAllowlist extends Component { interface DnsAllowlistProps {
getFilteringStatus: (...args: unknown[]) => unknown;
filtering: {
modalType: string;
modalFilterUrl: string;
isModalOpen: boolean;
isFilterAdded: boolean;
processingRefreshFilters: boolean;
processingRemoveFilter: boolean;
processingAddFilter: boolean;
processingConfigFilter: boolean;
processingFilters: boolean;
whitelistFilters: any[];
};
removeFilter: (...args: unknown[]) => unknown;
toggleFilterStatus: (...args: unknown[]) => unknown;
addFilter: (...args: unknown[]) => unknown;
toggleFilteringModal: (...args: unknown[]) => unknown;
handleRulesChange: (...args: unknown[]) => unknown;
refreshFilters: (...args: unknown[]) => unknown;
editFilter: (...args: unknown[]) => unknown;
t: (...args: unknown[]) => string;
}
class DnsAllowlist extends Component<DnsAllowlistProps> {
componentDidMount() { componentDidMount() {
this.props.getFilteringStatus(); this.props.getFilteringStatus();
} }
handleSubmit = (values) => { handleSubmit = (values: any) => {
const { name, url } = values; const { name, url } = values;
const { filtering } = this.props; const { filtering } = this.props;
const whitelist = true; const whitelist = true;
@ -28,15 +53,17 @@ class DnsAllowlist extends Component {
} }
}; };
handleDelete = (url) => { handleDelete = (url: any) => {
if (window.confirm(this.props.t('list_confirm_delete'))) { if (window.confirm(this.props.t('list_confirm_delete'))) {
const whitelist = true; const whitelist = true;
this.props.removeFilter(url, whitelist); this.props.removeFilter(url, whitelist);
} }
}; };
toggleFilter = (url, data) => { toggleFilter = (url: any, data: any) => {
const whitelist = true; const whitelist = true;
this.props.toggleFilterStatus(url, data, whitelist); this.props.toggleFilterStatus(url, data, whitelist);
}; };
@ -53,7 +80,6 @@ class DnsAllowlist extends Component {
t, t,
toggleFilteringModal, toggleFilteringModal,
addFilter, addFilter,
toggleFilterStatus,
filtering: { filtering: {
whitelistFilters, whitelistFilters,
isModalOpen, isModalOpen,
@ -68,19 +94,18 @@ class DnsAllowlist extends Component {
}, },
} = this.props; } = this.props;
const currentFilterData = getCurrentFilter(modalFilterUrl, whitelistFilters); const currentFilterData = getCurrentFilter(modalFilterUrl, whitelistFilters);
const loading = processingConfigFilter const loading =
|| processingFilters processingConfigFilter ||
|| processingAddFilter processingFilters ||
|| processingRemoveFilter processingAddFilter ||
|| processingRefreshFilters; processingRemoveFilter ||
processingRefreshFilters;
const whitelist = true; const whitelist = true;
return ( return (
<> <>
<PageTitle <PageTitle title={t('dns_allowlists')} subtitle={t('dns_allowlists_desc')} />
title={t('dns_allowlists')}
subtitle={t('dns_allowlists_desc')}
/>
<div className="content"> <div className="content">
<div className="row"> <div className="row">
<div className="col-md-12"> <div className="col-md-12">
@ -90,11 +115,11 @@ class DnsAllowlist extends Component {
loading={loading} loading={loading}
processingConfigFilter={processingConfigFilter} processingConfigFilter={processingConfigFilter}
toggleFilteringModal={toggleFilteringModal} toggleFilteringModal={toggleFilteringModal}
toggleFilterStatus={toggleFilterStatus}
handleDelete={this.handleDelete} handleDelete={this.handleDelete}
toggleFilter={this.toggleFilter} toggleFilter={this.toggleFilter}
whitelist={whitelist} whitelist={whitelist}
/> />
<Actions <Actions
handleAdd={this.openAddFiltersModal} handleAdd={this.openAddFiltersModal}
handleRefresh={this.handleRefresh} handleRefresh={this.handleRefresh}
@ -105,6 +130,7 @@ class DnsAllowlist extends Component {
</div> </div>
</div> </div>
</div> </div>
<Modal <Modal
filters={whitelistFilters} filters={whitelistFilters}
isOpen={isModalOpen} isOpen={isModalOpen}
@ -123,17 +149,4 @@ class DnsAllowlist extends Component {
} }
} }
DnsAllowlist.propTypes = {
getFilteringStatus: PropTypes.func.isRequired,
filtering: PropTypes.object.isRequired,
removeFilter: PropTypes.func.isRequired,
toggleFilterStatus: PropTypes.func.isRequired,
addFilter: PropTypes.func.isRequired,
toggleFilteringModal: PropTypes.func.isRequired,
handleRulesChange: PropTypes.func.isRequired,
refreshFilters: PropTypes.func.isRequired,
editFilter: PropTypes.func.isRequired,
t: PropTypes.func.isRequired,
};
export default withTranslation()(DnsAllowlist); export default withTranslation()(DnsAllowlist);

View File

@ -1,27 +1,38 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { withTranslation } from 'react-i18next'; import { withTranslation } from 'react-i18next';
import PageTitle from '../ui/PageTitle'; import PageTitle from '../ui/PageTitle';
import Card from '../ui/Card'; import Card from '../ui/Card';
import Modal from './Modal'; import Modal from './Modal';
import Actions from './Actions'; import Actions from './Actions';
import Table from './Table'; import Table from './Table';
import { MODAL_TYPE } from '../../helpers/constants'; import { MODAL_TYPE } from '../../helpers/constants';
import { import { getCurrentFilter } from '../../helpers/helpers';
getCurrentFilter,
} from '../../helpers/helpers';
import filtersCatalog from '../../helpers/filters/filters'; import filtersCatalog from '../../helpers/filters/filters';
import { FilteringData } from '../../initialState';
class DnsBlocklist extends Component { interface DnsBlocklistProps {
getFilteringStatus: (...args: unknown[]) => unknown;
filtering: FilteringData;
removeFilter: (...args: unknown[]) => unknown;
toggleFilterStatus: (...args: unknown[]) => unknown;
addFilter: (...args: unknown[]) => unknown;
toggleFilteringModal: (...args: unknown[]) => unknown;
handleRulesChange: (...args: unknown[]) => unknown;
refreshFilters: (...args: unknown[]) => unknown;
editFilter: (...args: unknown[]) => unknown;
t: (...args: unknown[]) => string;
}
class DnsBlocklist extends Component<DnsBlocklistProps> {
componentDidMount() { componentDidMount() {
this.props.getFilteringStatus(); this.props.getFilteringStatus();
} }
handleSubmit = (values) => { handleSubmit = (values: any) => {
const { modalFilterUrl, modalType } = this.props.filtering; const { modalFilterUrl, modalType } = this.props.filtering;
switch (modalType) { switch (modalType) {
@ -30,21 +41,23 @@ class DnsBlocklist extends Component {
break; break;
case MODAL_TYPE.ADD_FILTERS: { case MODAL_TYPE.ADD_FILTERS: {
const { name, url } = values; const { name, url } = values;
this.props.addFilter(url, name); this.props.addFilter(url, name);
break; break;
} }
case MODAL_TYPE.CHOOSE_FILTERING_LIST: { case MODAL_TYPE.CHOOSE_FILTERING_LIST: {
const changedValues = Object.entries(values)?.reduce((acc, [key, value]) => { const changedValues = Object.entries(values)?.reduce((acc: any, [key, value]) => {
if (value && key in filtersCatalog.filters) { if (value && key in filtersCatalog.filters) {
acc[key] = value; acc[key] = value;
} }
return acc; return acc;
}, {}); }, {});
Object.keys(changedValues) Object.keys(changedValues).forEach((fieldName) => {
.forEach((fieldName) => {
// filterId is actually in the field name // filterId is actually in the field name
const { source, name } = filtersCatalog.filters[fieldName]; const { source, name } = filtersCatalog.filters[fieldName];
this.props.addFilter(source, name); this.props.addFilter(source, name);
}); });
break; break;
@ -54,13 +67,13 @@ class DnsBlocklist extends Component {
} }
}; };
handleDelete = (url) => { handleDelete = (url: any) => {
if (window.confirm(this.props.t('list_confirm_delete'))) { if (window.confirm(this.props.t('list_confirm_delete'))) {
this.props.removeFilter(url); this.props.removeFilter(url);
} }
}; };
toggleFilter = (url, data) => { toggleFilter = (url: any, data: any) => {
this.props.toggleFilterStatus(url, data); this.props.toggleFilterStatus(url, data);
}; };
@ -75,8 +88,11 @@ class DnsBlocklist extends Component {
render() { render() {
const { const {
t, t,
toggleFilteringModal, toggleFilteringModal,
addFilter, addFilter,
filtering: { filtering: {
filters, filters,
isModalOpen, isModalOpen,
@ -91,18 +107,17 @@ class DnsBlocklist extends Component {
}, },
} = this.props; } = this.props;
const currentFilterData = getCurrentFilter(modalFilterUrl, filters); const currentFilterData = getCurrentFilter(modalFilterUrl, filters);
const loading = processingConfigFilter const loading =
|| processingFilters processingConfigFilter ||
|| processingAddFilter processingFilters ||
|| processingRemoveFilter processingAddFilter ||
|| processingRefreshFilters; processingRemoveFilter ||
processingRefreshFilters;
return ( return (
<> <>
<PageTitle <PageTitle title={t('dns_blocklists')} subtitle={t('dns_blocklists_desc')} />
title={t('dns_blocklists')}
subtitle={t('dns_blocklists_desc')}
/>
<div className="content"> <div className="content">
<div className="row"> <div className="row">
<div className="col-md-12"> <div className="col-md-12">
@ -115,6 +130,7 @@ class DnsBlocklist extends Component {
handleDelete={this.handleDelete} handleDelete={this.handleDelete}
toggleFilter={this.toggleFilter} toggleFilter={this.toggleFilter}
/> />
<Actions <Actions
handleAdd={this.openSelectTypeModal} handleAdd={this.openSelectTypeModal}
handleRefresh={this.handleRefresh} handleRefresh={this.handleRefresh}
@ -124,6 +140,7 @@ class DnsBlocklist extends Component {
</div> </div>
</div> </div>
</div> </div>
<Modal <Modal
filtersCatalog={filtersCatalog} filtersCatalog={filtersCatalog}
filters={filters} filters={filters}
@ -142,17 +159,4 @@ class DnsBlocklist extends Component {
} }
} }
DnsBlocklist.propTypes = {
getFilteringStatus: PropTypes.func.isRequired,
filtering: PropTypes.object.isRequired,
removeFilter: PropTypes.func.isRequired,
toggleFilterStatus: PropTypes.func.isRequired,
addFilter: PropTypes.func.isRequired,
toggleFilteringModal: PropTypes.func.isRequired,
handleRulesChange: PropTypes.func.isRequired,
refreshFilters: PropTypes.func.isRequired,
editFilter: PropTypes.func.isRequired,
t: PropTypes.func.isRequired,
};
export default withTranslation()(DnsBlocklist); export default withTranslation()(DnsBlocklist);

View File

@ -7,31 +7,37 @@ const Examples = () => (
<Trans>examples_title</Trans>: <Trans>examples_title</Trans>:
<ol className="leading-loose"> <ol className="leading-loose">
<li> <li>
<code>||example.org^</code>: <code>||example.org^</code>:<Trans>example_meaning_filter_block</Trans>
<Trans>example_meaning_filter_block</Trans>
</li> </li>
<li> <li>
<code> @@||example.org^</code>: <code> @@||example.org^</code>:<Trans>example_meaning_filter_whitelist</Trans>
<Trans>example_meaning_filter_whitelist</Trans>
</li> </li>
<li> <li>
<code>127.0.0.1 example.org</code>: <code>127.0.0.1 example.org</code>:<Trans>example_meaning_host_block</Trans>
<Trans>example_meaning_host_block</Trans>
</li> </li>
<li> <li>
<code><Trans>example_comment</Trans></code>: <code>
<Trans>example_comment_meaning</Trans> <Trans>example_comment</Trans>
</code>
:<Trans>example_comment_meaning</Trans>
</li> </li>
<li> <li>
<code><Trans>example_comment_hash</Trans></code>: <code>
<Trans>example_comment_meaning</Trans> <Trans>example_comment_hash</Trans>
</code>
:<Trans>example_comment_meaning</Trans>
</li> </li>
<li> <li>
<code>/REGEX/</code>: <code>/REGEX/</code>:<Trans>example_regex_meaning</Trans>
<Trans>example_regex_meaning</Trans>
</li> </li>
</ol> </ol>
</div> </div>
<p className="mt-1"> <p className="mt-1">
<Trans <Trans
components={[ components={[
@ -39,12 +45,10 @@ const Examples = () => (
href="https://link.adtidy.org/forward.html?action=dns_kb_filtering_syntax&from=ui&app=home" href="https://link.adtidy.org/forward.html?action=dns_kb_filtering_syntax&from=ui&app=home"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
key="0" key="0">
>
link link
</a>, </a>,
]} ]}>
>
filtering_rules_learn_more filtering_rules_learn_more
</Trans> </Trans>
</p> </p>

View File

@ -1,191 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Field, reduxForm } from 'redux-form';
import { withTranslation } from 'react-i18next';
import flow from 'lodash/flow';
import classNames from 'classnames';
import { validatePath, validateRequiredValue } from '../../helpers/validators';
import { CheckboxField, renderInputField } from '../../helpers/form';
import { MODAL_OPEN_TIMEOUT, MODAL_TYPE, FORM_NAME } from '../../helpers/constants';
import filtersCatalog from '../../helpers/filters/filters';
const getIconsData = (homepage, source) => ([
{
iconName: 'dashboard',
href: homepage,
className: 'ml-1',
},
{
iconName: 'info',
href: source,
},
]);
const renderIcons = (iconsData) => iconsData.map(({
iconName,
href,
className = '',
}) => <a key={iconName} href={href} target="_blank" rel="noopener noreferrer"
className={classNames('d-flex align-items-center', className)}
>
<svg className="icon icon--15 mr-1 icon--gray">
<use xlinkHref={`#${iconName}`} />
</svg>
</a>);
const renderCheckboxField = (
props,
) => <CheckboxField
{...props}
input={{
...props.input,
checked: props.disabled || props.input.checked,
}}
/>;
renderCheckboxField.propTypes = {
// https://redux-form.com/8.3.0/docs/api/field.md/#props
input: PropTypes.object.isRequired,
disabled: PropTypes.bool.isRequired,
};
const renderFilters = ({ categories, filters }, selectedSources, t) => Object.keys(categories)
.map((categoryId) => {
const category = categories[categoryId];
const categoryFilters = [];
Object.keys(filters)
.sort()
.forEach((key) => {
const filter = filters[key];
filter.id = key;
if (filter.categoryId === categoryId) {
categoryFilters.push(filter);
}
});
return <div key={category.name} className="modal-body__item">
<h6 className="font-weight-bold mb-1">{t(category.name)}</h6>
<p className="mb-3">{t(category.description)}</p>
{categoryFilters.map((filter) => {
const { homepage, source, name } = filter;
const isSelected = Object.prototype.hasOwnProperty.call(selectedSources, source);
const iconsData = getIconsData(homepage, source);
return <div key={name} className="d-flex align-items-center pb-1">
<Field
name={filter.id}
type="checkbox"
component={renderCheckboxField}
placeholder={t(name)}
disabled={isSelected}
/>
{renderIcons(iconsData)}
</div>;
})}
</div>;
});
const Form = (props) => {
const {
t,
closeModal,
handleSubmit,
processingAddFilter,
processingConfigFilter,
whitelist,
modalType,
toggleFilteringModal,
selectedSources,
} = props;
const openModal = (modalType, timeout = MODAL_OPEN_TIMEOUT) => {
toggleFilteringModal();
setTimeout(() => toggleFilteringModal({ type: modalType }), timeout);
};
const openFilteringListModal = () => openModal(MODAL_TYPE.CHOOSE_FILTERING_LIST);
const openAddFiltersModal = () => openModal(MODAL_TYPE.ADD_FILTERS);
return <form onSubmit={handleSubmit}>
<div className="modal-body modal-body--filters">
{modalType === MODAL_TYPE.SELECT_MODAL_TYPE
&& <div className="d-flex justify-content-around">
<button onClick={openFilteringListModal}
className="btn btn-success btn-standard mr-2 btn-large">
{t('choose_from_list')}
</button>
<button onClick={openAddFiltersModal} className="btn btn-primary btn-standard">
{t('add_custom_list')}
</button>
</div>}
{modalType === MODAL_TYPE.CHOOSE_FILTERING_LIST
&& renderFilters(filtersCatalog, selectedSources, t)}
{modalType !== MODAL_TYPE.CHOOSE_FILTERING_LIST
&& modalType !== MODAL_TYPE.SELECT_MODAL_TYPE
&& <>
<div className="form__group">
<Field
id="name"
name="name"
type="text"
component={renderInputField}
className="form-control"
placeholder={t('enter_name_hint')}
normalizeOnBlur={(data) => data.trim()}
/>
</div>
<div className="form__group">
<Field
id="url"
name="url"
type="text"
component={renderInputField}
className="form-control"
placeholder={t('enter_url_or_path_hint')}
validate={[validateRequiredValue, validatePath]}
normalizeOnBlur={(data) => data.trim()}
/>
</div>
<div className="form__description">
{whitelist ? t('enter_valid_allowlist') : t('enter_valid_blocklist')}
</div>
</>}
</div>
<div className="modal-footer">
<button
type="button"
className="btn btn-secondary"
onClick={closeModal}
>
{t('cancel_btn')}
</button>
{modalType !== MODAL_TYPE.SELECT_MODAL_TYPE && <button
type="submit"
className="btn btn-success"
disabled={processingAddFilter || processingConfigFilter}
>
{t('save_btn')}
</button>}
</div>
</form>;
};
Form.propTypes = {
t: PropTypes.func.isRequired,
closeModal: PropTypes.func.isRequired,
handleSubmit: PropTypes.func.isRequired,
processingAddFilter: PropTypes.bool.isRequired,
processingConfigFilter: PropTypes.bool.isRequired,
whitelist: PropTypes.bool,
modalType: PropTypes.string.isRequired,
toggleFilteringModal: PropTypes.func.isRequired,
selectedSources: PropTypes.object,
};
export default flow([
withTranslation(),
reduxForm({ form: FORM_NAME.FILTER }),
])(Form);

View File

@ -0,0 +1,208 @@
import React from 'react';
import { Field, reduxForm } from 'redux-form';
import { withTranslation } from 'react-i18next';
import flow from 'lodash/flow';
import classNames from 'classnames';
import { validatePath, validateRequiredValue } from '../../helpers/validators';
import { CheckboxField, renderInputField } from '../../helpers/form';
import { MODAL_OPEN_TIMEOUT, MODAL_TYPE, FORM_NAME } from '../../helpers/constants';
import filtersCatalog from '../../helpers/filters/filters';
const getIconsData = (homepage: any, source: any) => [
{
iconName: 'dashboard',
href: homepage,
className: 'ml-1',
},
{
iconName: 'info',
href: source,
},
];
const renderIcons = (iconsData: any) =>
iconsData.map(({ iconName, href, className = '' }: any) => (
<a
key={iconName}
href={href}
target="_blank"
rel="noopener noreferrer"
className={classNames('d-flex align-items-center', className)}>
<svg className="icon icon--15 mr-1 icon--gray">
<use xlinkHref={`#${iconName}`} />
</svg>
</a>
));
interface renderCheckboxFieldProps {
// https://redux-form.com/8.3.0/docs/api/field.md/#props
input: {
name: string;
value: string;
checked: boolean;
onChange: (...args: unknown[]) => unknown;
};
disabled: boolean;
}
const renderCheckboxField = (props: renderCheckboxFieldProps) => (
<CheckboxField
{...props}
meta={{ touched: false, error: null }}
input={{
...props.input,
checked: props.disabled || props.input.checked,
}}
/>
);
const renderFilters = ({ categories, filters }: any, selectedSources: any, t: any) =>
Object.keys(categories).map((categoryId) => {
const category = categories[categoryId];
const categoryFilters: any = [];
Object.keys(filters)
.sort()
.forEach((key) => {
const filter = filters[key];
filter.id = key;
if (filter.categoryId === categoryId) {
categoryFilters.push(filter);
}
});
return (
<div key={category.name} className="modal-body__item">
<h6 className="font-weight-bold mb-1">{t(category.name)}</h6>
<p className="mb-3">{t(category.description)}</p>
{categoryFilters.map((filter) => {
const { homepage, source, name } = filter;
const isSelected = Object.prototype.hasOwnProperty.call(selectedSources, source);
const iconsData = getIconsData(homepage, source);
return (
<div key={name} className="d-flex align-items-center pb-1">
<Field
name={filter.id}
type="checkbox"
component={renderCheckboxField}
placeholder={t(name)}
disabled={isSelected}
/>
{renderIcons(iconsData)}
</div>
);
})}
</div>
);
});
interface FormProps {
t: (...args: unknown[]) => string;
closeModal: (...args: unknown[]) => unknown;
handleSubmit: (...args: unknown[]) => string;
processingAddFilter: boolean;
processingConfigFilter: boolean;
whitelist?: boolean;
modalType: string;
toggleFilteringModal: (...args: unknown[]) => unknown;
selectedSources?: object;
}
const Form = (props: FormProps) => {
const {
t,
closeModal,
handleSubmit,
processingAddFilter,
processingConfigFilter,
whitelist,
modalType,
toggleFilteringModal,
selectedSources,
} = props;
const openModal = (modalType: any, timeout = MODAL_OPEN_TIMEOUT) => {
toggleFilteringModal();
setTimeout(() => toggleFilteringModal({ type: modalType }), timeout);
};
const openFilteringListModal = () => openModal(MODAL_TYPE.CHOOSE_FILTERING_LIST);
const openAddFiltersModal = () => openModal(MODAL_TYPE.ADD_FILTERS);
return (
<form onSubmit={handleSubmit}>
<div className="modal-body modal-body--filters">
{modalType === MODAL_TYPE.SELECT_MODAL_TYPE && (
<div className="d-flex justify-content-around">
<button
onClick={openFilteringListModal}
className="btn btn-success btn-standard mr-2 btn-large">
{t('choose_from_list')}
</button>
<button onClick={openAddFiltersModal} className="btn btn-primary btn-standard">
{t('add_custom_list')}
</button>
</div>
)}
{modalType === MODAL_TYPE.CHOOSE_FILTERING_LIST && renderFilters(filtersCatalog, selectedSources, t)}
{modalType !== MODAL_TYPE.CHOOSE_FILTERING_LIST && modalType !== MODAL_TYPE.SELECT_MODAL_TYPE && (
<>
<div className="form__group">
<Field
id="name"
name="name"
type="text"
component={renderInputField}
className="form-control"
placeholder={t('enter_name_hint')}
normalizeOnBlur={(data: any) => data.trim()}
/>
</div>
<div className="form__group">
<Field
id="url"
name="url"
type="text"
component={renderInputField}
className="form-control"
placeholder={t('enter_url_or_path_hint')}
validate={[validateRequiredValue, validatePath]}
normalizeOnBlur={(data: any) => data.trim()}
/>
</div>
<div className="form__description">
{whitelist ? t('enter_valid_allowlist') : t('enter_valid_blocklist')}
</div>
</>
)}
</div>
<div className="modal-footer">
<button type="button" className="btn btn-secondary" onClick={closeModal}>
{t('cancel_btn')}
</button>
{modalType !== MODAL_TYPE.SELECT_MODAL_TYPE && (
<button
type="submit"
className="btn btn-success"
disabled={processingAddFilter || processingConfigFilter}>
{t('save_btn')}
</button>
)}
</div>
</form>
);
};
export default flow([withTranslation(), reduxForm({ form: FORM_NAME.FILTER })])(Form);

View File

@ -1,11 +1,13 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types';
import ReactModal from 'react-modal'; import ReactModal from 'react-modal';
import { withTranslation } from 'react-i18next'; import { withTranslation } from 'react-i18next';
import { MODAL_TYPE } from '../../helpers/constants'; import { MODAL_TYPE } from '../../helpers/constants';
import Form from './Form'; import Form from './Form';
import '../ui/Modal.css'; import '../ui/Modal.css';
import { getMap } from '../../helpers/helpers'; import { getMap } from '../../helpers/helpers';
ReactModal.setAppElement('#root'); ReactModal.setAppElement('#root');
@ -25,7 +27,7 @@ const MODAL_TYPE_TO_TITLE_TYPE_MAP = {
* @returns {'new_allowlist' | 'edit_allowlist' | 'choose_allowlist' | * @returns {'new_allowlist' | 'edit_allowlist' | 'choose_allowlist' |
* 'new_blocklist' | 'edit_blocklist' | 'choose_blocklist' | null} * 'new_blocklist' | 'edit_blocklist' | 'choose_blocklist' | null}
*/ */
const getTitle = (modalType, whitelist) => { const getTitle = (modalType: any, whitelist: any) => {
const titleType = MODAL_TYPE_TO_TITLE_TYPE_MAP[modalType]; const titleType = MODAL_TYPE_TO_TITLE_TYPE_MAP[modalType];
if (!titleType) { if (!titleType) {
return null; return null;
@ -33,19 +35,39 @@ const getTitle = (modalType, whitelist) => {
return `${titleType}_${whitelist ? 'allowlist' : 'blocklist'}`; return `${titleType}_${whitelist ? 'allowlist' : 'blocklist'}`;
}; };
const getSelectedValues = (filters, catalogSourcesToIdMap) => filters.reduce((acc, { url }) => { const getSelectedValues = (filters: any, catalogSourcesToIdMap: any) =>
filters.reduce(
(acc: any, { url }: any) => {
if (Object.prototype.hasOwnProperty.call(catalogSourcesToIdMap, url)) { if (Object.prototype.hasOwnProperty.call(catalogSourcesToIdMap, url)) {
const fieldId = `filter${catalogSourcesToIdMap[url]}`; const fieldId = `filter${catalogSourcesToIdMap[url]}`;
acc.selectedFilterIds[fieldId] = true; acc.selectedFilterIds[fieldId] = true;
acc.selectedSources[url] = true; acc.selectedSources[url] = true;
} }
return acc; return acc;
}, { },
{
selectedFilterIds: {}, selectedFilterIds: {},
selectedSources: {}, selectedSources: {},
}); },
);
class Modal extends Component { interface ModalProps {
toggleFilteringModal: (...args: unknown[]) => unknown;
isOpen: boolean;
addFilter: (...args: unknown[]) => unknown;
isFilterAdded: boolean;
processingAddFilter: boolean;
processingConfigFilter: boolean;
handleSubmit: (values: any) => void;
modalType: string;
currentFilterData: object;
t: (...args: unknown[]) => string;
whitelist?: boolean;
filters: unknown[];
filtersCatalog?: any;
}
class Modal extends Component<ModalProps> {
closeModal = () => { closeModal = () => {
this.props.toggleFilteringModal(); this.props.toggleFilteringModal();
}; };
@ -53,15 +75,25 @@ class Modal extends Component {
render() { render() {
const { const {
isOpen, isOpen,
processingAddFilter, processingAddFilter,
processingConfigFilter, processingConfigFilter,
handleSubmit, handleSubmit,
modalType, modalType,
currentFilterData, currentFilterData,
whitelist, whitelist,
toggleFilteringModal, toggleFilteringModal,
filters, filters,
t, t,
filtersCatalog, filtersCatalog,
} = this.props; } = this.props;
@ -90,15 +122,16 @@ class Modal extends Component {
className="Modal__Bootstrap modal-dialog modal-dialog-centered" className="Modal__Bootstrap modal-dialog modal-dialog-centered"
closeTimeoutMS={0} closeTimeoutMS={0}
isOpen={isOpen} isOpen={isOpen}
onRequestClose={this.closeModal} onRequestClose={this.closeModal}>
>
<div className="modal-content"> <div className="modal-content">
<div className="modal-header"> <div className="modal-header">
{title && <h4 className="modal-title">{title}</h4>} {title && <h4 className="modal-title">{title}</h4>}
<button type="button" className="close" onClick={this.closeModal}> <button type="button" className="close" onClick={this.closeModal}>
<span className="sr-only">Close</span> <span className="sr-only">Close</span>
</button> </button>
</div> </div>
<Form <Form
selectedSources={selectedSources} selectedSources={selectedSources}
initialValues={initialValues} initialValues={initialValues}
@ -116,20 +149,4 @@ class Modal extends Component {
} }
} }
Modal.propTypes = {
toggleFilteringModal: PropTypes.func.isRequired,
isOpen: PropTypes.bool.isRequired,
addFilter: PropTypes.func.isRequired,
isFilterAdded: PropTypes.bool.isRequired,
processingAddFilter: PropTypes.bool.isRequired,
processingConfigFilter: PropTypes.bool.isRequired,
handleSubmit: PropTypes.func.isRequired,
modalType: PropTypes.string.isRequired,
currentFilterData: PropTypes.object.isRequired,
t: PropTypes.func.isRequired,
whitelist: PropTypes.bool,
filters: PropTypes.array.isRequired,
filtersCatalog: PropTypes.object,
};
export default withTranslation()(Modal); export default withTranslation()(Modal);

View File

@ -1,22 +1,26 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import { Field, reduxForm } from 'redux-form'; import { Field, reduxForm } from 'redux-form';
import { Trans, withTranslation } from 'react-i18next'; import { Trans, withTranslation } from 'react-i18next';
import flow from 'lodash/flow'; import flow from 'lodash/flow';
import { renderInputField } from '../../../helpers/form'; import { renderInputField } from '../../../helpers/form';
import { validateAnswer, validateDomain, validateRequiredValue } from '../../../helpers/validators'; import { validateAnswer, validateDomain, validateRequiredValue } from '../../../helpers/validators';
import { FORM_NAME } from '../../../helpers/constants'; import { FORM_NAME } from '../../../helpers/constants';
const Form = (props) => { interface FormProps {
const { pristine: boolean;
t, handleSubmit: (...args: unknown[]) => string;
handleSubmit, reset: (...args: unknown[]) => string;
reset, toggleRewritesModal: (...args: unknown[]) => unknown;
pristine, submitting: boolean;
submitting, processingAdd: boolean;
toggleRewritesModal, t: (...args: unknown[]) => string;
processingAdd, initialValues?: object;
} = props; }
const Form = (props: FormProps) => {
const { t, handleSubmit, reset, pristine, submitting, toggleRewritesModal, processingAdd } = props;
return ( return (
<form onSubmit={handleSubmit}> <form onSubmit={handleSubmit}>
@ -35,22 +39,19 @@ const Form = (props) => {
validate={[validateRequiredValue, validateDomain]} validate={[validateRequiredValue, validateDomain]}
/> />
</div> </div>
<Trans>examples_title</Trans>: <Trans>examples_title</Trans>:
<ol className="leading-loose"> <ol className="leading-loose">
<li> <li>
<code>example.org</code> <Trans>example_rewrite_domain</Trans> <code>example.org</code> <Trans>example_rewrite_domain</Trans>
</li> </li>
<li> <li>
<code>*.example.org</code> &nbsp; <code>*.example.org</code> &nbsp;
<span> <span>
<Trans components={[<code key="0">text</code>]}> <Trans components={[<code key="0">text</code>]}>example_rewrite_wildcard</Trans>
example_rewrite_wildcard
</Trans>
</span> </span>
</li> </li>
</ol> </ol>
<div className="form__group"> <div className="form__group">
<Field <Field
id="answer" id="answer"
@ -63,14 +64,15 @@ const Form = (props) => {
/> />
</div> </div>
</div> </div>
<ul>{['rewrite_ip_address',
'rewrite_domain_name', <ul>
'rewrite_A', {['rewrite_ip_address', 'rewrite_domain_name', 'rewrite_A', 'rewrite_AAAA'].map((str) => (
'rewrite_AAAA'] <li key={str}>
.map((str) => <li key={str}>
<Trans components={[<code key="0">text</code>]}>{str}</Trans> <Trans components={[<code key="0">text</code>]}>{str}</Trans>
</li>) </li>
}</ul> ))}
</ul>
<div className="modal-footer"> <div className="modal-footer">
<div className="btn-list"> <div className="btn-list">
<button <button
@ -80,15 +82,14 @@ const Form = (props) => {
onClick={() => { onClick={() => {
reset(); reset();
toggleRewritesModal(); toggleRewritesModal();
}} }}>
>
<Trans>cancel_btn</Trans> <Trans>cancel_btn</Trans>
</button> </button>
<button <button
type="submit" type="submit"
className="btn btn-success btn-standard" className="btn btn-success btn-standard"
disabled={submitting || pristine || processingAdd} disabled={submitting || pristine || processingAdd}>
>
<Trans>save_btn</Trans> <Trans>save_btn</Trans>
</button> </button>
</div> </div>
@ -97,17 +98,6 @@ const Form = (props) => {
); );
}; };
Form.propTypes = {
pristine: PropTypes.bool.isRequired,
handleSubmit: PropTypes.func.isRequired,
reset: PropTypes.func.isRequired,
toggleRewritesModal: PropTypes.func.isRequired,
submitting: PropTypes.bool.isRequired,
processingAdd: PropTypes.bool.isRequired,
t: PropTypes.func.isRequired,
initialValues: PropTypes.object,
};
export default flow([ export default flow([
withTranslation(), withTranslation(),
reduxForm({ reduxForm({

View File

@ -1,12 +1,23 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import { Trans, withTranslation } from 'react-i18next'; import { Trans, withTranslation } from 'react-i18next';
import ReactModal from 'react-modal'; import ReactModal from 'react-modal';
import { MODAL_TYPE } from '../../../helpers/constants'; import { MODAL_TYPE } from '../../../helpers/constants';
import Form from './Form'; import Form from './Form';
const Modal = (props) => { interface ModalProps {
isModalOpen: boolean;
handleSubmit: (values: any) => void;
toggleRewritesModal: (...args: unknown[]) => unknown;
processingAdd: boolean;
processingDelete: boolean;
modalType: string;
currentRewrite?: object;
}
const Modal = (props: ModalProps) => {
const { const {
isModalOpen, isModalOpen,
handleSubmit, handleSubmit,
@ -22,8 +33,7 @@ const Modal = (props) => {
className="Modal__Bootstrap modal-dialog modal-dialog-centered" className="Modal__Bootstrap modal-dialog modal-dialog-centered"
closeTimeoutMS={0} closeTimeoutMS={0}
isOpen={isModalOpen} isOpen={isModalOpen}
onRequestClose={() => toggleRewritesModal()} onRequestClose={() => toggleRewritesModal()}>
>
<div className="modal-content"> <div className="modal-content">
<div className="modal-header"> <div className="modal-header">
<h4 className="modal-title"> <h4 className="modal-title">
@ -33,10 +43,12 @@ const Modal = (props) => {
<Trans>rewrite_add</Trans> <Trans>rewrite_add</Trans>
)} )}
</h4> </h4>
<button type="button" className="close" onClick={() => toggleRewritesModal()}> <button type="button" className="close" onClick={() => toggleRewritesModal()}>
<span className="sr-only">Close</span> <span className="sr-only">Close</span>
</button> </button>
</div> </div>
<Form <Form
initialValues={{ ...currentRewrite }} initialValues={{ ...currentRewrite }}
onSubmit={handleSubmit} onSubmit={handleSubmit}
@ -49,14 +61,4 @@ const Modal = (props) => {
); );
}; };
Modal.propTypes = {
isModalOpen: PropTypes.bool.isRequired,
handleSubmit: PropTypes.func.isRequired,
toggleRewritesModal: PropTypes.func.isRequired,
processingAdd: PropTypes.bool.isRequired,
processingDelete: PropTypes.bool.isRequired,
modalType: PropTypes.string.isRequired,
currentRewrite: PropTypes.object,
};
export default withTranslation()(Modal); export default withTranslation()(Modal);

View File

@ -1,13 +1,26 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types';
// @ts-expect-error FIXME: update react-table
import ReactTable from 'react-table'; import ReactTable from 'react-table';
import { withTranslation } from 'react-i18next'; import { withTranslation } from 'react-i18next';
import { sortIp } from '../../../helpers/helpers'; import { sortIp } from '../../../helpers/helpers';
import { MODAL_TYPE, TABLES_MIN_ROWS } from '../../../helpers/constants'; import { MODAL_TYPE, TABLES_MIN_ROWS } from '../../../helpers/constants';
import { LocalStorageHelper, LOCAL_STORAGE_KEYS } from '../../../helpers/localStorageHelper'; import { LocalStorageHelper, LOCAL_STORAGE_KEYS } from '../../../helpers/localStorageHelper';
class Table extends Component { interface TableProps {
cellWrap = ({ value }) => ( t: (...args: unknown[]) => string;
list: unknown[];
processing: boolean;
processingAdd: boolean;
processingDelete: boolean;
processingUpdate: boolean;
handleDelete: (...args: unknown[]) => unknown;
toggleRewritesModal: (...args: unknown[]) => unknown;
}
class Table extends Component<TableProps> {
cellWrap = ({ value }: any) => (
<div className="logs__row o-hidden"> <div className="logs__row o-hidden">
<span className="logs__text" title={value}> <span className="logs__text" title={value}>
{value} {value}
@ -33,7 +46,7 @@ class Table extends Component {
maxWidth: 100, maxWidth: 100,
sortable: false, sortable: false,
resizable: false, resizable: false,
Cell: (value) => { Cell: (value: any) => {
const currentRewrite = { const currentRewrite = {
answer: value.row.answer, answer: value.row.answer,
domain: value.row.domain, domain: value.row.domain,
@ -51,8 +64,7 @@ class Table extends Component {
}); });
}} }}
disabled={this.props.processingUpdate} disabled={this.props.processingUpdate}
title={this.props.t('edit_table_action')} title={this.props.t('edit_table_action')}>
>
<svg className="icons icon12"> <svg className="icons icon12">
<use xlinkHref="#edit" /> <use xlinkHref="#edit" />
</svg> </svg>
@ -62,8 +74,7 @@ class Table extends Component {
type="button" type="button"
className="btn btn-icon btn-outline-secondary btn-sm" className="btn btn-icon btn-outline-secondary btn-sm"
onClick={() => this.props.handleDelete(currentRewrite)} onClick={() => this.props.handleDelete(currentRewrite)}
title={this.props.t('delete_table_action')} title={this.props.t('delete_table_action')}>
>
<svg className="icons"> <svg className="icons">
<use xlinkHref="#delete" /> <use xlinkHref="#delete" />
</svg> </svg>
@ -75,9 +86,7 @@ class Table extends Component {
]; ];
render() { render() {
const { const { t, list, processing, processingAdd, processingDelete } = this.props;
t, list, processing, processingAdd, processingDelete,
} = this.props;
return ( return (
<ReactTable <ReactTable
@ -87,7 +96,9 @@ class Table extends Component {
className="-striped -highlight card-table-overflow" className="-striped -highlight card-table-overflow"
showPagination showPagination
defaultPageSize={LocalStorageHelper.getItem(LOCAL_STORAGE_KEYS.REWRITES_PAGE_SIZE) || 10} defaultPageSize={LocalStorageHelper.getItem(LOCAL_STORAGE_KEYS.REWRITES_PAGE_SIZE) || 10}
onPageSizeChange={(size) => LocalStorageHelper.setItem(LOCAL_STORAGE_KEYS.REWRITES_PAGE_SIZE, size)} onPageSizeChange={(size: any) =>
LocalStorageHelper.setItem(LOCAL_STORAGE_KEYS.REWRITES_PAGE_SIZE, size)
}
minRows={TABLES_MIN_ROWS} minRows={TABLES_MIN_ROWS}
ofText="/" ofText="/"
previousText={t('previous_btn')} previousText={t('previous_btn')}
@ -101,15 +112,4 @@ class Table extends Component {
} }
} }
Table.propTypes = {
t: PropTypes.func.isRequired,
list: PropTypes.array.isRequired,
processing: PropTypes.bool.isRequired,
processingAdd: PropTypes.bool.isRequired,
processingDelete: PropTypes.bool.isRequired,
processingUpdate: PropTypes.bool.isRequired,
handleDelete: PropTypes.func.isRequired,
toggleRewritesModal: PropTypes.func.isRequired,
};
export default withTranslation()(Table); export default withTranslation()(Table);

View File

@ -1,26 +1,39 @@
import React, { Component, Fragment } from 'react'; import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import { Trans, withTranslation } from 'react-i18next'; import { Trans, withTranslation } from 'react-i18next';
import Table from './Table'; import Table from './Table';
import Modal from './Modal'; import Modal from './Modal';
import Card from '../../ui/Card'; import Card from '../../ui/Card';
import PageTitle from '../../ui/PageTitle'; import PageTitle from '../../ui/PageTitle';
import { MODAL_TYPE } from '../../../helpers/constants'; import { MODAL_TYPE } from '../../../helpers/constants';
import { RewritesData } from '../../../initialState';
class Rewrites extends Component { interface RewritesProps {
t: (...args: unknown[]) => string;
getRewritesList: () => (dispatch: any) => void;
toggleRewritesModal: (...args: unknown[]) => unknown;
addRewrite: (...args: unknown[]) => unknown;
deleteRewrite: (...args: unknown[]) => unknown;
updateRewrite: (...args: unknown[]) => unknown;
rewrites: RewritesData;
}
class Rewrites extends Component<RewritesProps> {
componentDidMount() { componentDidMount() {
this.props.getRewritesList(); this.props.getRewritesList();
} }
handleDelete = (values) => { handleDelete = (values: any) => {
// eslint-disable-next-line no-alert // eslint-disable-next-line no-alert
if (window.confirm(this.props.t('rewrite_confirm_delete', { key: values.domain }))) { if (window.confirm(this.props.t('rewrite_confirm_delete', { key: values.domain }))) {
this.props.deleteRewrite(values); this.props.deleteRewrite(values);
} }
}; };
handleSubmit = (values) => { handleSubmit = (values: any) => {
const { modalType, currentRewrite } = this.props.rewrites; const { modalType, currentRewrite } = this.props.rewrites;
if (modalType === MODAL_TYPE.EDIT_REWRITE && currentRewrite) { if (modalType === MODAL_TYPE.EDIT_REWRITE && currentRewrite) {
@ -36,7 +49,9 @@ class Rewrites extends Component {
render() { render() {
const { const {
t, t,
rewrites, rewrites,
toggleRewritesModal, toggleRewritesModal,
} = this.props; } = this.props;
@ -53,14 +68,9 @@ class Rewrites extends Component {
return ( return (
<Fragment> <Fragment>
<PageTitle <PageTitle title={t('dns_rewrites')} subtitle={t('rewrite_desc')} />
title={t('dns_rewrites')}
subtitle={t('rewrite_desc')} <Card id="rewrites" bodyType="card-body box-body--settings">
/>
<Card
id="rewrites"
bodyType="card-body box-body--settings"
>
<Fragment> <Fragment>
<Table <Table
list={list} list={list}
@ -76,8 +86,7 @@ class Rewrites extends Component {
type="button" type="button"
className="btn btn-success btn-standard mt-3" className="btn btn-success btn-standard mt-3"
onClick={() => toggleRewritesModal({ type: MODAL_TYPE.ADD_REWRITE })} onClick={() => toggleRewritesModal({ type: MODAL_TYPE.ADD_REWRITE })}
disabled={processingAdd} disabled={processingAdd}>
>
<Trans>rewrite_add</Trans> <Trans>rewrite_add</Trans>
</button> </button>
@ -88,7 +97,6 @@ class Rewrites extends Component {
handleSubmit={this.handleSubmit} handleSubmit={this.handleSubmit}
processingAdd={processingAdd} processingAdd={processingAdd}
processingDelete={processingDelete} processingDelete={processingDelete}
processingUpdate={processingUpdate}
currentRewrite={currentRewrite} currentRewrite={currentRewrite}
/> />
</Fragment> </Fragment>
@ -98,14 +106,4 @@ class Rewrites extends Component {
} }
} }
Rewrites.propTypes = {
t: PropTypes.func.isRequired,
getRewritesList: PropTypes.func.isRequired,
toggleRewritesModal: PropTypes.func.isRequired,
addRewrite: PropTypes.func.isRequired,
deleteRewrite: PropTypes.func.isRequired,
updateRewrite: PropTypes.func.isRequired,
rewrites: PropTypes.object.isRequired,
};
export default withTranslation()(Rewrites); export default withTranslation()(Rewrites);

View File

@ -1,23 +1,27 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import { Field, reduxForm } from 'redux-form'; import { Field, reduxForm } from 'redux-form';
import { Trans, withTranslation } from 'react-i18next'; import { Trans, withTranslation } from 'react-i18next';
import flow from 'lodash/flow'; import flow from 'lodash/flow';
import { toggleAllServices } from '../../../helpers/helpers'; import { toggleAllServices } from '../../../helpers/helpers';
import { renderServiceField } from '../../../helpers/form'; import { renderServiceField } from '../../../helpers/form';
import { FORM_NAME } from '../../../helpers/constants'; import { FORM_NAME } from '../../../helpers/constants';
const Form = (props) => { interface FormProps {
const { blockedServices: unknown[];
blockedServices, pristine: boolean;
handleSubmit, handleSubmit: (...args: unknown[]) => string;
change, change: (...args: unknown[]) => unknown;
pristine, submitting: boolean;
submitting, processing: boolean;
processing, processingSet: boolean;
processingSet, t: (...args: unknown[]) => string;
} = props; }
const Form = (props: FormProps) => {
const { blockedServices, handleSubmit, change, pristine, submitting, processing, processingSet } = props;
return ( return (
<form onSubmit={handleSubmit}> <form onSubmit={handleSubmit}>
@ -28,24 +32,24 @@ const Form = (props) => {
type="button" type="button"
className="btn btn-secondary btn-block" className="btn btn-secondary btn-block"
disabled={processing || processingSet} disabled={processing || processingSet}
onClick={() => toggleAllServices(blockedServices, change, true)} onClick={() => toggleAllServices(blockedServices, change, true)}>
>
<Trans>block_all</Trans> <Trans>block_all</Trans>
</button> </button>
</div> </div>
<div className="col-6"> <div className="col-6">
<button <button
type="button" type="button"
className="btn btn-secondary btn-block" className="btn btn-secondary btn-block"
disabled={processing || processingSet} disabled={processing || processingSet}
onClick={() => toggleAllServices(blockedServices, change, false)} onClick={() => toggleAllServices(blockedServices, change, false)}>
>
<Trans>unblock_all</Trans> <Trans>unblock_all</Trans>
</button> </button>
</div> </div>
</div> </div>
<div className="services"> <div className="services">
{blockedServices.map((service) => ( {blockedServices.map((service: any) => (
<Field <Field
key={service.id} key={service.id}
icon={service.icon_svg} icon={service.icon_svg}
@ -63,8 +67,7 @@ const Form = (props) => {
<button <button
type="submit" type="submit"
className="btn btn-success btn-standard btn-large" className="btn btn-success btn-standard btn-large"
disabled={submitting || pristine || processing || processingSet} disabled={submitting || pristine || processing || processingSet}>
>
<Trans>save_btn</Trans> <Trans>save_btn</Trans>
</button> </button>
</div> </div>
@ -72,17 +75,6 @@ const Form = (props) => {
); );
}; };
Form.propTypes = {
blockedServices: PropTypes.array.isRequired,
pristine: PropTypes.bool.isRequired,
handleSubmit: PropTypes.func.isRequired,
change: PropTypes.func.isRequired,
submitting: PropTypes.bool.isRequired,
processing: PropTypes.bool.isRequired,
processingSet: PropTypes.bool.isRequired,
t: PropTypes.func.isRequired,
};
export default flow([ export default flow([
withTranslation(), withTranslation(),
reduxForm({ reduxForm({

View File

@ -1,10 +1,12 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import ReactModal from 'react-modal'; import ReactModal from 'react-modal';
import { Timezone } from './Timezone'; import { Timezone } from './Timezone';
import { TimeSelect } from './TimeSelect'; import { TimeSelect } from './TimeSelect';
import { TimePeriod } from './TimePeriod'; import { TimePeriod } from './TimePeriod';
import { getFullDayName, getShortDayName } from './helpers'; import { getFullDayName, getShortDayName } from './helpers';
import { LOCAL_TIMEZONE_VALUE } from '../../../../helpers/constants'; import { LOCAL_TIMEZONE_VALUE } from '../../../../helpers/constants';
@ -14,21 +16,26 @@ export const DAYS_OF_WEEK = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'];
const INITIAL_START_TIME_MS = 0; const INITIAL_START_TIME_MS = 0;
const INITIAL_END_TIME_MS = 86340000; const INITIAL_END_TIME_MS = 86340000;
export const Modal = ({ interface ModalProps {
isOpen, schedule: {
currentDay, time_zone: string;
schedule, };
onClose, currentDay?: string;
onSubmit, isOpen: boolean;
}) => { onClose: (...args: unknown[]) => unknown;
onSubmit: (values: any) => void;
}
export const Modal = ({ isOpen, currentDay, schedule, onClose, onSubmit }: ModalProps) => {
const [t] = useTranslation(); const [t] = useTranslation();
const intialTimezone = schedule.time_zone === LOCAL_TIMEZONE_VALUE const intialTimezone =
schedule.time_zone === LOCAL_TIMEZONE_VALUE
? Intl.DateTimeFormat().resolvedOptions().timeZone ? Intl.DateTimeFormat().resolvedOptions().timeZone
: schedule.time_zone; : schedule.time_zone;
const [timezone, setTimezone] = useState(intialTimezone); const [timezone, setTimezone] = useState(intialTimezone);
const [days, setDays] = useState(new Set()); const [days, setDays] = useState<Set<string>>(new Set());
const [startTime, setStartTime] = useState(INITIAL_START_TIME_MS); const [startTime, setStartTime] = useState(INITIAL_START_TIME_MS);
const [endTime, setEndTime] = useState(INITIAL_END_TIME_MS); const [endTime, setEndTime] = useState(INITIAL_END_TIME_MS);
@ -53,7 +60,7 @@ export const Modal = ({
} }
}, [startTime, endTime]); }, [startTime, endTime]);
const addDays = (day) => { const addDays = (day: any) => {
const newDays = new Set(days); const newDays = new Set(days);
if (newDays.has(day)) { if (newDays.has(day)) {
@ -65,11 +72,11 @@ export const Modal = ({
setDays(newDays); setDays(newDays);
}; };
const activeDay = (day) => { const activeDay = (day: any) => {
return days.has(day); return days.has(day);
}; };
const onFormSubmit = (e) => { const onFormSubmit = (e: any) => {
e.preventDefault(); e.preventDefault();
const newSchedule = schedule; const newSchedule = schedule;
@ -93,23 +100,19 @@ export const Modal = ({
className="Modal__Bootstrap modal-dialog modal-dialog-centered modal-dialog--schedule" className="Modal__Bootstrap modal-dialog modal-dialog-centered modal-dialog--schedule"
closeTimeoutMS={0} closeTimeoutMS={0}
isOpen={isOpen} isOpen={isOpen}
onRequestClose={onClose} onRequestClose={onClose}>
>
<div className="modal-content"> <div className="modal-content">
<div className="modal-header"> <div className="modal-header">
<h4 className="modal-title"> <h4 className="modal-title">{currentDay ? t('schedule_edit') : t('schedule_new')}</h4>
{currentDay ? t('schedule_edit') : t('schedule_new')}
</h4>
<button type="button" className="close" onClick={onClose}> <button type="button" className="close" onClick={onClose}>
<span className="sr-only">Close</span> <span className="sr-only">Close</span>
</button> </button>
</div> </div>
<form onSubmit={onFormSubmit}> <form onSubmit={onFormSubmit}>
<div className="modal-body"> <div className="modal-body">
<Timezone <Timezone timezone={timezone} setTimezone={setTimezone} />
timezone={timezone}
setTimezone={setTimezone}
/>
<div className="schedule__days"> <div className="schedule__days">
{DAYS_OF_WEEK.map((day) => ( {DAYS_OF_WEEK.map((day) => (
@ -118,8 +121,7 @@ export const Modal = ({
key={day} key={day}
className="btn schedule__button-day" className="btn schedule__button-day"
data-active={activeDay(day)} data-active={activeDay(day)}
onClick={() => addDays(day)} onClick={() => addDays(day)}>
>
{getShortDayName(t, day)} {getShortDayName(t, day)}
</button> </button>
))} ))}
@ -127,69 +129,52 @@ export const Modal = ({
<div className="schedule__time-wrap"> <div className="schedule__time-wrap">
<div className="schedule__time-row"> <div className="schedule__time-row">
<TimeSelect <TimeSelect value={startTime} onChange={(v) => setStartTime(v)} />
value={startTime}
onChange={(v) => setStartTime(v)}
/>
<TimeSelect <TimeSelect value={endTime} onChange={(v) => setEndTime(v)} />
value={endTime}
onChange={(v) => setEndTime(v)}
/>
</div> </div>
{wrongPeriod && ( {wrongPeriod && <div className="schedule__error">{t('schedule_invalid_select')}</div>}
<div className="schedule__error">
{t('schedule_invalid_select')}
</div>
)}
</div> </div>
<div className="schedule__info"> <div className="schedule__info">
<div className="schedule__info-title"> <div className="schedule__info-title">{t('schedule_modal_time_off')}</div>
{t('schedule_modal_time_off')}
</div>
<div className="schedule__info-row"> <div className="schedule__info-row">
<svg className="icons schedule__info-icon"> <svg className="icons schedule__info-icon">
<use xlinkHref="#calendar" /> <use xlinkHref="#calendar" />
</svg> </svg>
{days.size ? ( {days.size ? (
Array.from(days).map((day) => getFullDayName(t, day)).join(', ') Array.from(days)
.map((day) => getFullDayName(t, day))
.join(', ')
) : ( ) : (
<span> <span></span>
</span>
)} )}
</div> </div>
<div className="schedule__info-row"> <div className="schedule__info-row">
<svg className="icons schedule__info-icon"> <svg className="icons schedule__info-icon">
<use xlinkHref="#watch" /> <use xlinkHref="#watch" />
</svg> </svg>
{wrongPeriod ? ( {wrongPeriod ? (
<span> <span></span>
</span>
) : ( ) : (
<TimePeriod <TimePeriod startTimeMs={startTime} endTimeMs={endTime} />
startTimeMs={startTime}
endTimeMs={endTime}
/>
)} )}
</div> </div>
</div> </div>
<div className="schedule__notice"> <div className="schedule__notice">{t('schedule_modal_description')}</div>
{t('schedule_modal_description')}
</div>
</div> </div>
<div className="modal-footer"> <div className="modal-footer">
<div className="btn-list"> <div className="btn-list">
<button <button
type="button" type="button"
className="btn btn-success btn-standard" className="btn btn-success btn-standard"
disabled={days.size === 0 || wrongPeriod} disabled={days.size === 0 || wrongPeriod}
onClick={onFormSubmit} onClick={onFormSubmit}>
>
{currentDay ? t('schedule_save') : t('schedule_add')} {currentDay ? t('schedule_save') : t('schedule_add')}
</button> </button>
</div> </div>
@ -199,11 +184,3 @@ export const Modal = ({
</ReactModal> </ReactModal>
); );
}; };
Modal.propTypes = {
schedule: PropTypes.object.isRequired,
currentDay: PropTypes.string,
isOpen: PropTypes.bool.isRequired,
onClose: PropTypes.func.isRequired,
onSubmit: PropTypes.func.isRequired,
};

View File

@ -1,25 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { getTimeFromMs } from './helpers';
export const TimePeriod = ({
startTimeMs,
endTimeMs,
}) => {
const startTime = getTimeFromMs(startTimeMs);
const endTime = getTimeFromMs(endTimeMs);
return (
<div className="schedule__time">
<time>{startTime.hours}:{startTime.minutes}</time>
&nbsp;&nbsp;
<time>{endTime.hours}:{endTime.minutes}</time>
</div>
);
};
TimePeriod.propTypes = {
startTimeMs: PropTypes.number.isRequired,
endTimeMs: PropTypes.number.isRequired,
};

View File

@ -0,0 +1,25 @@
import React from 'react';
import { getTimeFromMs } from './helpers';
interface TimePeriodProps {
startTimeMs: number;
endTimeMs: number;
}
export const TimePeriod = ({ startTimeMs, endTimeMs }: TimePeriodProps) => {
const startTime = getTimeFromMs(startTimeMs);
const endTime = getTimeFromMs(endTimeMs);
return (
<div className="schedule__time">
<time>
{startTime.hours}:{startTime.minutes}
</time>
&nbsp;&nbsp;
<time>
{endTime.hours}:{endTime.minutes}
</time>
</div>
);
};

View File

@ -1,37 +1,35 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { getTimeFromMs, convertTimeToMs } from './helpers'; import { getTimeFromMs, convertTimeToMs } from './helpers';
export const TimeSelect = ({ interface TimeSelectProps {
value, value: number;
onChange, onChange: (time: number) => void;
}) => { }
export const TimeSelect = ({ value, onChange }: TimeSelectProps) => {
const { hours: initialHours, minutes: initialMinutes } = getTimeFromMs(value); const { hours: initialHours, minutes: initialMinutes } = getTimeFromMs(value);
const [hours, setHours] = useState(initialHours); const [hours, setHours] = useState(initialHours);
const [minutes, setMinutes] = useState(initialMinutes); const [minutes, setMinutes] = useState(initialMinutes);
const hourOptions = Array.from({ length: 24 }, (_, i) => i.toString().padStart(2, '0')); const hourOptions = Array.from({ length: 24 }, (_, i) => i.toString().padStart(2, '0'));
const minuteOptions = Array.from({ length: 60 }, (_, i) => i.toString().padStart(2, '0')); const minuteOptions = Array.from({ length: 60 }, (_, i) => i.toString().padStart(2, '0'));
const onHourChange = (event) => { const onHourChange = (event: any) => {
setHours(event.target.value); setHours(event.target.value);
onChange(convertTimeToMs(event.target.value, minutes)); onChange(convertTimeToMs(event.target.value, minutes));
}; };
const onMinuteChange = (event) => { const onMinuteChange = (event: any) => {
setMinutes(event.target.value); setMinutes(event.target.value);
onChange(convertTimeToMs(hours, event.target.value)); onChange(convertTimeToMs(hours, event.target.value));
}; };
return ( return (
<div className="schedule__time-select"> <div className="schedule__time-select">
<select <select value={hours} onChange={onHourChange} className="form-control custom-select">
value={hours}
onChange={onHourChange}
className="form-control custom-select"
>
{hourOptions.map((hour) => ( {hourOptions.map((hour) => (
<option key={hour} value={hour}> <option key={hour} value={hour}>
{hour} {hour}
@ -39,11 +37,7 @@ export const TimeSelect = ({
))} ))}
</select> </select>
&nbsp;:&nbsp; &nbsp;:&nbsp;
<select <select value={minutes} onChange={onMinuteChange} className="form-control custom-select">
value={minutes}
onChange={onMinuteChange}
className="form-control custom-select"
>
{minuteOptions.map((minute) => ( {minuteOptions.map((minute) => (
<option key={minute} value={minute}> <option key={minute} value={minute}>
{minute} {minute}
@ -53,8 +47,3 @@ export const TimeSelect = ({
</div> </div>
); );
}; };
TimeSelect.propTypes = {
value: PropTypes.number.isRequired,
onChange: PropTypes.func.isRequired,
};

View File

@ -1,17 +1,18 @@
import React from 'react'; import React from 'react';
import ct from 'countries-and-timezones'; import ct from 'countries-and-timezones';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import PropTypes from 'prop-types';
import { LOCAL_TIMEZONE_VALUE } from '../../../../helpers/constants'; import { LOCAL_TIMEZONE_VALUE } from '../../../../helpers/constants';
export const Timezone = ({ interface TimezoneProps {
timezone, timezone: string;
setTimezone, setTimezone: (...args: unknown[]) => unknown;
}) => { }
export const Timezone = ({ timezone, setTimezone }: TimezoneProps) => {
const [t] = useTranslation(); const [t] = useTranslation();
const onTimeZoneChange = (event) => { const onTimeZoneChange = (event: any) => {
setTimezone(event.target.value); setTimezone(event.target.value);
}; };
@ -19,18 +20,10 @@ export const Timezone = ({
return ( return (
<div className="schedule__timezone"> <div className="schedule__timezone">
<label className="form__label form__label--with-desc mb-2"> <label className="form__label form__label--with-desc mb-2">{t('schedule_timezone')}</label>
{t('schedule_timezone')}
</label>
<select <select className="form-control custom-select" value={timezone} onChange={onTimeZoneChange}>
className="form-control custom-select" <option value={LOCAL_TIMEZONE_VALUE}>{t('schedule_timezone')}</option>
value={timezone}
onChange={onTimeZoneChange}
>
<option value={LOCAL_TIMEZONE_VALUE}>
{t('schedule_timezone')}
</option>
{/* TODO: get timezones from backend method when the method is ready */} {/* TODO: get timezones from backend method when the method is ready */}
{Object.keys(timezones).map((zone) => ( {Object.keys(timezones).map((zone) => (
<option key={zone} value={zone}> <option key={zone} value={zone}>
@ -41,8 +34,3 @@ export const Timezone = ({
</div> </div>
); );
}; };
Timezone.propTypes = {
timezone: PropTypes.string.isRequired,
setTimezone: PropTypes.func.isRequired,
};

View File

@ -1,4 +1,4 @@
export const getFullDayName = (t, abbreviation) => { export const getFullDayName = (t: any, abbreviation: any) => {
const dayMap = { const dayMap = {
sun: t('sunday'), sun: t('sunday'),
mon: t('monday'), mon: t('monday'),
@ -12,7 +12,7 @@ export const getFullDayName = (t, abbreviation) => {
return dayMap[abbreviation] || ''; return dayMap[abbreviation] || '';
}; };
export const getShortDayName = (t, abbreviation) => { export const getShortDayName = (t: any, abbreviation: any) => {
const dayMap = { const dayMap = {
sun: t('sunday_short'), sun: t('sunday_short'),
mon: t('monday_short'), mon: t('monday_short'),
@ -26,18 +26,19 @@ export const getShortDayName = (t, abbreviation) => {
return dayMap[abbreviation] || ''; return dayMap[abbreviation] || '';
}; };
export const getTimeFromMs = (value) => { export const getTimeFromMs = (value: any) => {
const selectedTime = new Date(value); const selectedTime = new Date(value);
const hours = selectedTime.getUTCHours(); const hours = selectedTime.getUTCHours();
const minutes = selectedTime.getUTCMinutes(); const minutes = selectedTime.getUTCMinutes();
return { return {
hours: hours.toString().padStart(2, '0'), hours: hours.toString().padStart(2, '0'),
minutes: minutes.toString().padStart(2, '0'), minutes: minutes.toString().padStart(2, '0'),
}; };
}; };
export const convertTimeToMs = (hours, minutes) => { export const convertTimeToMs = (hours: any, minutes: any) => {
const selectedTime = new Date(0); const selectedTime = new Date(0);
selectedTime.setUTCHours(parseInt(hours, 10)); selectedTime.setUTCHours(parseInt(hours, 10));
selectedTime.setUTCMinutes(parseInt(minutes, 10)); selectedTime.setUTCMinutes(parseInt(minutes, 10));

View File

@ -1,19 +1,23 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import PropTypes from 'prop-types';
import cn from 'classnames'; import cn from 'classnames';
import { Modal } from './Modal'; import { Modal } from './Modal';
import { getFullDayName, getShortDayName } from './helpers'; import { getFullDayName, getShortDayName } from './helpers';
import { LOCAL_TIMEZONE_VALUE } from '../../../../helpers/constants'; import { LOCAL_TIMEZONE_VALUE } from '../../../../helpers/constants';
import { TimePeriod } from './TimePeriod'; import { TimePeriod } from './TimePeriod';
import './styles.css'; import './styles.css';
export const ScheduleForm = ({ interface ScheduleFormProps {
schedule, schedule?: {
onScheduleSubmit, time_zone: string;
clientForm, };
}) => { onScheduleSubmit: (values: any) => void;
clientForm?: boolean;
}
export const ScheduleForm = ({ schedule, onScheduleSubmit, clientForm }: ScheduleFormProps) => {
const [t] = useTranslation(); const [t] = useTranslation();
const [modalOpen, setModalOpen] = useState(false); const [modalOpen, setModalOpen] = useState(false);
const [currentDay, setCurrentDay] = useState(); const [currentDay, setCurrentDay] = useState();
@ -25,12 +29,12 @@ export const ScheduleForm = ({
const scheduleMap = new Map(); const scheduleMap = new Map();
filteredScheduleKeys.forEach((day) => scheduleMap.set(day, schedule[day])); filteredScheduleKeys.forEach((day) => scheduleMap.set(day, schedule[day]));
const onSubmit = (values) => { const onSubmit = (values: any) => {
onScheduleSubmit(values); onScheduleSubmit(values);
onModalClose(); onModalClose();
}; };
const onDelete = (day) => { const onDelete = (day: any) => {
scheduleMap.delete(day); scheduleMap.delete(day);
const scheduleWeek = Object.fromEntries(Array.from(scheduleMap.entries())); const scheduleWeek = Object.fromEntries(Array.from(scheduleMap.entries()));
@ -41,7 +45,7 @@ export const ScheduleForm = ({
}); });
}; };
const onEdit = (day) => { const onEdit = (day: any) => {
setCurrentDay(day); setCurrentDay(day);
onModalOpen(); onModalOpen();
}; };
@ -67,23 +71,18 @@ export const ScheduleForm = ({
return ( return (
<div key={day} className="schedule__row"> <div key={day} className="schedule__row">
<div className="schedule__day"> <div className="schedule__day">{getFullDayName(t, day)}</div>
{getFullDayName(t, day)}
</div> <div className="schedule__day schedule__day--mobile">{getShortDayName(t, day)}</div>
<div className="schedule__day schedule__day--mobile">
{getShortDayName(t, day)} <TimePeriod startTimeMs={data.start} endTimeMs={data.end} />
</div>
<TimePeriod
startTimeMs={data.start}
endTimeMs={data.end}
/>
<div className="schedule__actions"> <div className="schedule__actions">
<button <button
type="button" type="button"
className="btn btn-icon btn-outline-primary btn-sm schedule__button" className="btn btn-icon btn-outline-primary btn-sm schedule__button"
title={t('edit_table_action')} title={t('edit_table_action')}
onClick={() => onEdit(day)} onClick={() => onEdit(day)}>
>
<svg className="icons icon12"> <svg className="icons icon12">
<use xlinkHref="#edit" /> <use xlinkHref="#edit" />
</svg> </svg>
@ -93,8 +92,7 @@ export const ScheduleForm = ({
type="button" type="button"
className="btn btn-icon btn-outline-secondary btn-sm schedule__button" className="btn btn-icon btn-outline-secondary btn-sm schedule__button"
title={t('delete_table_action')} title={t('delete_table_action')}
onClick={() => onDelete(day)} onClick={() => onDelete(day)}>
>
<svg className="icons"> <svg className="icons">
<use xlinkHref="#delete" /> <use xlinkHref="#delete" />
</svg> </svg>
@ -112,8 +110,7 @@ export const ScheduleForm = ({
{ 'btn-outline-success btn-sm': clientForm }, { 'btn-outline-success btn-sm': clientForm },
{ 'btn-success btn-standard': !clientForm }, { 'btn-success btn-standard': !clientForm },
)} )}
onClick={onAdd} onClick={onAdd}>
>
{t('schedule_new')} {t('schedule_new')}
</button> </button>
@ -129,9 +126,3 @@ export const ScheduleForm = ({
</div> </div>
); );
}; };
ScheduleForm.propTypes = {
schedule: PropTypes.object,
onScheduleSubmit: PropTypes.func.isRequired,
clientForm: PropTypes.bool,
};

View File

@ -73,7 +73,7 @@
outline: 0; outline: 0;
} }
.schedule__button-day[data-active="true"] { .schedule__button-day[data-active='true'] {
color: var(--btn-success-bgcolor); color: var(--btn-success-bgcolor);
border-color: var(--btn-success-bgcolor); border-color: var(--btn-success-bgcolor);
} }

View File

@ -2,50 +2,63 @@ import React, { useEffect } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import Form from './Form'; import Form from './Form';
import Card from '../../ui/Card'; import Card from '../../ui/Card';
import { getBlockedServices, getAllBlockedServices, updateBlockedServices } from '../../../actions/services'; import { getBlockedServices, getAllBlockedServices, updateBlockedServices } from '../../../actions/services';
import PageTitle from '../../ui/PageTitle'; import PageTitle from '../../ui/PageTitle';
import { ScheduleForm } from './ScheduleForm'; import { ScheduleForm } from './ScheduleForm';
import { RootState } from '../../../initialState';
const getInitialDataForServices = (initial) => (initial ? initial.reduce( const getInitialDataForServices = (initial: any) =>
(acc, service) => { initial
? initial.reduce(
(acc: any, service: any) => {
acc.blocked_services[service] = true; acc.blocked_services[service] = true;
return acc; return acc;
}, { blocked_services: {} }, },
) : initial); { blocked_services: {} },
)
: initial;
const Services = () => { const Services = () => {
const [t] = useTranslation(); const [t] = useTranslation();
const dispatch = useDispatch(); const dispatch = useDispatch();
const services = useSelector((store) => store?.services);
const services = useSelector((state: RootState) => state.services);
useEffect(() => { useEffect(() => {
dispatch(getBlockedServices()); dispatch(getBlockedServices());
dispatch(getAllBlockedServices()); dispatch(getAllBlockedServices());
}, []); }, []);
const handleSubmit = (values) => { const handleSubmit = (values: any) => {
if (!values || !values.blocked_services) { if (!values || !values.blocked_services) {
return; return;
} }
const blocked_services = Object const blocked_services = Object.keys(values.blocked_services).filter(
.keys(values.blocked_services) (service) => values.blocked_services[service],
.filter((service) => values.blocked_services[service]); );
dispatch(updateBlockedServices({ dispatch(
updateBlockedServices({
ids: blocked_services, ids: blocked_services,
schedule: services.list.schedule, schedule: services.list.schedule,
})); }),
);
}; };
const handleScheduleSubmit = (values) => { const handleScheduleSubmit = (values: any) => {
dispatch(updateBlockedServices({ dispatch(
updateBlockedServices({
ids: services.list.ids, ids: services.list.ids,
schedule: values, schedule: values,
})); }),
);
}; };
const initialValues = getInitialDataForServices(services.list.ids); const initialValues = getInitialDataForServices(services.list.ids);
@ -56,13 +69,9 @@ const Services = () => {
return ( return (
<> <>
<PageTitle <PageTitle title={t('blocked_services')} subtitle={t('blocked_services_desc')} />
title={t('blocked_services')}
subtitle={t('blocked_services_desc')} <Card bodyType="card-body box-body--settings">
/>
<Card
bodyType="card-body box-body--settings"
>
<div className="form"> <div className="form">
<Form <Form
initialValues={initialValues} initialValues={initialValues}
@ -77,12 +86,8 @@ const Services = () => {
<Card <Card
title={t('schedule_services')} title={t('schedule_services')}
subtitle={t('schedule_services_desc')} subtitle={t('schedule_services_desc')}
bodyType="card-body box-body--settings" bodyType="card-body box-body--settings">
> <ScheduleForm schedule={services.list.schedule} onScheduleSubmit={handleScheduleSubmit} />
<ScheduleForm
schedule={services.list.schedule}
onScheduleSubmit={handleScheduleSubmit}
/>
</Card> </Card>
</> </>
); );

View File

@ -1,17 +1,32 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types';
// @ts-expect-error FIXME: update react-table
import ReactTable from 'react-table'; import ReactTable from 'react-table';
import { withTranslation, Trans } from 'react-i18next'; import { withTranslation, Trans } from 'react-i18next';
import CellWrap from '../ui/CellWrap'; import CellWrap from '../ui/CellWrap';
import { MODAL_TYPE } from '../../helpers/constants'; import { MODAL_TYPE } from '../../helpers/constants';
import { formatDetailedDateTime } from '../../helpers/helpers'; import { formatDetailedDateTime } from '../../helpers/helpers';
import { isValidAbsolutePath } from '../../helpers/form'; import { isValidAbsolutePath } from '../../helpers/form';
import { LOCAL_STORAGE_KEYS, LocalStorageHelper } from '../../helpers/localStorageHelper'; import { LOCAL_STORAGE_KEYS, LocalStorageHelper } from '../../helpers/localStorageHelper';
class Table extends Component { interface TableProps {
getDateCell = (row) => CellWrap(row, formatDetailedDateTime); filters: unknown[];
loading: boolean;
processingConfigFilter: boolean;
toggleFilteringModal: (...args: unknown[]) => unknown;
handleDelete: (...args: unknown[]) => unknown;
toggleFilter: (...args: unknown[]) => unknown;
t: (...args: unknown[]) => string;
whitelist?: boolean;
}
renderCheckbox = ({ original }) => { class Table extends Component<TableProps> {
getDateCell = (row: any) => CellWrap(row, formatDetailedDateTime);
renderCheckbox = ({ original }: any) => {
const { processingConfigFilter, toggleFilter } = this.props; const { processingConfigFilter, toggleFilter } = this.props;
const { url, name, enabled } = original; const { url, name, enabled } = original;
const data = { name, url, enabled: !enabled }; const data = { name, url, enabled: !enabled };
@ -25,6 +40,7 @@ class Table extends Component {
checked={enabled} checked={enabled}
disabled={processingConfigFilter} disabled={processingConfigFilter}
/> />
<span className="checkbox__label" /> <span className="checkbox__label" />
</label> </label>
); );
@ -50,17 +66,15 @@ class Table extends Component {
accessor: 'url', accessor: 'url',
minWidth: 180, minWidth: 180,
// eslint-disable-next-line react/prop-types // eslint-disable-next-line react/prop-types
Cell: ({ value }) => ( Cell: ({ value }: any) => (
<div className="logs__row"> <div className="logs__row">
{isValidAbsolutePath(value) ? value {isValidAbsolutePath(value) ? (
: <a value
href={value} ) : (
target="_blank" <a href={value} target="_blank" rel="noopener noreferrer" className="link logs__text">
rel="noopener noreferrer"
className="link logs__text"
>
{value} {value}
</a>} </a>
)}
</div> </div>
), ),
}, },
@ -69,7 +83,7 @@ class Table extends Component {
accessor: 'rulesCount', accessor: 'rulesCount',
className: 'text-center', className: 'text-center',
minWidth: 100, minWidth: 100,
Cell: (props) => props.value.toLocaleString(), Cell: (props: any) => props.value.toLocaleString(),
}, },
{ {
Header: <Trans>last_time_updated_table_header</Trans>, Header: <Trans>last_time_updated_table_header</Trans>,
@ -85,9 +99,10 @@ class Table extends Component {
width: 100, width: 100,
sortable: false, sortable: false,
resizable: false, resizable: false,
Cell: (row) => { Cell: (row: any) => {
const { original } = row; const { original } = row;
const { url } = original; const { url } = original;
const { t, toggleFilteringModal, handleDelete } = this.props; const { t, toggleFilteringModal, handleDelete } = this.props;
return ( return (
@ -96,22 +111,22 @@ class Table extends Component {
type="button" type="button"
className="btn btn-icon btn-outline-primary btn-sm mr-2" className="btn btn-icon btn-outline-primary btn-sm mr-2"
title={t('edit_table_action')} title={t('edit_table_action')}
onClick={() => toggleFilteringModal({ onClick={() =>
toggleFilteringModal({
type: MODAL_TYPE.EDIT_FILTERS, type: MODAL_TYPE.EDIT_FILTERS,
url, url,
}) })
} }>
>
<svg className="icons icon12"> <svg className="icons icon12">
<use xlinkHref="#edit" /> <use xlinkHref="#edit" />
</svg> </svg>
</button> </button>
<button <button
type="button" type="button"
className="btn btn-icon btn-outline-secondary btn-sm" className="btn btn-icon btn-outline-secondary btn-sm"
onClick={() => handleDelete(url)} onClick={() => handleDelete(url)}
title={t('delete_table_action')} title={t('delete_table_action')}>
>
<svg className="icons icon12"> <svg className="icons icon12">
<use xlinkHref="#delete" /> <use xlinkHref="#delete" />
</svg> </svg>
@ -123,9 +138,7 @@ class Table extends Component {
]; ];
render() { render() {
const { const { loading, filters, t, whitelist } = this.props;
loading, filters, t, whitelist,
} = this.props;
const localStorageKey = whitelist const localStorageKey = whitelist
? LOCAL_STORAGE_KEYS.ALLOWLIST_PAGE_SIZE ? LOCAL_STORAGE_KEYS.ALLOWLIST_PAGE_SIZE
@ -137,7 +150,7 @@ class Table extends Component {
columns={this.columns} columns={this.columns}
showPagination showPagination
defaultPageSize={LocalStorageHelper.getItem(localStorageKey) || 10} defaultPageSize={LocalStorageHelper.getItem(localStorageKey) || 10}
onPageSizeChange={(size) => LocalStorageHelper.setItem(localStorageKey, size)} onPageSizeChange={(size: any) => LocalStorageHelper.setItem(localStorageKey, size)}
loading={loading} loading={loading}
minRows={6} minRows={6}
ofText="/" ofText="/"
@ -152,15 +165,4 @@ class Table extends Component {
} }
} }
Table.propTypes = {
filters: PropTypes.array.isRequired,
loading: PropTypes.bool.isRequired,
processingConfigFilter: PropTypes.bool.isRequired,
toggleFilteringModal: PropTypes.func.isRequired,
handleDelete: PropTypes.func.isRequired,
toggleFilter: PropTypes.func.isRequired,
t: PropTypes.func.isRequired,
whitelist: PropTypes.bool,
};
export default withTranslation()(Table); export default withTranslation()(Table);

View File

@ -1,10 +1,12 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { NavLink } from 'react-router-dom'; import { NavLink } from 'react-router-dom';
import PropTypes from 'prop-types';
import enhanceWithClickOutside from 'react-click-outside'; import enhanceWithClickOutside from 'react-click-outside';
import classnames from 'classnames'; import classnames from 'classnames';
import { Trans, withTranslation } from 'react-i18next'; import { Trans, withTranslation } from 'react-i18next';
import { SETTINGS_URLS, FILTERS_URLS, MENU_URLS } from '../../helpers/constants'; import { SETTINGS_URLS, FILTERS_URLS, MENU_URLS } from '../../helpers/constants';
import Dropdown from '../ui/Dropdown'; import Dropdown from '../ui/Dropdown';
const MENU_ITEMS = [ const MENU_ITEMS = [
@ -80,7 +82,14 @@ const FILTERS_ITEMS = [
}, },
]; ];
class Menu extends Component { interface MenuProps {
isMenuOpen: boolean;
closeMenu: (...args: unknown[]) => unknown;
pathname: string;
t?: (...args: unknown[]) => string;
}
class Menu extends Component<MenuProps> {
handleClickOutside = () => { handleClickOutside = () => {
this.props.closeMenu(); this.props.closeMenu();
}; };
@ -89,52 +98,51 @@ class Menu extends Component {
this.props.closeMenu(); this.props.closeMenu();
}; };
getActiveClassForDropdown = (URLS) => { getActiveClassForDropdown = (URLS: any) => {
const isActivePage = Object.values(URLS) const isActivePage = Object.values(URLS)
.some((item) => item === this.props.pathname);
.some((item: any) => item === this.props.pathname);
return isActivePage ? 'active' : ''; return isActivePage ? 'active' : '';
}; };
getNavLink = ({ getNavLink = ({ route, exact, text, order, className, icon }: any) => (
route, exact, text, order, className, icon,
}) => (
<NavLink <NavLink
to={route} to={route}
key={route} key={route}
exact={exact || false} exact={exact || false}
className={`order-${order} ${className}`} className={`order-${order} ${className}`}
onClick={this.closeMenu} onClick={this.closeMenu}>
>
{icon && ( {icon && (
<svg className="nav-icon"> <svg className="nav-icon">
<use xlinkHref={`#${icon}`} /> <use xlinkHref={`#${icon}`} />
</svg> </svg>
)} )}
<Trans>{text}</Trans> <Trans>{text}</Trans>
</NavLink> </NavLink>
); );
getDropdown = ({ getDropdown = ({ label, order, URLS, icon, ITEMS }: any) => (
label, order, URLS, icon, ITEMS,
}) => (
<Dropdown <Dropdown
label={this.props.t(label)} label={this.props.t(label)}
baseClassName='dropdown' baseClassName="dropdown"
controlClassName={`nav-link ${this.getActiveClassForDropdown(URLS)}`} controlClassName={`nav-link ${this.getActiveClassForDropdown(URLS)}`}
icon={icon}> icon={icon}>
{ITEMS.map((item) => ( {ITEMS.map((item: any) =>
this.getNavLink({ this.getNavLink({
...item, ...item,
order, order,
className: 'dropdown-item', className: 'dropdown-item',
})))} }),
)}
</Dropdown> </Dropdown>
); );
render() { render() {
const menuClass = classnames({ const menuClass = classnames({
'header__column mobile-menu': true, 'header__column mobile-menu': true,
'mobile-menu--active': this.props.isMenuOpen, 'mobile-menu--active': this.props.isMenuOpen,
}); });
return ( return (
@ -142,17 +150,14 @@ class Menu extends Component {
<div className={menuClass}> <div className={menuClass}>
<ul className="nav nav-tabs border-0 flex-column flex-lg-row flex-nowrap"> <ul className="nav nav-tabs border-0 flex-column flex-lg-row flex-nowrap">
{MENU_ITEMS.map((item) => ( {MENU_ITEMS.map((item) => (
<li <li className={`nav-item order-${item.order}`} key={item.text} onClick={this.closeMenu}>
className={`nav-item order-${item.order}`}
key={item.text}
onClick={this.closeMenu}
>
{this.getNavLink({ {this.getNavLink({
...item, ...item,
className: 'nav-link', className: 'nav-link',
})} })}
</li> </li>
))} ))}
<li className="nav-item order-1"> <li className="nav-item order-1">
{this.getDropdown({ {this.getDropdown({
order: 1, order: 1,
@ -162,6 +167,7 @@ class Menu extends Component {
ITEMS: SETTINGS_ITEMS, ITEMS: SETTINGS_ITEMS,
})} })}
</li> </li>
<li className="nav-item order-2"> <li className="nav-item order-2">
{this.getDropdown({ {this.getDropdown({
order: 2, order: 2,
@ -178,11 +184,4 @@ class Menu extends Component {
} }
} }
Menu.propTypes = {
isMenuOpen: PropTypes.bool.isRequired,
closeMenu: PropTypes.func.isRequired,
pathname: PropTypes.string.isRequired,
t: PropTypes.func,
};
export default withTranslation()(enhanceWithClickOutside(Menu)); export default withTranslation()(enhanceWithClickOutside(Menu));

View File

@ -1,75 +0,0 @@
import React, { useState } from 'react';
import { Link, useLocation } from 'react-router-dom';
import { shallowEqual, useSelector } from 'react-redux';
import { useTranslation } from 'react-i18next';
import classnames from 'classnames';
import Menu from './Menu';
import logo from '../ui/svg/logo.svg';
import './Header.css';
const Header = () => {
const [isMenuOpen, setIsMenuOpen] = useState(false);
const { t } = useTranslation();
const {
protectionEnabled,
processing,
isCoreRunning,
processingProfile,
name,
} = useSelector((state) => state.dashboard, shallowEqual);
const { pathname } = useLocation();
const toggleMenuOpen = () => {
setIsMenuOpen((isMenuOpen) => !isMenuOpen);
};
const closeMenu = () => {
setIsMenuOpen(false);
};
const badgeClass = classnames('badge dns-status', {
'badge-success': protectionEnabled,
'badge-danger': !protectionEnabled,
});
return <div className="header">
<div className="header__container">
<div className="header__row">
<div
className="header-toggler d-lg-none ml-lg-0 collapsed"
onClick={toggleMenuOpen}
>
<span className="header-toggler-icon" />
</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="AdGuard Home logo" className="header-brand-img" />
</Link>
{!processing && isCoreRunning
&& <span className={badgeClass}
>{t(protectionEnabled ? 'on' : 'off')}
</span>}
</div>
</div>
<Menu
pathname={pathname}
isMenuOpen={isMenuOpen}
closeMenu={closeMenu}
/>
<div className="header__column">
<div className="header__right">
{!processingProfile && name
&& <a href="control/logout" className="btn btn-sm btn-outline-secondary">
{t('sign_out')}
</a>}
</div>
</div>
</div>
</div>
</div>;
};
export default Header;

View File

@ -0,0 +1,74 @@
import React, { useState } from 'react';
import { Link, useLocation } from 'react-router-dom';
import { shallowEqual, useSelector } from 'react-redux';
import { useTranslation } from 'react-i18next';
import classnames from 'classnames';
import Menu from './Menu';
import { Logo } from '../ui/svg/logo';
import './Header.css';
import { RootState } from '../../initialState';
const Header = () => {
const [isMenuOpen, setIsMenuOpen] = useState(false);
const { t } = useTranslation();
const { protectionEnabled, processing, isCoreRunning, processingProfile, name } = useSelector(
(state: RootState) => state.dashboard,
shallowEqual,
);
const { pathname } = useLocation();
const toggleMenuOpen = () => {
setIsMenuOpen((isMenuOpen) => !isMenuOpen);
};
const closeMenu = () => {
setIsMenuOpen(false);
};
const badgeClass = classnames('badge dns-status', {
'badge-success': protectionEnabled,
'badge-danger': !protectionEnabled,
});
return (
<div className="header">
<div className="header__container">
<div className="header__row">
<div className="header-toggler d-lg-none ml-lg-0 collapsed" onClick={toggleMenuOpen}>
<span className="header-toggler-icon" />
</div>
<div className="header__column">
<div className="d-flex align-items-center">
<Link to="/" className="nav-link pl-0 pr-1">
<Logo className="header-brand-img" />
</Link>
{!processing && isCoreRunning && (
<span className={badgeClass}>{t(protectionEnabled ? 'on' : 'off')}</span>
)}
</div>
</div>
<Menu pathname={pathname} isMenuOpen={isMenuOpen} closeMenu={closeMenu} />
<div className="header__column">
<div className="header__right">
{!processingProfile && name && (
<a href="control/logout" className="btn btn-sm btn-outline-secondary">
{t('sign_out')}
</a>
)}
</div>
</div>
</div>
</div>
</div>
);
};
export default Header;

View File

@ -1,12 +1,17 @@
import React from 'react'; import React from 'react';
import { Trans } from 'react-i18next'; import { Trans } from 'react-i18next';
import { HashLink as Link } from 'react-router-hash-link'; import { HashLink as Link } from 'react-router-hash-link';
const AnonymizerNotification = () => ( const AnonymizerNotification = () => (
<div className="alert alert-primary mt-6"> <div className="alert alert-primary mt-6">
<Trans components={[ <Trans
components={[
<strong key="0">text</strong>, <strong key="0">text</strong>,
<Link to="/settings#logs-config" key="1">link</Link>,
<Link to="/settings#logs-config" key="1">
link
</Link>,
]}> ]}>
anonymizer_notification anonymizer_notification
</Trans> </Trans>

View File

@ -3,36 +3,55 @@ import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import { nanoid } from 'nanoid'; import { nanoid } from 'nanoid';
import classNames from 'classnames'; import classNames from 'classnames';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Link, useHistory } from 'react-router-dom'; import { Link, useHistory } from 'react-router-dom';
import propTypes from 'prop-types';
import { checkFiltered, getBlockingClientName } from '../../../helpers/helpers'; import { checkFiltered, getBlockingClientName } from '../../../helpers/helpers';
import { BLOCK_ACTIONS } from '../../../helpers/constants'; import { BLOCK_ACTIONS } from '../../../helpers/constants';
import { toggleBlocking, toggleBlockingForClient } from '../../../actions'; import { toggleBlocking, toggleBlockingForClient } from '../../../actions';
import IconTooltip from './IconTooltip'; import IconTooltip from './IconTooltip';
import { renderFormattedClientCell } from '../../../helpers/renderFormattedClientCell'; import { renderFormattedClientCell } from '../../../helpers/renderFormattedClientCell';
import { toggleClientBlock } from '../../../actions/access'; import { toggleClientBlock } from '../../../actions/access';
import { getBlockClientInfo } from './helpers'; import { getBlockClientInfo } from './helpers';
import { getStats } from '../../../actions/stats'; import { getStats } from '../../../actions/stats';
import { updateLogs } from '../../../actions/queryLogs'; import { updateLogs } from '../../../actions/queryLogs';
import { RootState } from '../../../initialState';
const ClientCell = ({ interface ClientCellProps {
client, client: string;
client_id, client_id?: string;
client_info, client_info?: {
domain, name: string;
reason, whois: {
}) => { country?: string;
city?: string;
orgname?: string;
};
disallowed: boolean;
disallowed_rule: string;
};
domain: string;
reason: string;
}
const ClientCell = ({ client, client_id, client_info, domain, reason }: ClientCellProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useDispatch(); const dispatch = useDispatch();
const history = useHistory(); const history = useHistory();
const autoClients = useSelector((state) => state.dashboard.autoClients, shallowEqual);
const isDetailed = useSelector((state) => state.queryLogs.isDetailed); const autoClients = useSelector((state: RootState) => state.dashboard.autoClients, shallowEqual);
const allowedСlients = useSelector((state) => state.access.allowed_clients, shallowEqual);
const isDetailed = useSelector((state: RootState) => state.queryLogs.isDetailed);
const allowedClients = useSelector((state: RootState) => state.access.allowed_clients, shallowEqual);
const [isOptionsOpened, setOptionsOpened] = useState(false); const [isOptionsOpened, setOptionsOpened] = useState(false);
const autoClient = autoClients.find((autoClient) => autoClient.name === client); const autoClient = autoClients.find((autoClient: any) => autoClient.name === client);
const clients = useSelector((state) => state.dashboard.clients);
const clients = useSelector((state: RootState) => state.dashboard.clients);
const source = autoClient?.source; const source = autoClient?.source;
const whoisAvailable = client_info && Object.keys(client_info.whois).length > 0; const whoisAvailable = client_info && Object.keys(client_info.whois).length > 0;
const clientName = client_info?.name || client_id; const clientName = client_info?.name || client_id;
@ -57,7 +76,7 @@ const ClientCell = ({
const isFiltered = checkFiltered(reason); const isFiltered = checkFiltered(reason);
const clientIds = clients.map((c) => c.ids).flat(); const clientIds = clients.map((c: any) => c.ids).flat();
const nameClass = classNames('w-90 o-hidden d-flex flex-column', { const nameClass = classNames('w-90 o-hidden d-flex flex-column', {
'mt-2': isDetailed && !client_info?.name && !whoisAvailable, 'mt-2': isDetailed && !client_info?.name && !whoisAvailable,
@ -68,7 +87,7 @@ const ClientCell = ({
'my-3': isDetailed, 'my-3': isDetailed,
}); });
const renderBlockingButton = (isFiltered, domain) => { const renderBlockingButton = (isFiltered: any, domain: any) => {
const buttonType = isFiltered ? BLOCK_ACTIONS.UNBLOCK : BLOCK_ACTIONS.BLOCK; const buttonType = isFiltered ? BLOCK_ACTIONS.UNBLOCK : BLOCK_ACTIONS.BLOCK;
const { const {
@ -79,7 +98,7 @@ const ClientCell = ({
client, client,
client_info?.disallowed || false, client_info?.disallowed || false,
client_info?.disallowed_rule || '', client_info?.disallowed_rule || '',
allowedСlients, allowedClients,
); );
const blockingForClientKey = isFiltered ? 'unblock_for_this_client_only' : 'block_for_this_client_only'; const blockingForClientKey = isFiltered ? 'unblock_for_this_client_only' : 'block_for_this_client_only';
@ -108,11 +127,13 @@ const ClientCell = ({
name: blockingClientKey, name: blockingClientKey,
onClick: async () => { onClick: async () => {
if (window.confirm(confirmMessage)) { if (window.confirm(confirmMessage)) {
await dispatch(toggleClientBlock( await dispatch(
toggleClientBlock(
client, client,
client_info?.disallowed || false, client_info?.disallowed || false,
client_info?.disallowed_rule || '', client_info?.disallowed_rule || '',
)); ),
);
await dispatch(updateLogs()); await dispatch(updateLogs());
setOptionsOpened(false); setOptionsOpened(false);
} }
@ -130,21 +151,19 @@ const ClientCell = ({
}); });
} }
const getOptions = (options) => { const getOptions = (options: any) => {
if (options.length === 0) { if (options.length === 0) {
return null; return null;
} }
return ( return (
<> <>
{options.map(({ {options.map(({ name, onClick, disabled, className }: any) => (
name, onClick, disabled, className,
}) => (
<button <button
key={name} key={name}
className={classNames('button-action--arrow-option px-4 py-1', className)} className={classNames('button-action--arrow-option px-4 py-1', className)}
onClick={onClick} onClick={onClick}
disabled={disabled} disabled={disabled}>
>
{t(name)} {t(name)}
</button> </button>
))} ))}
@ -160,11 +179,7 @@ const ClientCell = ({
return ( return (
<div className={containerClass}> <div className={containerClass}>
<button <button type="button" className="btn btn-icon btn-sm px-0" onClick={() => setOptionsOpened(true)}>
type="button"
className="btn btn-icon btn-sm px-0"
onClick={() => setOptionsOpened(true)}
>
<svg className="icon24 icon--lightgray button-action__icon"> <svg className="icon24 icon--lightgray button-action__icon">
<use xlinkHref="#bullets" /> <use xlinkHref="#bullets" />
</svg> </svg>
@ -188,10 +203,7 @@ const ClientCell = ({
}; };
return ( return (
<div <div className="o-hidden h-100 logs__cell logs__cell--client" role="gridcell">
className="o-hidden h-100 logs__cell logs__cell--client"
role="gridcell"
>
<IconTooltip <IconTooltip
className={hintClass} className={hintClass}
columnClass="grid grid--limited" columnClass="grid grid--limited"
@ -202,6 +214,7 @@ const ClientCell = ({
content={processedData} content={processedData}
placement="bottom" placement="bottom"
/> />
<div className={nameClass}> <div className={nameClass}>
<div data-tip={true} data-for={id}> <div data-tip={true} data-for={id}>
{renderFormattedClientCell(client, clientInfo, isDetailed, true)} {renderFormattedClientCell(client, clientInfo, isDetailed, true)}
@ -210,8 +223,7 @@ const ClientCell = ({
<Link <Link
className="detailed-info d-none d-sm-block logs__text logs__text--link logs__text--client" className="detailed-info d-none d-sm-block logs__text logs__text--link logs__text--client"
to={`logs?search="${encodeURIComponent(clientName)}"`} to={`logs?search="${encodeURIComponent(clientName)}"`}
title={clientName} title={clientName}>
>
{clientName} {clientName}
</Link> </Link>
)} )}
@ -221,21 +233,4 @@ const ClientCell = ({
); );
}; };
ClientCell.propTypes = {
client: propTypes.string.isRequired,
client_id: propTypes.string,
client_info: propTypes.shape({
name: propTypes.string.isRequired,
whois: propTypes.shape({
country: propTypes.string,
city: propTypes.string,
orgname: propTypes.string,
}).isRequired,
disallowed: propTypes.bool.isRequired,
disallowed_rule: propTypes.string.isRequired,
}),
domain: propTypes.string.isRequired,
reason: propTypes.string.isRequired,
};
export default ClientCell; export default ClientCell;

Some files were not shown because too many files have changed in this diff Show More