mirror of
https://git.vectorsigma.ru/public/AdGuardHome.git
synced 2026-07-28 20:19:13 +00:00
fix build
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -23,6 +23,10 @@
|
||||
/client/playwright-report/
|
||||
/client/playwright/.cache/
|
||||
/client/test-results/
|
||||
/client_v2/blob-report/
|
||||
/client_v2/playwright-report/
|
||||
/client_v2/playwright/.cache/
|
||||
/client_v2/test-results/
|
||||
/data/
|
||||
/dist/
|
||||
/filtering/tests/filtering.TestLotsOfRules*.pprof
|
||||
|
||||
14
client_v2/.editorconfig
Normal file
14
client_v2/.editorconfig
Normal file
@@ -0,0 +1,14 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
end_of_line = lf
|
||||
max_line_length = 180
|
||||
|
||||
[*.md]
|
||||
max_line_length = off
|
||||
trim_trailing_whitespace = false
|
||||
87
client_v2/.eslintrc.json
Normal file
87
client_v2/.eslintrc.json
Normal file
@@ -0,0 +1,87 @@
|
||||
{
|
||||
"plugins": [
|
||||
"prettier"
|
||||
],
|
||||
"extends": [
|
||||
"airbnb-base",
|
||||
"prettier",
|
||||
"eslint:recommended",
|
||||
"plugin:react/recommended",
|
||||
"plugin:@typescript-eslint/recommended"
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"env": {
|
||||
"jest": true,
|
||||
"node": true,
|
||||
"browser": true,
|
||||
"commonjs": true
|
||||
},
|
||||
"settings": {
|
||||
"react": {
|
||||
"pragma": "React",
|
||||
"version": "16.4"
|
||||
},
|
||||
"import/resolver": {
|
||||
"node": {
|
||||
"extensions": [
|
||||
".js",
|
||||
".jsx",
|
||||
".ts",
|
||||
".tsx"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"rules": {
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
"argsIgnorePattern": "^_"
|
||||
}
|
||||
],
|
||||
"import/extensions": [
|
||||
"error",
|
||||
"ignorePackages",
|
||||
{
|
||||
"js": "never",
|
||||
"jsx": "never",
|
||||
"ts": "never",
|
||||
"tsx": "never"
|
||||
}
|
||||
],
|
||||
"class-methods-use-this": "off",
|
||||
"no-shadow": "off",
|
||||
"camelcase": "off",
|
||||
"no-console": [
|
||||
"warn",
|
||||
{
|
||||
"allow": [
|
||||
"warn",
|
||||
"error"
|
||||
]
|
||||
}
|
||||
],
|
||||
"import/no-extraneous-dependencies": [
|
||||
"error",
|
||||
{
|
||||
"devDependencies": true
|
||||
}
|
||||
],
|
||||
"import/prefer-default-export": "off",
|
||||
"no-alert": "off",
|
||||
"arrow-body-style": "off",
|
||||
"max-len": [
|
||||
"error",
|
||||
120,
|
||||
2,
|
||||
{
|
||||
"ignoreUrls": true,
|
||||
"ignoreComments": false,
|
||||
"ignoreRegExpLiterals": true,
|
||||
"ignoreStrings": true,
|
||||
"ignoreTemplateLiterals": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
1
client_v2/.gitattributes
vendored
Normal file
1
client_v2/.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.ts text eol=lf
|
||||
10
client_v2/.prettierrc
Normal file
10
client_v2/.prettierrc
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"printWidth": 120,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all",
|
||||
"bracketSpacing": true,
|
||||
"bracketSameLine": true,
|
||||
"tabWidth": 4,
|
||||
"semi": true,
|
||||
"arrowParens": "always"
|
||||
}
|
||||
44
client_v2/.stylelintrc.js
Normal file
44
client_v2/.stylelintrc.js
Normal 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_v2/babel.config.cjs
Normal file
14
client_v2/babel.config.cjs
Normal 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',
|
||||
],
|
||||
};
|
||||
};
|
||||
6
client_v2/constants.js
Normal file
6
client_v2/constants.js
Normal file
@@ -0,0 +1,6 @@
|
||||
export const BUILD_ENVS = {
|
||||
dev: 'development',
|
||||
prod: 'production',
|
||||
};
|
||||
|
||||
export const BASE_URL = 'control';
|
||||
6
client_v2/dev.eslintrc
Normal file
6
client_v2/dev.eslintrc
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": ".eslintrc",
|
||||
"rules": {
|
||||
"no-debugger":"warn"
|
||||
}
|
||||
}
|
||||
16
client_v2/global.d.ts
vendored
Normal file
16
client_v2/global.d.ts
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
import React from 'react';
|
||||
|
||||
declare module '*.svg' {
|
||||
const content: React.FunctionComponent<React.SVGAttributes<SVGElement>>;
|
||||
export default content;
|
||||
}
|
||||
|
||||
declare module '*.module.pcss' {
|
||||
const classes: { [key: string]: string };
|
||||
export default classes;
|
||||
}
|
||||
|
||||
declare module '*.pcss' {
|
||||
const classes: { [key: string]: string };
|
||||
export default classes;
|
||||
}
|
||||
132
client_v2/package.json
Normal file
132
client_v2/package.json
Normal file
@@ -0,0 +1,132 @@
|
||||
{
|
||||
"name": "dashboard",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"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",
|
||||
"watch": "cross-env BUILD_ENV=dev webpack --config webpack.dev.js --watch",
|
||||
"watch:hot": "cross-env BUILD_ENV=dev webpack-dev-server --config webpack.dev.js",
|
||||
"lint": "eslint --ext .ts,.tsx src",
|
||||
"lint:fix": "eslint --ext .ts,.tsx src --fix",
|
||||
"test": "vitest --run",
|
||||
"test:watch": "vitest --watch",
|
||||
"test:e2e": "npx playwright test tests/e2e",
|
||||
"test:e2e:interactive": "npx playwright test --ui",
|
||||
"test:e2e:debug": "npx playwright test --debug",
|
||||
"test:e2e:codegen": "npx playwright codegen",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"typecheck:watch": "tsc --noEmit --watch"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@nivo/line": "^0.64.0",
|
||||
"axios": "^0.19.2",
|
||||
"classnames": "^2.5.1",
|
||||
"clsx": "^2.1.1",
|
||||
"countries-and-timezones": "^3.6.0",
|
||||
"date-fns": "^1.29.0",
|
||||
"i18next": "^19.6.2",
|
||||
"i18next-browser-languagedetector": "^4.2.0",
|
||||
"ipaddr.js": "^1.9.1",
|
||||
"js-yaml": "^3.14.0",
|
||||
"lodash": "^4.17.19",
|
||||
"nanoid": "^3.1.9",
|
||||
"popper.js": "^1.16.1",
|
||||
"prop-types": "^15.8.1",
|
||||
"qs": "^6.14.0",
|
||||
"query-string": "^6.13.1",
|
||||
"rc-dialog": "^10.0.0",
|
||||
"rc-dropdown": "^4.2.1",
|
||||
"react": "^16.13.1",
|
||||
"react-click-outside": "^3.0.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-hook-form": "^7.54.0",
|
||||
"react-i18next": "^11.7.2",
|
||||
"react-modal": "^3.11.2",
|
||||
"react-popper-tooltip": "^2.11.1",
|
||||
"react-redux": "^7.2.0",
|
||||
"react-redux-loading-bar": "^4.6.0",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-router-hash-link": "^1.2.2",
|
||||
"react-select": "^3.1.0",
|
||||
"react-table": "^6.11.4",
|
||||
"react-transition-group": "^4.4.5",
|
||||
"redux": "^4.0.5",
|
||||
"redux-actions": "^2.6.5",
|
||||
"redux-thunk": "^2.3.0",
|
||||
"ts-migrate": "^0.1.35",
|
||||
"url-polyfill": "^1.1.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.24.5",
|
||||
"@babel/plugin-transform-class-properties": "^7.24.1",
|
||||
"@babel/plugin-transform-nullish-coalescing-operator": "^7.24.1",
|
||||
"@babel/plugin-transform-object-rest-spread": "^7.24.5",
|
||||
"@babel/plugin-transform-optional-chaining": "^7.24.5",
|
||||
"@babel/plugin-transform-runtime": "^7.24.3",
|
||||
"@babel/preset-env": "^7.24.5",
|
||||
"@babel/preset-react": "^7.24.1",
|
||||
"@playwright/test": "1.50.1",
|
||||
"@types/lodash": "^4.17.4",
|
||||
"@types/node": "^22.13.10",
|
||||
"@types/react": "^17.0.80",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@types/react-redux": "^7.1.33",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"@types/react-table": "^7.7.20",
|
||||
"@types/redux-actions": "^2.6.5",
|
||||
"@typescript-eslint/eslint-plugin": "^7.11.0",
|
||||
"@typescript-eslint/parser": "^7.10.0",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"babel-loader": "^9.1.3",
|
||||
"clean-webpack-plugin": "^4.0.0",
|
||||
"copy-webpack-plugin": "^12.0.2",
|
||||
"cross-env": "^7.0.3",
|
||||
"css-loader": "^7.1.2",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-airbnb": "^19.0.4",
|
||||
"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",
|
||||
"jscodeshift": "^0.15.2",
|
||||
"mini-css-extract-plugin": "^2.9.0",
|
||||
"path": "^0.12.7",
|
||||
"postcss": "^8.5.6",
|
||||
"postcss-custom-media": "^11.0.6",
|
||||
"postcss-import": "^16.1.1",
|
||||
"postcss-loader": "^8.1.1",
|
||||
"postcss-nested": "^7.0.2",
|
||||
"prettier": "^3.2.5",
|
||||
"react-hot-loader": "^4.13.1",
|
||||
"style-loader": "^4.0.0",
|
||||
"stylelint": "^16.5.0",
|
||||
"ts-loader": "^9.5.1",
|
||||
"url-loader": "^4.1.1",
|
||||
"user-agent-data-types": "^0.4.2",
|
||||
"vitest": "^3.1.1",
|
||||
"webpack": "^5.91.0",
|
||||
"webpack-cli": "^5.1.4",
|
||||
"webpack-dev-server": "^5.0.4",
|
||||
"webpack-merge": "^5.10.0"
|
||||
},
|
||||
"browserslist": {
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
],
|
||||
"production": [
|
||||
">1%",
|
||||
"last 4 chrome version",
|
||||
"last 4 firefox version",
|
||||
"last 4 safari version",
|
||||
"firefox esr",
|
||||
"not ie < 9"
|
||||
]
|
||||
}
|
||||
}
|
||||
52
client_v2/playwright.config.ts
Normal file
52
client_v2/playwright.config.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { defineConfig, devices } from '@playwright/test';
|
||||
|
||||
import path from 'path';
|
||||
import { CONFIG_FILE_PATH } from './tests/constants';
|
||||
|
||||
/**
|
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
*/
|
||||
export default defineConfig({
|
||||
testDir: './tests/e2e',
|
||||
globalSetup: path.resolve('./tests/e2e/globalSetup.ts'),
|
||||
globalTeardown: path.resolve('./tests/e2e/globalTeardown.ts'),
|
||||
timeout: 5000,
|
||||
/* Run tests in files in parallel */
|
||||
fullyParallel: true,
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
forbidOnly: !!process.env.CI,
|
||||
/* Retry on CI only */
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
/* Opt out of parallel tests on CI. */
|
||||
workers: process.env.CI ? 1 : undefined,
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: 'html',
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
use: {
|
||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||
baseURL: 'http://127.0.0.1:3000',
|
||||
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
trace: 'on-first-retry',
|
||||
launchOptions: {
|
||||
headless: true,
|
||||
},
|
||||
},
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
projects: [
|
||||
{
|
||||
name: 'chromium',
|
||||
use: { ...devices['Desktop Chrome'] },
|
||||
},
|
||||
],
|
||||
|
||||
webServer: {
|
||||
stdout: process.env.CI ? 'pipe' : 'ignore',
|
||||
command: `${!process.env.CI ? 'sudo ' : ''}./AdGuardHome --local-frontend -v -c ${CONFIG_FILE_PATH}`,
|
||||
url: 'http://127.0.0.1:3000',
|
||||
cwd: '..',
|
||||
reuseExistingServer: !process.env.CI,
|
||||
timeout: 10000,
|
||||
},
|
||||
});
|
||||
11770
client_v2/pnpm-lock.yaml
generated
Normal file
11770
client_v2/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
13
client_v2/postcss.config.js
Normal file
13
client_v2/postcss.config.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import autoprefixer from 'autoprefixer';
|
||||
import postcssImport from 'postcss-import';
|
||||
import postcssCustomMedia from 'postcss-custom-media';
|
||||
import postcssNested from 'postcss-nested';
|
||||
|
||||
export default {
|
||||
plugins: [
|
||||
postcssImport(),
|
||||
postcssCustomMedia(),
|
||||
postcssNested(),
|
||||
autoprefixer({ overrideBrowserslist: ['last 2 versions'] }),
|
||||
],
|
||||
};
|
||||
6
client_v2/prod.eslintrc
Normal file
6
client_v2/prod.eslintrc
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"rules": {
|
||||
// disallow the use of debugger
|
||||
"no-debugger": "error",
|
||||
}
|
||||
}
|
||||
BIN
client_v2/public/assets/apple-touch-icon-180x180.png
Normal file
BIN
client_v2/public/assets/apple-touch-icon-180x180.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.1 KiB |
BIN
client_v2/public/assets/favicon.png
Normal file
BIN
client_v2/public/assets/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
8
client_v2/public/assets/safari-pinned-tab.svg
Normal file
8
client_v2/public/assets/safari-pinned-tab.svg
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16pt" height="16pt"
|
||||
viewBox="0 0 16 16" version="1.1">
|
||||
<g id="surface1">
|
||||
<path style=" stroke:none;fill-rule:evenodd;fill:rgb(0%,0%,0%);fill-opacity:1;"
|
||||
d="M 8 0 C 10.5 0 13.515625 0.574219 16 1.835938 L 15.996094 2.542969 C 15.957031 5.605469 15.410156 11.71875 8 16 C 0.5 11.667969 0.03125 5.460938 0.00390625 2.433594 L 0 1.835938 C 2.484375 0.574219 5.5 0 8 0 Z M 11.769531 4.203125 L 11.761719 4.203125 L 7.890625 8.160156 L 6.433594 6.4375 C 5.738281 5.644531 4.792969 6.25 4.570312 6.40625 L 7.929688 10.285156 L 12.570312 4.136719 C 12.230469 3.867188 11.933594 4.054688 11.769531 4.203125 Z M 11.769531 4.203125 "/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 801 B |
51
client_v2/public/index.html
Normal file
51
client_v2/public/index.html
Normal file
@@ -0,0 +1,51 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||
<meta name="google" content="notranslate">
|
||||
<meta http-equiv="x-dns-prefetch-control" content="off">
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="assets/apple-touch-icon-180x180.png" />
|
||||
<link rel="mask-icon" href="assets/safari-pinned-tab.svg" color="#67B279">
|
||||
<link rel="icon" type="image/png" href="assets/favicon.png" sizes="48x48">
|
||||
<title>AdGuard Home</title>
|
||||
<style>
|
||||
.wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
[data-theme="DARK"] .wrapper {
|
||||
background-color: #f5f7fb;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
You need to enable JavaScript to run this app.
|
||||
</noscript>
|
||||
<div id="root">
|
||||
<div class="wrapper"></div>
|
||||
</div>
|
||||
<script>
|
||||
(function() {
|
||||
var LOCAL_STORAGE_THEME_KEY = 'account_theme';
|
||||
var theme = 'light';
|
||||
|
||||
try {
|
||||
theme = window.localStorage.getItem(LOCAL_STORAGE_THEME_KEY);
|
||||
} catch(e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
document.body.dataset.theme = theme;
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
21
client_v2/public/install.html
Normal file
21
client_v2/public/install.html
Normal file
@@ -0,0 +1,21 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||
<meta name="google" content="notranslate">
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="assets/apple-touch-icon-180x180.png" />
|
||||
<link rel="mask-icon" href="assets/safari-pinned-tab.svg" color="#67B279">
|
||||
<link rel="icon" type="image/png" href="assets/favicon.png" sizes="48x48">
|
||||
<title>Setup AdGuard Home</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
You need to enable JavaScript to run this app.
|
||||
</noscript>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
28
client_v2/public/login.html
Normal file
28
client_v2/public/login.html
Normal file
@@ -0,0 +1,28 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||
<meta name="google" content="notranslate">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="assets/apple-touch-icon-180x180.png" />
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
||||
<link rel="mask-icon" href="assets/safari-pinned-tab.svg" color="#67B279">
|
||||
<link rel="icon" type="image/png" href="assets/favicon.png" sizes="48x48">
|
||||
<title>Login</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
You need to enable JavaScript to run this app.
|
||||
</noscript>
|
||||
<div id="root"></div>
|
||||
<script>
|
||||
(function() {
|
||||
var prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
var currentTheme = prefersDark ? 'dark' : 'light';
|
||||
document.body.dataset.theme = currentTheme;
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
4
client_v2/tests/constants.ts
Normal file
4
client_v2/tests/constants.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export const ADMIN_USERNAME = 'admin';
|
||||
export const ADMIN_PASSWORD = 'superpassword';
|
||||
export const PORT = 3000;
|
||||
export const CONFIG_FILE_PATH = '/tmp/AdGuard.e2e.yaml';
|
||||
34
client_v2/tests/e2e/control-panel.spec.ts
Normal file
34
client_v2/tests/e2e/control-panel.spec.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { ADMIN_USERNAME, ADMIN_PASSWORD } from '../constants';
|
||||
|
||||
test.describe('Control Panel', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/login.html');
|
||||
await page.getByTestId('username').click();
|
||||
await page.getByTestId('username').fill(ADMIN_USERNAME);
|
||||
await page.getByTestId('password').click();
|
||||
await page.getByTestId('password').fill(ADMIN_PASSWORD);
|
||||
await page.keyboard.press('Tab');
|
||||
await page.getByTestId('sign_in').click();
|
||||
await page.waitForURL((url) => !url.href.endsWith('/login.html'));
|
||||
});
|
||||
|
||||
test('should sign out successfully', async ({ page }) => {
|
||||
await page.getByTestId('sign_out').click();
|
||||
|
||||
await page.waitForURL((url) => url.href.endsWith('/login.html'));
|
||||
|
||||
await expect(page.getByTestId('sign_in')).toBeVisible();
|
||||
});
|
||||
|
||||
test('should change theme to dark and then light', async ({ page }) => {
|
||||
await page.getByTestId('theme_dark').click();
|
||||
|
||||
await expect(page.locator('body[data-theme="dark"]')).toBeVisible();
|
||||
|
||||
|
||||
await page.getByTestId('theme_light').click();
|
||||
|
||||
await expect(page.locator('body:not([data-theme="dark"])')).toBeVisible();
|
||||
});
|
||||
});
|
||||
64
client_v2/tests/e2e/dhcp.spec.ts
Normal file
64
client_v2/tests/e2e/dhcp.spec.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { ADMIN_PASSWORD, ADMIN_USERNAME } from '../constants';
|
||||
import { getDHCPConfig } from '../helpers/network';
|
||||
|
||||
const dhcpConfig = getDHCPConfig();
|
||||
const INTERFACE_NAME = dhcpConfig.interfaceName;
|
||||
const RANGE_START = dhcpConfig.rangeStart;
|
||||
const RANGE_END = dhcpConfig.rangeEnd;
|
||||
const SUBNET_MASK = dhcpConfig.subnetMask;
|
||||
const LEASE_TIME = '86400';
|
||||
|
||||
test.describe('DHCP Configuration', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/login.html');
|
||||
await page.getByTestId('username').click();
|
||||
await page.getByTestId('username').fill(ADMIN_USERNAME);
|
||||
await page.getByTestId('password').click();
|
||||
await page.getByTestId('password').fill(ADMIN_PASSWORD);
|
||||
await page.keyboard.press('Tab');
|
||||
await page.getByTestId('sign_in').click();
|
||||
await page.waitForURL((url) => !url.href.endsWith('/login.html'));
|
||||
await page.goto(`/#dhcp`);
|
||||
});
|
||||
|
||||
test('should select the correct DHCP interface', async ({ page }) => {
|
||||
await page.getByTestId('interface_name').selectOption(INTERFACE_NAME);
|
||||
expect(await page.locator('select[name="interface_name"]').inputValue()).toBe(INTERFACE_NAME);
|
||||
});
|
||||
|
||||
test('should configure DHCP IPv4 settings correctly', async ({ page }) => {
|
||||
await page.getByTestId('interface_name').selectOption(INTERFACE_NAME);
|
||||
await page.getByTestId('v4_gateway_ip').click();
|
||||
await page.getByTestId('v4_gateway_ip').fill('192.168.1.99');
|
||||
await page.getByTestId('v4_subnet_mask').click();
|
||||
await page.getByTestId('v4_subnet_mask').fill(SUBNET_MASK);
|
||||
await page.getByTestId('v4_range_start').click();
|
||||
await page.getByTestId('v4_range_start').fill(RANGE_START);
|
||||
await page.getByTestId('v4_range_end').click();
|
||||
await page.getByTestId('v4_range_end').fill(RANGE_END);
|
||||
await page.getByTestId('v4_lease_duration').click();
|
||||
await page.getByTestId('v4_lease_duration').fill(LEASE_TIME);
|
||||
await page.getByTestId('v4_save').click();
|
||||
});
|
||||
|
||||
test('should show error for invalid DHCP IPv4 range', async ({ page }) => {
|
||||
await page.getByTestId('interface_name').selectOption(INTERFACE_NAME);
|
||||
await page.getByTestId('v4_range_start').click();
|
||||
await page.getByTestId('v4_range_start').fill(RANGE_END);
|
||||
await page.getByTestId('v4_range_end').click();
|
||||
await page.getByTestId('v4_range_end').fill(RANGE_START);
|
||||
await page.keyboard.press('Tab');
|
||||
|
||||
expect(await page.getByText('Must be greater than range').isVisible()).toBe(true);
|
||||
});
|
||||
|
||||
test('should show error for invalid DHCP IPv4 address', async ({ page }) => {
|
||||
await page.getByTestId('interface_name').selectOption(INTERFACE_NAME);
|
||||
await page.getByTestId('v4_gateway_ip').click();
|
||||
await page.getByTestId('v4_gateway_ip').fill('192.168.1.200s');
|
||||
await page.keyboard.press('Tab');
|
||||
|
||||
expect(await page.getByText('Invalid IPv4 address').isVisible()).toBe(true);
|
||||
});
|
||||
});
|
||||
52
client_v2/tests/e2e/dns-settings.spec.ts
Normal file
52
client_v2/tests/e2e/dns-settings.spec.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { test, expect, type Page } from '@playwright/test';
|
||||
import { ADMIN_USERNAME, ADMIN_PASSWORD } from '../constants';
|
||||
|
||||
test.describe('DNS Settings', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// Login before each test
|
||||
await page.goto('/login.html');
|
||||
await page.getByTestId('username').click();
|
||||
await page.getByTestId('username').fill(ADMIN_USERNAME);
|
||||
await page.getByTestId('password').click();
|
||||
await page.getByTestId('password').fill(ADMIN_PASSWORD);
|
||||
await page.keyboard.press('Tab');
|
||||
await page.getByTestId('sign_in').click();
|
||||
await page.waitForURL((url) => !url.href.endsWith('/login.html'));
|
||||
});
|
||||
|
||||
const runDNSSettingsTest = async (page: Page, address: string) => {
|
||||
await page.goto('/#dns');
|
||||
|
||||
const currentDns = await page.getByTestId('upstream_dns').inputValue();
|
||||
|
||||
await page.getByTestId('upstream_dns').fill(address);
|
||||
await page.getByTestId('dns_upstream_test').click();
|
||||
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
await expect(page.getByTestId('upstream_dns')).toHaveValue(address);
|
||||
|
||||
await page.getByTestId('upstream_dns').fill(currentDns);
|
||||
await page.getByTestId('dns_upstream_save').click({ force: true });
|
||||
};
|
||||
|
||||
test('test for Default DNS', async ({ page }) => {
|
||||
await runDNSSettingsTest(page, 'https://dns10.quad9.net/dns-query');
|
||||
});
|
||||
|
||||
test('test for Plain DNS', async ({ page }) => {
|
||||
await runDNSSettingsTest(page, '94.140.14.140');
|
||||
});
|
||||
|
||||
test('test for DNS-over-HTTPS', async ({ page }) => {
|
||||
await runDNSSettingsTest(page, 'https://unfiltered.adguard-dns.com/dns-query');
|
||||
});
|
||||
|
||||
test('test for DNS-over-TLS', async ({ page }) => {
|
||||
await runDNSSettingsTest(page, 'tls://unfiltered.adguard-dns.com');
|
||||
});
|
||||
|
||||
test('test for DNS-over-QUIC', async ({ page }) => {
|
||||
await runDNSSettingsTest(page, 'quic://unfiltered.adguard-dns.com');
|
||||
});
|
||||
});
|
||||
73
client_v2/tests/e2e/filtering.spec.ts
Normal file
73
client_v2/tests/e2e/filtering.spec.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import { test, expect, type Page } from '@playwright/test';
|
||||
import { execSync } from 'child_process';
|
||||
import { ADMIN_USERNAME, ADMIN_PASSWORD } from '../constants';
|
||||
|
||||
test.describe('Filtering', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// Login before each test
|
||||
await page.goto('/login.html');
|
||||
await page.getByTestId('username').click();
|
||||
await page.getByTestId('username').fill(ADMIN_USERNAME);
|
||||
await page.getByTestId('password').click();
|
||||
await page.getByTestId('password').fill(ADMIN_PASSWORD);
|
||||
await page.keyboard.press('Tab');
|
||||
await page.getByTestId('sign_in').click();
|
||||
await page.waitForURL((url) => !url.href.endsWith('/login.html'));
|
||||
});
|
||||
|
||||
const runTerminalCommand = (command: string) => {
|
||||
try {
|
||||
console.info(`Executing command: ${command}`);
|
||||
|
||||
const output = execSync(command, { encoding: 'utf-8', stdio: 'pipe' }).trim();
|
||||
|
||||
console.info('Command executed successfully.');
|
||||
console.debug(`Command output:\n${output}`);
|
||||
|
||||
return output;
|
||||
} catch (error: any) {
|
||||
console.error(`Command execution failed with error:\n${error.message}`);
|
||||
throw new Error(`Failed to execute command: ${command}\nError: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
const runCustomRuleTest = async (page: Page, domain_to_block: string) => {
|
||||
await page.goto('/#custom_rules');
|
||||
|
||||
await page.getByTestId('custom_rule_textarea').fill(domain_to_block);
|
||||
await page.getByTestId('apply_custom_rule').click();
|
||||
|
||||
const nslookupBlockedResult = await runTerminalCommand(`nslookup ${domain_to_block} 127.0.0.1`).toString();
|
||||
|
||||
console.info(`nslookup blocked CNAME result: '${nslookupBlockedResult}'`);
|
||||
|
||||
const currentRules = await page.getByTestId('custom_rule_textarea').inputValue();
|
||||
console.debug(`Current rules before removal:\n${currentRules}`);
|
||||
|
||||
if (currentRules.includes(domain_to_block)) {
|
||||
const updatedRules = currentRules
|
||||
.split('\n')
|
||||
.filter((line) => line.trim() !== domain_to_block.trim())
|
||||
.join('\n');
|
||||
|
||||
await page.getByTestId('custom_rule_textarea').fill(updatedRules);
|
||||
console.info(`Rule '${domain_to_block}' removed successfully.`);
|
||||
|
||||
console.info('Applying the updated filtering rules after removal.');
|
||||
await page.getByTestId('apply_custom_rule').click();
|
||||
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
|
||||
console.info(`Filtering rules successfully updated after removing '${domain_to_block}'.`);
|
||||
} else {
|
||||
console.warn(`Rule '${domain_to_block}' not found. No changes were made.`);
|
||||
}
|
||||
|
||||
const nslookupUnblockedResult = await runTerminalCommand(`nslookup ${domain_to_block} 127.0.0.1`).toString();
|
||||
console.info(`nslookup unblocked CNAME result: '${nslookupUnblockedResult}'`);
|
||||
};
|
||||
|
||||
test('Test blocking rule for apple.com', async ({ page }) => {
|
||||
await runCustomRuleTest(page, 'apple.com');
|
||||
});
|
||||
});
|
||||
89
client_v2/tests/e2e/general-settings.spec.ts
Normal file
89
client_v2/tests/e2e/general-settings.spec.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { execSync } from 'child_process';
|
||||
import { ADMIN_USERNAME, ADMIN_PASSWORD } from '../constants';
|
||||
|
||||
test.describe('General Settings', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/login.html');
|
||||
await page.getByTestId('username').click();
|
||||
await page.getByTestId('username').fill(ADMIN_USERNAME);
|
||||
await page.getByTestId('password').click();
|
||||
await page.getByTestId('password').fill(ADMIN_PASSWORD);
|
||||
await page.keyboard.press('Tab');
|
||||
await page.getByTestId('sign_in').click();
|
||||
await page.waitForURL((url) => !url.href.endsWith('/login.html'));
|
||||
});
|
||||
|
||||
test('should toggle browsing security feature and verify DNS changes', async ({ page }) => {
|
||||
await page.goto('/#settings');
|
||||
|
||||
const browsingSecurity = await page.getByTestId('safebrowsing');
|
||||
const browsingSecurityLabel = await browsingSecurity.locator('xpath=following-sibling::*[1]');
|
||||
|
||||
const initialState = await browsingSecurity.isChecked();
|
||||
|
||||
if (!initialState) {
|
||||
await browsingSecurityLabel.click();
|
||||
await expect(browsingSecurity).toBeChecked();
|
||||
}
|
||||
|
||||
const resultEnabled = execSync('nslookup totalvirus.com 127.0.0.1').toString();
|
||||
|
||||
await browsingSecurityLabel.click();
|
||||
await expect(browsingSecurity).not.toBeChecked();
|
||||
|
||||
const resultDisabled = execSync('nslookup totalvirus.com 127.0.0.1').toString();
|
||||
|
||||
expect(resultEnabled).not.toEqual(resultDisabled);
|
||||
|
||||
if (initialState) {
|
||||
await browsingSecurityLabel.click();
|
||||
await expect(browsingSecurity).toBeChecked();
|
||||
}
|
||||
});
|
||||
|
||||
test('should toggle parental control feature and verify DNS changes', async ({ page }) => {
|
||||
await page.goto('/#settings');
|
||||
|
||||
const parentalControl = page.getByTestId('parental');
|
||||
const parentalControlLabel = await parentalControl.locator('xpath=following-sibling::*[1]');
|
||||
|
||||
const initialState = await parentalControl.isChecked();
|
||||
|
||||
if (!initialState) {
|
||||
await parentalControlLabel.click();
|
||||
await expect(parentalControl).toBeChecked();
|
||||
}
|
||||
|
||||
const resultEnabled = execSync('nslookup pornhub.com 127.0.0.1').toString();
|
||||
|
||||
await parentalControlLabel.click();
|
||||
await expect(parentalControl).not.toBeChecked();
|
||||
|
||||
const resultDisabled = execSync('nslookup pornhub.com 127.0.0.1').toString();
|
||||
|
||||
expect(resultEnabled).not.toEqual(resultDisabled);
|
||||
|
||||
if (initialState) {
|
||||
await parentalControlLabel.click();
|
||||
await expect(parentalControl).toBeChecked();
|
||||
}
|
||||
});
|
||||
|
||||
test('should toggle safe search feature', async ({ page }) => {
|
||||
await page.goto('/#settings');
|
||||
|
||||
const safeSearch = page.getByTestId('safesearch');
|
||||
const safeSearchLabel = await safeSearch.locator('xpath=following-sibling::*[1]');
|
||||
|
||||
const initialState = await safeSearch.isChecked();
|
||||
|
||||
await safeSearchLabel.click();
|
||||
|
||||
await expect(safeSearch).not.toBeChecked({ checked: initialState });
|
||||
|
||||
await safeSearchLabel.click();
|
||||
|
||||
await expect(safeSearch).toBeChecked({ checked: initialState });
|
||||
});
|
||||
});
|
||||
31
client_v2/tests/e2e/globalSetup.ts
Normal file
31
client_v2/tests/e2e/globalSetup.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { chromium, type FullConfig } from '@playwright/test';
|
||||
|
||||
import { ADMIN_USERNAME, ADMIN_PASSWORD, PORT } from '../constants';
|
||||
|
||||
async function globalSetup(config: FullConfig) {
|
||||
const browser = await chromium.launch({
|
||||
slowMo: 100,
|
||||
});
|
||||
const page = await browser.newPage({ baseURL: config.webServer?.url });
|
||||
|
||||
try {
|
||||
await page.goto('/');
|
||||
await page.getByTestId('install_get_started').click();
|
||||
await page.getByTestId('install_web_port').fill(PORT.toString());
|
||||
await page.getByTestId('install_next').click();
|
||||
await page.getByTestId('install_username').fill(ADMIN_USERNAME);
|
||||
await page.getByTestId('install_password').fill(ADMIN_PASSWORD);
|
||||
await page.getByTestId('install_confirm_password').click();
|
||||
await page.getByTestId('install_confirm_password').fill(ADMIN_PASSWORD);
|
||||
await page.getByTestId('install_next').click();
|
||||
await page.getByTestId('install_next').click();
|
||||
await page.getByTestId('install_open_dashboard').click();
|
||||
await page.waitForURL((url) => !url.href.endsWith('/install.html'));
|
||||
} catch (error) {
|
||||
console.error('Error during global setup:', error);
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
}
|
||||
|
||||
export default globalSetup;
|
||||
11
client_v2/tests/e2e/globalTeardown.ts
Normal file
11
client_v2/tests/e2e/globalTeardown.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { existsSync, unlinkSync } from 'fs';
|
||||
import { CONFIG_FILE_PATH } from '../constants';
|
||||
|
||||
async function globalTeardown() {
|
||||
// Remove the test config file
|
||||
if (existsSync(CONFIG_FILE_PATH)) {
|
||||
unlinkSync(CONFIG_FILE_PATH);
|
||||
}
|
||||
}
|
||||
|
||||
export default globalTeardown;
|
||||
16
client_v2/tests/e2e/login.spec.ts
Normal file
16
client_v2/tests/e2e/login.spec.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { test } from '@playwright/test';
|
||||
import { ADMIN_PASSWORD, ADMIN_USERNAME } from '../constants';
|
||||
|
||||
|
||||
test.describe('Login', () => {
|
||||
test('should successfully log in with valid credentials', async ({ page }) => {
|
||||
await page.goto('/login.html');
|
||||
await page.getByTestId('username').click();
|
||||
await page.getByTestId('username').fill(ADMIN_USERNAME);
|
||||
await page.getByTestId('password').click();
|
||||
await page.getByTestId('password').fill(ADMIN_PASSWORD);
|
||||
await page.keyboard.press('Tab');
|
||||
await page.getByTestId('sign_in').click();
|
||||
await page.waitForURL((url) => !url.href.endsWith('/login.html'));
|
||||
});
|
||||
});
|
||||
124
client_v2/tests/e2e/querylog.spec.ts
Normal file
124
client_v2/tests/e2e/querylog.spec.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { ADMIN_USERNAME, ADMIN_PASSWORD } from '../constants';
|
||||
|
||||
test.describe('QueryLog', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/login.html');
|
||||
await page.getByTestId('username').click();
|
||||
await page.getByTestId('username').fill(ADMIN_USERNAME);
|
||||
await page.getByTestId('password').click();
|
||||
await page.getByTestId('password').fill(ADMIN_PASSWORD);
|
||||
await page.keyboard.press('Tab');
|
||||
await page.getByTestId('sign_in').click();
|
||||
await page.waitForURL((url) => !url.href.endsWith('/login.html'));
|
||||
});
|
||||
|
||||
test('Search of queryLog should work correctly', async ({ page }) => {
|
||||
await page.route('/control/querylog', async (route) => {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify(
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"answer": [
|
||||
{
|
||||
"type": "A",
|
||||
"value": "77.88.44.242",
|
||||
"ttl": 294
|
||||
},
|
||||
{
|
||||
"type": "A",
|
||||
"value": "5.255.255.242",
|
||||
"ttl": 294
|
||||
},
|
||||
{
|
||||
"type": "A",
|
||||
"value": "77.88.55.242",
|
||||
"ttl": 294
|
||||
}
|
||||
],
|
||||
"answer_dnssec": false,
|
||||
"cached": false,
|
||||
"client": "127.0.0.1",
|
||||
"client_info": {
|
||||
"whois": {},
|
||||
"name": "localhost",
|
||||
"disallowed_rule": "127.0.0.1",
|
||||
"disallowed": false
|
||||
},
|
||||
"client_proto": "",
|
||||
"elapsedMs": "78.163167",
|
||||
"question": {
|
||||
"class": "IN",
|
||||
"name": "ya.ru",
|
||||
"type": "A"
|
||||
},
|
||||
"reason": "NotFilteredNotFound",
|
||||
"rules": [],
|
||||
"status": "NOERROR",
|
||||
"time": "2024-07-17T16:02:37.500662+02:00",
|
||||
"upstream": "https://dns10.quad9.net:443/dns-query"
|
||||
},
|
||||
{
|
||||
"answer": [
|
||||
{
|
||||
"type": "A",
|
||||
"value": "77.88.55.242",
|
||||
"ttl": 351
|
||||
},
|
||||
{
|
||||
"type": "A",
|
||||
"value": "77.88.44.242",
|
||||
"ttl": 351
|
||||
},
|
||||
{
|
||||
"type": "A",
|
||||
"value": "5.255.255.242",
|
||||
"ttl": 351
|
||||
}
|
||||
],
|
||||
"answer_dnssec": false,
|
||||
"cached": false,
|
||||
"client": "127.0.0.1",
|
||||
"client_info": {
|
||||
"whois": {},
|
||||
"name": "localhost",
|
||||
"disallowed_rule": "127.0.0.1",
|
||||
"disallowed": false
|
||||
},
|
||||
"client_proto": "",
|
||||
"elapsedMs": "5051.070708",
|
||||
"question": {
|
||||
"class": "IN",
|
||||
"name": "ya.ru",
|
||||
"type": "A"
|
||||
},
|
||||
"reason": "NotFilteredNotFound",
|
||||
"rules": [],
|
||||
"status": "NOERROR",
|
||||
"time": "2024-07-17T16:02:37.4983+02:00",
|
||||
"upstream": "https://dns10.quad9.net:443/dns-query"
|
||||
}
|
||||
],
|
||||
"oldest": "2024-07-17T16:02:37.4983+02:00"
|
||||
}
|
||||
),
|
||||
});
|
||||
});
|
||||
|
||||
await page.goto('/#logs');
|
||||
|
||||
await page.getByTestId('querylog_search').fill('127.0.0.1');
|
||||
|
||||
const [request] = await Promise.all([
|
||||
page.waitForRequest((req) => req.url().includes('/control/querylog')),
|
||||
]);
|
||||
|
||||
if (request) {
|
||||
expect(request.url()).toContain('search=127.0.0.1');
|
||||
expect(await page.getByTestId('querylog_cell').first().isVisible()).toBe(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
65
client_v2/tests/helpers/network.ts
Normal file
65
client_v2/tests/helpers/network.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { networkInterfaces } from 'os';
|
||||
import type { NetworkInterfaceInfo } from 'node:os';
|
||||
|
||||
interface DHCPConfig {
|
||||
interfaceName: string;
|
||||
rangeStart: string;
|
||||
rangeEnd: string;
|
||||
subnetMask: string;
|
||||
}
|
||||
|
||||
const DEFAULT_SUBNET_MASK = '255.255.255.0';
|
||||
const DEFAULT_SUBNET_MASK_OCTETS = DEFAULT_SUBNET_MASK.split('.').map(Number);
|
||||
|
||||
function checkIsIPv4(addr: NetworkInterfaceInfo): boolean {
|
||||
return addr.family === 'IPv4' && !addr.internal;
|
||||
}
|
||||
|
||||
function calculateNetwork(ip: number[], mask: number[]): number[] {
|
||||
// Calculate the network address by applying the bitwise AND operation.
|
||||
// eslint-disable-next-line no-bitwise
|
||||
return ip.map((octet, i) => octet & mask[i]);
|
||||
}
|
||||
|
||||
function calculateBroadcast(network: number[], mask: number[]): number[] {
|
||||
// Calculate the broadcast address by ORing the network address with the inverted mask.
|
||||
// eslint-disable-next-line no-bitwise
|
||||
return network.map((octet, i) => octet | (~mask[i] & 255));
|
||||
}
|
||||
|
||||
export function getDHCPConfig(): DHCPConfig {
|
||||
const interfaces = networkInterfaces();
|
||||
|
||||
// Select the first interface that has a valid non-internal IPv4 address.
|
||||
const ipV4Interface = Object.entries(interfaces)
|
||||
.map(([name, addresses]) => ({ name, addresses }))
|
||||
.find((i) => i.addresses?.some(checkIsIPv4));
|
||||
|
||||
if (!ipV4Interface) {
|
||||
throw new Error('No suitable network interface found');
|
||||
}
|
||||
|
||||
// Get the first valid IPv4 address from the interface.
|
||||
const ipv4Address = ipV4Interface.addresses.find(checkIsIPv4);
|
||||
|
||||
const ip = ipv4Address.address.split('.').map(Number);
|
||||
const mask = ipv4Address.netmask?.split('.').map(Number) || DEFAULT_SUBNET_MASK_OCTETS;
|
||||
|
||||
const network = calculateNetwork(ip, mask);
|
||||
|
||||
// Calculate first usable address (network address + 1)
|
||||
const rangeStart = [...network];
|
||||
rangeStart[3] = network[3] + 1;
|
||||
|
||||
// Calculate broadcast address and then the last usable address (broadcast - 1)
|
||||
const broadcast = calculateBroadcast(network, mask);
|
||||
const rangeEnd = [...broadcast];
|
||||
rangeEnd[3] = broadcast[3] - 1;
|
||||
|
||||
return {
|
||||
interfaceName: ipV4Interface.name,
|
||||
rangeStart: rangeStart.join('.'),
|
||||
rangeEnd: rangeEnd.join('.'),
|
||||
subnetMask: ipv4Address.netmask || DEFAULT_SUBNET_MASK,
|
||||
};
|
||||
}
|
||||
31
client_v2/tsconfig.json
Normal file
31
client_v2/tsconfig.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"jsx": "react",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"allowArbitraryExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"allowJs": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": false,
|
||||
"noImplicitAny": false,
|
||||
"skipLibCheck": true,
|
||||
"types": [
|
||||
"node",
|
||||
"react",
|
||||
"react-dom",
|
||||
"user-agent-data-types"
|
||||
],
|
||||
"paths": {
|
||||
"Twosky": [
|
||||
"../.twosky.json"
|
||||
],
|
||||
"panel/*": [
|
||||
"./src/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"include": ["src", "global.d.ts", "src/types/**/*.d.ts"]
|
||||
}
|
||||
8
client_v2/vitest.config.ts
Normal file
8
client_v2/vitest.config.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
environment: 'jsdom',
|
||||
include: ['src/__tests__/**'],
|
||||
},
|
||||
});
|
||||
165
client_v2/webpack.common.js
Normal file
165
client_v2/webpack.common.js
Normal file
@@ -0,0 +1,165 @@
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import HtmlWebpackPlugin from 'html-webpack-plugin';
|
||||
import { CleanWebpackPlugin } from 'clean-webpack-plugin';
|
||||
import CopyPlugin from 'copy-webpack-plugin';
|
||||
import MiniCssExtractPlugin from 'mini-css-extract-plugin';
|
||||
import * as url from 'url';
|
||||
import { BUILD_ENVS } from './constants.js';
|
||||
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
const __dirname = url.fileURLToPath(new URL('.', import.meta.url));
|
||||
|
||||
const RESOURCES_PATH = path.resolve(__dirname);
|
||||
const ENTRY_REACT = path.resolve(RESOURCES_PATH, 'src/index.tsx');
|
||||
const ENTRY_INSTALL = path.resolve(RESOURCES_PATH, 'src/install/index.tsx');
|
||||
const ENTRY_LOGIN = path.resolve(RESOURCES_PATH, 'src/login/index.tsx');
|
||||
const HTML_PATH = path.resolve(RESOURCES_PATH, 'public/index.html');
|
||||
const HTML_INSTALL_PATH = path.resolve(RESOURCES_PATH, 'public/install.html');
|
||||
const HTML_LOGIN_PATH = path.resolve(RESOURCES_PATH, 'public/login.html');
|
||||
const ASSETS_PATH = path.resolve(RESOURCES_PATH, 'public/assets');
|
||||
|
||||
const PUBLIC_PATH = path.resolve(__dirname, '../build/static');
|
||||
const PUBLIC_ASSETS_PATH = path.resolve(PUBLIC_PATH, 'assets');
|
||||
|
||||
const BUILD_ENV = BUILD_ENVS[process.env.BUILD_ENV];
|
||||
|
||||
const isDev = BUILD_ENV === BUILD_ENVS.dev;
|
||||
|
||||
function getAliasesFromTsconfig(tsconfigPath, resourcesPath) {
|
||||
const tsConfig = JSON.parse(fs.readFileSync(tsconfigPath, 'utf8'));
|
||||
const aliases = {};
|
||||
if (tsConfig.compilerOptions && tsConfig.compilerOptions.paths) {
|
||||
Object.entries(tsConfig.compilerOptions.paths).forEach(([alias, targetArr]) => {
|
||||
if (alias.endsWith('/*')) {
|
||||
const aliasName = alias.replace('/*', '');
|
||||
let target = targetArr[0].replace('/*', '');
|
||||
aliases[aliasName] = path.resolve(resourcesPath, target);
|
||||
}
|
||||
});
|
||||
}
|
||||
return aliases;
|
||||
}
|
||||
|
||||
const tsConfigPath = path.resolve(__dirname, './tsconfig.json');
|
||||
const aliasesFromTsconfig = getAliasesFromTsconfig(tsConfigPath, RESOURCES_PATH);
|
||||
|
||||
const config = {
|
||||
mode: BUILD_ENV,
|
||||
target: 'web',
|
||||
context: RESOURCES_PATH,
|
||||
entry: {
|
||||
main: ENTRY_REACT,
|
||||
install: ENTRY_INSTALL,
|
||||
login: ENTRY_LOGIN,
|
||||
},
|
||||
output: {
|
||||
path: PUBLIC_PATH,
|
||||
filename: '[name].[chunkhash].js',
|
||||
},
|
||||
resolve: {
|
||||
modules: ['node_modules'],
|
||||
extensions: ['.js', '.jsx', '.ts', '.tsx'],
|
||||
alias: {
|
||||
...aliasesFromTsconfig,
|
||||
},
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.ya?ml$/,
|
||||
type: 'json',
|
||||
use: 'yaml-loader',
|
||||
},
|
||||
{
|
||||
test: /\.module\.pcss$/i,
|
||||
use: [
|
||||
{
|
||||
loader: MiniCssExtractPlugin.loader,
|
||||
},
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
importLoaders: 1,
|
||||
modules: {
|
||||
localIdentName: isDev ? '[name]__[local]--[hash:base64:5]' : '[hash:base64]',
|
||||
namedExport: false,
|
||||
exportLocalsConvention: 'asIs',
|
||||
},
|
||||
esModule: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
loader: 'postcss-loader',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.p?css$/i,
|
||||
exclude: /\.module\.pcss$/i,
|
||||
use: [
|
||||
{
|
||||
loader: MiniCssExtractPlugin.loader,
|
||||
},
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
importLoaders: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
loader: 'postcss-loader',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
exclude: /node_modules/,
|
||||
use: {
|
||||
loader: 'ts-loader',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
new CleanWebpackPlugin({
|
||||
root: PUBLIC_PATH,
|
||||
verbose: false,
|
||||
dry: false,
|
||||
}),
|
||||
new HtmlWebpackPlugin({
|
||||
inject: true,
|
||||
cache: false,
|
||||
chunks: ['main'],
|
||||
template: HTML_PATH,
|
||||
}),
|
||||
new HtmlWebpackPlugin({
|
||||
inject: true,
|
||||
cache: false,
|
||||
chunks: ['install'],
|
||||
filename: 'install.html',
|
||||
template: HTML_INSTALL_PATH,
|
||||
}),
|
||||
new HtmlWebpackPlugin({
|
||||
inject: true,
|
||||
cache: false,
|
||||
chunks: ['login'],
|
||||
filename: 'login.html',
|
||||
template: HTML_LOGIN_PATH,
|
||||
}),
|
||||
new MiniCssExtractPlugin({
|
||||
filename: isDev ? '[name].css' : '[name].[hash].css',
|
||||
chunkFilename: isDev ? '[id].css' : '[id].[hash].css',
|
||||
}),
|
||||
new CopyPlugin({
|
||||
patterns: [
|
||||
{
|
||||
from: ASSETS_PATH,
|
||||
to: PUBLIC_ASSETS_PATH,
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
export default config;
|
||||
53
client_v2/webpack.dev.js
Normal file
53
client_v2/webpack.dev.js
Normal file
@@ -0,0 +1,53 @@
|
||||
import { merge } from 'webpack-merge';
|
||||
import yaml from 'js-yaml';
|
||||
import fs from 'fs';
|
||||
import { BASE_URL } from './constants.js';
|
||||
import common from './webpack.common.js';
|
||||
|
||||
const ZERO_HOST = '0.0.0.0';
|
||||
const LOCALHOST = '127.0.0.1';
|
||||
const DEFAULT_PORT = 80;
|
||||
|
||||
/**
|
||||
* Get document, or throw exception on error
|
||||
* @returns {{bind_host: string, bind_port: number}}
|
||||
*/
|
||||
const importConfig = () => {
|
||||
try {
|
||||
const doc = yaml.safeLoad(fs.readFileSync('../AdguardHome.yaml', 'utf8'));
|
||||
const { bind_host, bind_port } = doc;
|
||||
return {
|
||||
bind_host,
|
||||
bind_port,
|
||||
};
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return {
|
||||
bind_host: ZERO_HOST,
|
||||
bind_port: DEFAULT_PORT,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const getDevServerConfig = (proxyUrl = BASE_URL) => {
|
||||
const { bind_host: host, bind_port: port } = importConfig();
|
||||
const { DEV_SERVER_PORT } = process.env;
|
||||
|
||||
const devServerHost = host === ZERO_HOST ? LOCALHOST : host;
|
||||
const devServerPort = DEV_SERVER_PORT || port + 8000;
|
||||
|
||||
return {
|
||||
hot: true,
|
||||
open: true,
|
||||
host: devServerHost,
|
||||
port: devServerPort,
|
||||
proxy: {
|
||||
[proxyUrl]: `http://${devServerHost}:${port}`,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export default merge(common, {
|
||||
devtool: 'eval-source-map',
|
||||
...(process.env.WEBPACK_DEV_SERVER ? { devServer: getDevServerConfig(BASE_URL) } : undefined),
|
||||
});
|
||||
9
client_v2/webpack.prod.js
Normal file
9
client_v2/webpack.prod.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import { merge } from 'webpack-merge';
|
||||
import common from './webpack.common.js';
|
||||
|
||||
export default merge(common, {
|
||||
stats: 'minimal',
|
||||
performance: {
|
||||
hints: false,
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user